20210107のRubyに関する記事は21件です。

[Rails] RSpec エラー expected `User.count` to have changed by 1, but was not given a block

はじめに

Rails のテストツール Rspec を使ってみました。
使用中に、expected User.count to have changed by 1, but was not given a block と言うエラーにハマったので備忘録として残します。

問題のソースコード

これがエラーの出るソースコードです。

エラーのでるソース
      expect(
        find('input[name="commit"]').click 
      ).to change{User.count}.by(1)
エラーの内容

エラー  expected User.count to have changed by 1, but was not given a block
翻訳  User.countが1変更されると予想されましたが、ブロックが与えられませんでした

change マッチャを使うときは、expectをブロックにしなきゃいけないよって事でした。
つまり、()だと引数を渡してるので、{}にして動作そのものを渡せって事みたいです。
それを踏まえて、ソースを書き直します。

正常に動作するソース
      expect{
        find('input[name="commit"]').click 
      }.to change{User.count}.by(1)

これで実行したら正常に動作しました。

ブロックは メゾット名 do / end って書き方もあるのでついでにこのようにソースを書き換えて実行しました。

実験1
      expect do
        find('input[name="commit"]').click 
      end.to change{User.count}.by(1) 
実験1結果 成功!!
    27:       # }.to change{User.count}.by(1)
    28:       #実験
    29:       # expect(
    30:       #   find('input[name="commit"]').click 
    31:       # ).to change{User.count}.by(1)
 => 32:       binding.pry
    33:       expect do
    34:         find('input[name="commit"]').click 
    35:       end.to change{User.count}.by(1) 
    36: 
    37: 

[1] pry(#<RSpec::ExampleGroups::Nested::Nested>)> exit
    正しい情報を入力すればユーザー新規登録ができてトップページに移動する
  ユーザー新規登録ができないとき
    誤った情報ではユーザー新規登録ができずに新規登録ページへ戻ってくる

Finished in 17.61 seconds (files took 1.73 seconds to load)
2 examples, 0 failures

メゾット名 do / end でも正常に動くことを確認しました。

ついでに、もう一つ確認しました。chage のブロックを do / end に変更。

実験2
      expect do
        find('input[name="commit"]').click 
      end.to change do User.count end.by(1) 
実験2結果 失敗。。。
     Failure/Error:
       expect do
         find('input[name="commit"]').click 
       end.to change do User.count end.by(1) 

     SyntaxError:
       Block not received by the `change` matcher. Perhaps you want to use `{ ... }` instead of do/end?

構文エラーになりました。

エラーの内容

エラー  Block not received by the change matcher. Perhaps you want to use { ... } instead of do/end?
翻訳  changeマッチャーがブロックを受信していません。 おそらく、do / endの代わりに {...}を使用したいですか?

結果は、{} 使えって言われました。
わざわざ、do / end なんて使わず{}で統一するのが良さそうですね。

まとめ

  • ()だと引数になって、{}だとブロックをわたす。
  • expect {}.to change{}.by() と書く。
  • do / end は使わない。

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

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

RailsにおけるURLの直打ちを阻止する方法

きっかけ

viewファイルに条件分岐を作っていても、そもそもUrlから直打ちされてしまうとページに飛ばれてしまうことに気づいたため。

結論

任意のコントローラーにbefore_actionを使って、アクションを起こす前段階の条件分岐を作っておく。

コード

items.controller.rb
class ItemsController < ApplicationController
  #before_actionを使って、コントーラーのアクションに飛ぶ前に条件分岐をかける
  before_action :correct_user, only: [:edit]
  before_action :item_find, only: [:show, :edit, :update, :destroy]

  def edit
  end

  private

  def item_params
    params.require(:item).permit(:title, :explain, :category_id, :condition_id, :price, :delivery_fee_id, :prefecture_id, :delivery_date_id, :image).merge(user_id: current_user.id)
  end
# before_actionをかけたメソッドでユーザーの選別を行っている
  def correct_user
    @item = Item.find(params[:id])
    if user_signed_in? && @item.user == current_user
      render :edit
    else 
      redirect_to root_path
    end
  end

補足

Urlに直打ちしているということは、ルーティングを経由して必ずコントローラーにページの開示を求めにくるから、コントーラーに記述する必要がある。MVCの流れを今一度再確認できた。

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

Ransackでor検索機能を作る。

使用環境

rails 6.0.2
ubuntu (WSL)

手順

gemインストール

gem 'ransack'

bundleを忘れずに。

内容の検索

post_controllerは既に作ってあるものとします。

posts_controller.rb
  def index
    unless params[:q].blank?
       #入力された単語を空白で分割
      split_keyword = params[:q][:content_cont].split(/\p{blank}/)
    end
    @q = Post.ransack(content_cont_any: split_keyword)
    @posts = @q.result
  end

入力(検索ワード)があるか調べる。
ここがないとエラーになると思う。

unless params[:q].blnak?

検索フォーム

posts/index.html.slim
 = search_form_for @q, class: 'mb-5 search-container' do |f|
   .form-group.row
     = f.search_field :content_cont, placeholder: "キーワードを入力してください", class: "form-control"
     = f.submit class: "btn btn-outline-primary"

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

【Ruby】3桁の整数を取得しif文を使って条件分岐 ( digitsメソッド )

概要

前回書いた記事で使用した digitsメソッド を使って3桁の整数を位ごとに分けて条件分岐をするという問題を解いてみました。前回とは少し視点が違うので理解を深めるのには良いと考え記事にさせていただきました。

目次

  • 実践

    • 問題
      • 条件
    • 解答
  • 補足

    • 変数numの処理結果
    • 変数sumの処理結果
  • まとめ

  • 参考文献

実践

問題

3桁の整数があります。その整数の 「百の位・十の位・一の位の和」 を出力し、10の倍数との差を出力するプログラムを書いてください。

条件

  • 10の倍数との差が 2 以内であればTrue
  • それ以外であれば 10の倍数との差は◯です
  • ただし、10の倍数との差が近い方を出力する。

解答

def near_ten_multiple(i)
  num = i.digits.take(3)   # 1の位, 10の位, 100の位を取得し変数numに格納
  sum = (num.sum) % 10     # 3桁の合計を取得し1の位を取得

  if sum <= 2 || sum >= 8  # 2以下 または 8以上のとき
    p 'True'
  elsif sum >= 5           # 5, 6, 7 のとき
    p "10の倍数との差は#{ 10 - sum }です"
  else                     # 3, 4 のとき
    p "10の倍数との差は#{sum}です"
  end
end

# メソッド呼び出し
near_ten_multiple(117)   # 1 + 1 + 7 = 9
near_ten_multiple(111)   # 1 + 1 + 1 = 3
near_ten_multiple(123)   # 1 + 2 + 3 = 6

# ターミナル出力結果
# "True"
# "10の倍数との差は3です"
# "10の倍数との差は4です"

補足

変数num の処理結果

digits.take(3) で引数で指定した位の数だけ1の位から順番に取得しています。今回は合計を取得したいだけなのであまり順番に関しては考慮しなくて良いですが、このような取得結果になっています。

num = i.digits.take(3)

#ターミナル出力結果
# 117 => [7, 1, 1]
# 111 => [1, 1, 1]
# 123 => [3, 2, 1]

変数sum の処理結果

sum で合計値を出したあとに % 10 をすることで 1の位を取得 しています。
たとえば、24という数字があったとします。%10 をすると

24 ÷ 10 = 2 余り 4になります。この余りが 1の位 になります。

sum = (num.sum) % 10

#ターミナル出力結果
# 9
# 3
# 6

まとめ

  • 整数を10で割ったときの余りは 1の位 になる

参考文献

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

rubyとpythonのif文の書き方の違い

Progateでpythonの基本的な文法を勉強中なので、rubyとの違いをまとめておく。

rubyのif文

・if〜endで囲む

x = 100

if x == 100
 print "xは100です"
else
 print "xは100じゃないです"
end

pythonのif文

・条件式の最後に「:(コロン)」をつける
・実行する処理の記述はインデントする

x = 100
if x == 100:
    print('xは100です')
else:
    print('xは100じゃないです')

pythonはインデントが動作に直接影響するから、自然と可読性が高いコードになりそうで良き

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

サーバーにruby、クライアントにPHPでgRPCを頑張る

はじめに

google謹製のRPCフレームワークであるgRPCを使って、サーバーにruby、クライアントにPHPを使って、異なる言語間を共通のI/FでAPI通信してみたいと思います。

異なる言語間としているのはgRPCのメリットであるIDL定義によるI/Fの共通化の醍醐味を味わってみたいというより、PHPでgRPCサーバの構築ができないという現実に対する条件反射のようなものですが、結果的に醍醐味もやってくるので、味わってみたいと思います。

クライアントにPHPを使う場合、サーバー側はどうもgoで紹介しているところが多いので、ここではrubyを使ってみます(はい、go知らないの確定)。

概要はここで書くまでもないので、さっそく。
ここではAPIとして、ドメインを指定すると有効期限を取ってくる、というものを書いてみます。

INPUT: FQDN
OUTPUT: yyyy/mm/dd hh:ii:ss

API(gRPC): 定義したI/Oで共通I/Fを用意
クライアント(PHP): サーバを通じてAPIにFQDNをリクエスト
サーバ(ruby): クライアントからリクエストされたFQDNからSSL証明書の有効期限を調べてAPI経由で日付をレスポンス

事前作業

pecl install grpc
pecl install protobuf
vi ()/php.ini
    extension=grpc.so
    extension=protobuf.so

gem install grpc
gem install grpc-tools

mkdir ~/projects/grpc_trial/
cd ~/projects/grpc_trial/

composer require grpc/grpc
composer require google/protobuf

mkdir {protos,php,ruby}

実作業

vi protos/checkexpires.proto
    (後述)

grpc_tools_ruby_protoc -I ./protos --ruby_out=./ruby --grpc_out=./ruby ./protos/checkexpires.proto
ls ruby/ | grep checkexpires
    checkexpires_pb.rb
    checkexpires_services_pb.rb

protoc --proto_path=./protos --php_out=./php --grpc_out=./php ./protos/checkexpires.proto --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin
ls php/Checkexpires/
    CheckReply.php  CheckRequest.php  GetSslClient.php

vi checkexpires.rb
    (後述)

vi checkexpires.php
    (後述)

ruby ./checkexpires.rb &
php ./checkexpires.php 
php checkexpires.php example.com
    2021/12/25 23:59:59
checkexpires.proto
syntax = "proto3";

package checkexpires;

import "google/protobuf/timestamp.proto";

service GetSsl {
  rpc getExpire (CheckRequest) returns (CheckReply) {}
}

message CheckRequest {
  string fqdn = 1;
}

message CheckReply {
  google.protobuf.Timestamp timestamp = 1;
}
checkexpires.rb
this_dir = File.expand_path(File.dirname(__FILE__))
lib_dir = File.join(this_dir, 'ruby')
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)

require 'time'
require 'grpc'
require 'checkexpires_services_pb'

class CheckexpiresServer < Checkexpires::GetSsl::Service
  def get_expire(check_req, _unused_call)
    end_at = `openssl s_client -connect #{check_req.fqdn}:443 </dev/null 2>/dev/null|openssl x509 -text | grep "Not After"`
    end_at = end_at.split(' : ').pop
    end_at = end_at.gsub("\n", '')
    end_at = Time.parse(end_at).to_i
    end_at = Time.at(end_at)
    Checkexpires::CheckReply.new(timestamp: end_at)
  end
end

def main
  s = GRPC::RpcServer.new
  s.add_http2_port('localhost:50051', :this_port_is_insecure)
  s.handle(CheckexpiresServer)
  s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT'])
end

main
checkexpires.php
<?php
require dirname(__FILE__).'/vendor/autoload.php';
require dirname(__FILE__).'/php/Checkexpires/GetSslClient.php';
require dirname(__FILE__).'/php/Checkexpires/CheckReply.php';
require dirname(__FILE__).'/php/Checkexpires/CheckRequest.php';
require dirname(__FILE__).'/php/GPBMetadata/Checkexpires.php';
$server = 'localhost:50051'; //checkexpires.rb

if (is_null($argv[1] ?? null)) {
    echo "need to input fqdn\n";
    exit(1);
}

try {
    $client = new Checkexpires\GetSslClient($server, [
        'credentials' => Grpc\ChannelCredentials::createInsecure(),
    ]);
    $request = new Checkexpires\CheckRequest();
    $request->setFqdn($argv[1]);
    list($reply, $status) = $client->getExpire($request)->wait();

    if (($status->code ?? null) === 0) {
        $ts = $reply->getTimestamp()->getSeconds();
        echo date('Y/m/d H:i:s', $ts);
        exit(0);
    }
} catch (Exception $e) {
    echo $e->getMessage();
    exit(1);
}

補足

せっかく異なる言語で共通I/Fを定義しているのですから、それぞれのコンパイラも一個に共通化してしまいましょう。

vi ~/.bashrc
    function protoc2() {
        grpc_tools_ruby_protoc -I ./protos --ruby_out=./ruby --grpc_out=./ruby ./protos/$@ && protoc --proto_path=./protos --php_out=./php --grpc_out=./php ./protos/$@ --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin
    }

. ~/.bashrc
cd ~/projects/grpc_trial/
protoc2 checkexpires.proto

これで、ひとつのprotoファイルでrubyとPHPそれぞれの共通I/Fが準備できました。
あとはクライアント側とサーバ側にそれぞれ固有な処理を書いていけばいい感じですね。
頑張れそうです。

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

devise使わずユーザー編集・更新機能を実装する

はじめに

今回はユーザーのマイページを実装してから、プロフィールを編集できるようにするための方法を解説します。

前提条件

・deviseを導入済み
・deviseによるゆーざー新規登録/ログイン機能を実装済み

開発環境

Ruby 2.6.5
Rails 6.0.0
MySQL 5.6.50

手順

1)ユーザー詳細(マイページの実装)

まずdeviseのものとは別にusersコントローラーを作成します。
ターミナルにて以下コマンドを実行します。

ターミナル
$ rails g controller users show

コントローラーが作成できたら、以下のようにファイルを編集ます。

controllers/users_controller
class UsersController < ApplicationController
  before_action :set_user, only: [:show]

  def show; end

  private

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

end

次にルーティングも設定します

config/routes.rb
# 既存の記述に追記する
resources :users, only: [:show] 

これでマイページへのパスとアクションの設定は終わりました。
次にビューの記述をしていきます。

views/users/show.html.erbにて記述していきます。
マイページの実装は以上です。

2)ユーザー編集機能の実装

ここからは、あくまでも自分の場合なので、参考までにとどめてください。

まずDB設計(マイグレーションファイル)を以下のようにしました。

deviseのusesテーブルはnicknameカラムのみ作成します。

devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :nickname, null: false
      t.string :email,              null: false, default: ''
      t.string :encrypted_password, null: false, default: ''
      # 省略
end

続いてプロフィール用のテーブルの設計をしていきます。
ターミナルにて以下のコマンドを実行します
(僕の場合はプロフィール用にintroモデルを作成)

ターミナル
$ rails g model intro

マイグレーションファイルを編集します。

create_intros.rb
class CreateIntros < ActiveRecord::Migration[6.0]
  def change
    create_table :intros do |t|
      t.string :image
      t.string :first_name
      t.string :last_name
      t.string :website
      t.text   :profile
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end

以上でテーブルの設計は終了です。

2-1)モデルのアソシエーションの設定

次にアソシエーションですが、以下のような設定にしました。

models/user.rb
has_one :intro
models/intro.rb
belongs_to :user

このように1対1の関係を結びました。

2-2)ルーティングとアクションの設定

config/routes.rb
resources :users, only: [:show] 
resources :intros, only: [:new, :create, :edit, :update]

ルーティングは上記のようにし、ネストはしませんでした。
理由は、ユーザー編集用のテーブルはイメージとしてはツイートなどの投稿用のテーブルと近いような気がしたからです。

アクションもそうですが、イメージとしては投稿機能を、ユーザー編集に置き換えるとイメージを持つと実装しやすいかと思います。

次にアクションの定義を行います。

controllers/intros_controller.rb
class IntrosController < ApplicationController
 # 未ログインユーザーの処理
  before_action :authenticate_user!, only: [:new, :edit]
  # リファクタリング
  before_action :set_intro, only: [:edit, :update]

  def new
    @intro = Intro.new
   # すでに登録済みのユーザーが新規登録ページに遷移しないようにする
  if Intro.find_by(user_id: current_user.id)
      redirect_to root_path
    end
  end

  def create
    @intro = Intro.new(intro_params)
   # 保存できたら、ユーザー詳細ページに戻るため引数にintroモデルに紐づくuserのidをわたす
    # intoro.valid?でないのはバリデーションをかけていないため
    if @intro.save
      redirect_to user_path(@intro.user.id)
    else
      render :new
    end
  end

  def edit
    # ログイン中のユーザーと編集するユーザーが一致しないとページに遷移できないようにする
    unless current_user.id == @intro.user.id
      redirect_to user_path(@intro.user.id)
    end
  end

  def update
   # createと同じように更新できたらユーザー詳細に戻るため引数をわたす
    if @intro.update(intro_params)
      redirect_to user_path(@intro.user.id)
    else
      render :edit
    end
  end

  private

  def set_intro
    @intro = Intro.find(params[:id])
  end

  def intro_params
    params.require(:intro).permit(:first_name, :last_name, :website, :profile, :image).merge(user_id: current_user.id)
  end
end

これでユーザー編集・更新機能の実装はほとんど完了です。
最後にビューを作成します。

また、この時ユーザー編集ページへ遷移するリンクに条件を指定します。

2-3)views/users/show.html.erbの編集

users/show.html.erb
<% if user_signed_in? %>
# introテーブルにUserの値が存在しなれば新規登録のリンクを表示
  <% unless @user.intro.present? %>
# ログインしているユーザーとマイページに表示されているユーザーが同じなら新規登録のリンクを表示
    <% if current_user.id == @user.id %>
       <div class="profile-btn">
         <%= link_to 'プロフィールを編集する', new_intro_path, class: "profile-edit-btn" %>
       </div>
    <% end %>
  <% end %>

# ログインしているユーザーとマイページに表示されているユーザーが同じで、かつintroテーブルに値が存在していれば編集ページへのリンクを表示する
  <% if current_user.id == @user.id && @user.intro.present? %>
    <div class="profile-btn">
      <%= link_to 'プロフィールを編集する', edit_intro_path(@user.intro.id), class: "profile-edit-btn" %>
    </div>
  <% end %>
<% end %>   

僕の場合はこのような記述しかできませんでしたが、もっと良い記述があればコメントくださると嬉しいです。

僕はここにたどり着くまでに1日半かかったので、この記事を見て少しでも作業が進んだという方がいらっしゃれば幸いです。

参考文献

【Rails】データが1件でもあるかどうかチェックするにはModel.exists?

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

【Ruby on Rails】「||=」←この代入演算子の使い方まとめ

代入演算子

代入演算子とは、変数に対して何か値を代入する為の演算子です。
以下記事にわかりやすく解説してあるので、わからない方は読んでみてください。

https://wa3.i-3-i.info/word18049.html

代入演算子には以下のようなさまざまな種類が存在します。

記号 意味
= 代入
+= 加算して代入
-= 減算して代入
*= 乗算して代入
/= 除算して代入
%= 乗余して代入
**= 累乗して代入

「||=」(or equal)の代入演算子

代入演算子の中に、「||=」このような代入演算子も存在します。

「=」と「||」が合体した演算子です。

「=」は代入を意味します。上でも記載したが代入演算子の一種です。

a = b
a に b を代入します。

続いて、「||」はどちらかの条件が成立すれば、trueを返すという演算子です。(ORを意味します。)
これは、論理演算子と呼ばれています。
if文と一緒に使うと以下のようになります。

if a || b #a または b がtrueでtrueであればtrueを返す。
 #a か b がtrueの場合
else
 #両方falseの場合
end

この2つの演算子が合体するとどのような挙動になるのか。

a ||= b
a がfalseもしくは未定義ならbを代入します。
また a = (a || b) と同じ意味に当たります。

というような挙動になります。
実際の例文を以下に記載します。

  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end

変数@current_userが存在する場合、もともと存在する@current_userを返します。

変数@current_userが存在しない場合、User.find_by(id: session[:user_id])でユーザーを見つけ、そのまま@current_userという変数に代入します。

上記コードは、以下のように書き換えることも可能です。

  def current_user
    @current_user = @current_user || User.find_by(id: session[:user_id])
  end

参考文献

Rails tutorial 第8章
https://railstutorial.jp/chapters/log_in_log_out?version=4.2#cha-log_in_log_out

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

deviseのlockableを使うときに意識しないといけないこと

つまり

config.unlock_strategy:time:noneにするときはよく考えて

バージョン

  • Rails 6.1.0
  • devise 4.7.3

lockableについて

deviseのlockableは、ログインに何回か失敗したらアカウントロックを行う、というもの。
注意すべきは、「ログインがロックされる」のではなく、「アカウントがロックされる」のだということ。
これは、アカウントロック中にアクセスしたログイン中セッションは、全て強制ログアウトされる動作になる。

これを利用して、次のようなイタズラをされると、結構困る。

  1. ユーザAがdeviseを使ったwebサービスにログインしている
  2. 他人がユーザAのメールアドレスでログインしようとするが連続で失敗して、ユーザAのアカウントロックされる
  3. ユーザAがアカウントロック中にwebサービスにアクセスする
  4. ログアウトされて、ユーザAはロック解除されるまでwebサービスが利用できなくなる

回避方法

同回避するか、3つほど考えた。

メールアドレスでアンロックできるようにする

deviseの設定にunlock_strategyという物があり、これには:email:time, :both, :noneが指定できる。

  • :email
    • アンロックするリンクをメールで送信する
  • :time
    • 指定時間が経過するまでロックされる
  • :both
    • :emailでアンロックするか、:timeで自然にアンロックされるか両方
    • デフォルトはこれなので、比較的安心か。
  • :none
    • アンロック方法は無い。アンロックにはDB直接いじるしかなさそう?

このうち、:email:bothの場合はロックされたアカウントの所有者が自分でロックを解除できるので、アカウントを使用できなくことに関する被害は少ない。
けど、:timeの場合は、イタズラでロックされたユーザがいた場合に、時間経過で解除されるのを待たないといけない(config.unlock_inのデフォルトは1時間)ので、困ることも発生する。

ログインだけロックする

「アカウントロック」ではなく、「ログインをロック」にする場合。
難易度が非常に高いからおすすめしないのだけれど、active_for_authentication?をオーバーライドする方法がある。
めっちゃ単純なアプリでlockableのみを使っている場合は、次のようにしても良いかもしれない。

  def active_for_authentication?
    true
  end

でも、confirmableとかも使っている場合は? 他にも色々なアカウントのアクティベーションに関する仕様がある場合は?
安易にactive_for_authentication?trueにしないようにしましょう、余計なセキュリティリスクが増えます。

deviseを使わない

例えば、deviseを避ける

終わりに

セキュリティに関する話なので、間違っているとまずい・・。
厳しいご指摘お待ちしております。

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

「bundle exec rubocop -a」を使って失敗したお話

■状況
staffマイグレーションの簡単な修正を行う
コンフリクトが起きる
ローカルのmainブランチを最新にする
git merge メインブランチを行い、コンフリクトを解消する
「bundle exec rubocop」でいくつかエラーが出たため、「bundle exec rubocop -a」を実行
「bundle exec rubocop」を行うが、以下のエラーだけ解消されない

コンソール
# bundle exec rubocop

app/models/user.rb:47:3: C: Rails/UniqueValidationWithoutIndex: Uniqueness validation should be with a unique index.
  validates :email, presence: true, length: { maximum: 70 }, ...

しかし、このファイルは今回自分は触っていないため、原因が分からず。

結果、以下の方法で検証・解決しました。

・念のためメインブランチをテスト実行→エラー0件
・そのため、「bundle exec rubocop -a」が何かしているのではないかと予測
・確認した結果、rubocopの警告を無視する記述(コメント)が自動的に削除されている事が判明

削除された記述を戻し、エラーは0件となりました!

rubocopの警告を無視する記述に関しては
以下が参考になるかと思います。(古いかも?)

https://qiita.com/tbpgr/items/a9000c5c6fa92a46c206

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

devise gem気づきメモ: リダイレクトの仕様

アプリ開発で、トップページを作成する前にdeviseを用いてユーザー管理機能を実装していたところ、分かっているようで分かっていなかったdeviseのリダイレクトの仕様に気づいたので記録として残します。

deviseの仕様に気づいたきっかけ

トップページを作成する前に、deviseで新規登録機能を実装し、ローカル環境(localhost:3000)で動作確認をしたところ、新規登録後にRailsのお馴染みのトップページにリダイレクトしたことです。
スクリーンショット 2021-01-07 21.47.12.png

新規登録の処理後はルートパスにリダイレクトする

新規登録機能(コントローラ:devise/registration#create)の処理後は、ルートパス(root_path)にリダイレクトされます。

route.rbでルートパス(root_path)を設定していなかった場合、先程のRailsのお馴染みのトップページが表示されることになります。

ログアウトの処理後はログイン画面にリダイレクトする

ログアウト機能(コントローラ:devise/sessions#destroy)の処理後は、ログイン画面(コントローラ:devise/sessions#new)にリダイレクトされます。

先述の新規登録→ルートパスへのリダイレクトに気づいて、「じゃあ、ログアウト後はどこにリダイレクトされるんだろう?」と思って確認したところ、ログイン画面にリダイレクトされることが分かりました。

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

Exponential Backkoff ってなんじゃらホイ?を Enumerator クラスで書いてみた

0. はじめに

今回の記事は Ruby 2.4.1 で動作確認しています ?

1. Exponential Backoff And Jitter?

サーバーリクエストなんかのリトライ処理ってありますよね?

よくあるやっつけ実装が、以下みたいにリトライの最大回数と、待ち時間を定数で設定しておいて。。ってやつです

1.upto(MAX_ATTEMPT) do |attempt|
  break if request # リクエストが成功したら終了
  sleep RETRY_WAIT
end

でその定数ってのが、実装のときに適当に決めた、

  • 最大回数 = 5(回)
  • 待ち時間 = 5(秒)

とかを使っててそのまま。。ってのは意外とよくあるんじゃないかと思います。

この根拠のないリトライの最大回数と待ち時間について、もうちょっとよい決め方はないのかな〜って検索してみて見つけたのが、以下の記事です。

参考: AWS Solutions Architect ブログ: Exponential Backoff And Jitter

Exponential Backoff And Jitter というテクニックが紹介されています。

言葉の意味は、

  • Exponential(指数的)
  • Backoff(遅延)
  • Jitter(ゆらぎ)

ってことらしいです :eyes:

2. Enumerator を返そう?

それとは別に、こちらの記事で紹介されているテクニックも、どこかで使えないかな〜っと前から気になってました。

参考: Ruby: EnumerableをincludeするよりEnumeratorを返そう(翻訳)

記事の趣旨としては、「そのクラスがコレクションなら include Enumerable すればいいけど、そうじゃないならメソッドで Enumerator オブジェクトを返すようにしとけばいいよ」ってことみたいです。

Exponential BackoffEnumerator

...そう、点と点がつながりましたね ?

両者を組み合わせてコードにしてみました。

3. Exponential Backoff

※ ここから先、引用されている計算式の引用元は、すべて 先程のブログ記事 です

sleep = min(cap, base * 2 ** attempt)

まずはゆらぎ(Jitter)のない Exponential Backoff パターンを書いてみました。

こんな感じです。

module ExponentialBackoff
  def self.call(max_attempt: Float::INFINITY, capacity: Float::INFINITY, base: 1)
    Enumerator.new do |yielder|
      1.upto(max_attempt) do |attempt|
        yielder << [capacity, base * 2 ** attempt].min
      end
    end
  end
end

使い方はこんな感じです。

irb(main)> eb = ExponentialBackoff.call
=> #<Enumerator: #<Enumerator::Generator:0x007fefe68f5b28>:each>
irb(main)> eb.next
=> 2
irb(main)> eb.next
=> 4
irb(main)> eb.next
=> 8

.call を呼び出すことによって、Exponential Backoff な数値を列挙する Enumerator オブジェクトが得られます。

また、.call には3つのキーワード引数が指定できます。

irb(main)> ExponentialBackoff.call.take(10) # 未指定の場合
=> [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
irb(main)> ExponentialBackoff.call(max_attempt: 5).take(10) # 最大回数
=> [2, 4, 8, 16, 32]
irb(main)> ExponentialBackoff.call(capacity: 100).take(10) # 最大値
=> [2, 4, 8, 16, 32, 64, 100, 100, 100, 100]
irb(main)> ExponentialBackoff.call(base: 10).take(10) # 倍率
=> [20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240]

この ExponentialBackoff モジュールを利用して冒頭のリトライ処理を改善するとこのようになります。

ExponentialBackoff.call(max_attempt: MAX_ATTEMPT).each do |eb|
  break if request # リクエストが成功したら終了
  sleep eb
end

ピタッと収まりました! :smile:

4. Exponential Backoff And Full Jitter

sleep = random_between(0 min(cap, base * 2 ** attempt))

次は、ゆらぎ(Jitter)を入れていきます。といっても、コードに大きな違いはないですね。

こんな感じです。

module ExponentialBackoffAndFullJitter
  def self.call(max_attempt: Float::INFINITY, capacity: Float::INFINITY, base: 1)
    Enumerator.new do |yielder|
      1.upto(max_attempt) do |attempt|
        yielder << [capacity, rand(0..(base * 2 ** attempt))].min
      end
    end
  end
end

得られる数値はこんな感じです。

irb(main)> ExponentialBackoffAndFullJitter.call.take(10)
=> [2, 4, 0, 2, 19, 26, 25, 107, 476, 513]

0 を許容したくなければ、倍率(base)の指定が必要ですね :thinking:

irb(main)> ExponentialBackoffAndFullJitter.call(base: 100).take(10).map { |n| n.fdiv(100) }
=> [0.29, 3.35, 0.76, 5.63, 31.74, 28.19, 92.0, 17.62, 408.15, 567.1]

5. Exponential Backoff And Equal Jitter

temp = min(cap, base * 2 ** attempt)
sleep = temp / 2 + random_between(0, temp / 2)

これも、計算部分が異なるだけなので、あまり大きな違いはありません。

こんな感じです。

module ExponentialBackoffAndEqualJitter
  def self.call(max_attempt: Float::INFINITY, capacity: Float::INFINITY, base: 1)
    Enumerator.new do |yielder|
      1.upto(max_attempt) do |attempt|
        temp = [capacity, base * 2 ** attempt].min
        yielder << temp / 2 + rand(0..(temp / 2))
      end
    end
  end
end

得られる数値はこんな感じです。

irb(main)> ExponentialBackoffAndEqualJitter.call.take(10)
=> [1, 2, 7, 14, 21, 50, 117, 179, 438, 957]

計算に除算が含まれていますが、#to_f#fdiv を使っていないので小数は切り捨てです :rolling_eyes:

6. Exponential Backoff And Decorrlated Jitter

sleep = min(cap, random_between(base, sleep * 3))

今度は、計算中に「前回の計算値」が必要になってきます。

こんな感じです。

module ExponentialBackoffAndDecorrlatedJitter
  def self.call(max_attempt: Float::INFINITY, capacity: Float::INFINITY, base: 1)
    Enumerator.new do |yielder|
      previous_value = 1
      1.upto(max_attempt) do |attempt|
        temp = [capacity, rand(base..(previous_value * 3))].min
        yielder << temp
        previous_value = temp
      end
    end
  end
end

得られる数値はこんな感じです。

irb(main)> ExponentialBackoffAndDecorrlatedJitter.call.take(10)
=> [1, 2, 2, 6, 18, 37, 24, 34, 51, 34]

数値の増えたり減ったりが激しめですね :eyes:

7. まとめ

今回の挑戦を通じて、Ruby の Enumerator クラスの理解が深まりました :smile: とても有用なクラスだと思いますので、積極的に使っていきたいと思います。

リトライ処理については、今後は少なくとも「5秒ごとに最大5回」よりはちょっとはマシなリトライ処理を、最初から実装しておくことができそうです。

もちろん、どの遅延パターンが最も効果的かについては、ケースごとに効果測定する必要がありますが、Jitter を入れるだけでも、リトライのタイミングを分散させて、サーバーの負荷を減らす効果があるはずです :wink: tabun

みなさんもぜひ使ってみてください :heart:

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

プログラミングとは?

そもそもプログラミングとは何か?

コンピューターが実行する処理の手順をまとめたデータをプログラムと呼ぶ

プログラミング言語とは?
普段私たちが話している言葉はコンピューターは理解できない。
コンピューターがわかる言葉をプログラミング言語という。
プログラミング言語で書かれたテキストやファイルをソースコードと呼ぶ

プログラミング言語の種類

Ruby
java
php
Python

などがある。

プログラムを実行するにはターミナルにコマンドを打ち込んで実行する。

ここからプログラミング言語Rubyについて記述していきます。

irb

irbコマンドはターミナルから直接Rubyのプログラムを動かすことのできる機能(Interactive Ruby の略)
エディタでコードを記述してターミナルを実行するのではなく直接ターミナルで実行できる。

ターミナル画面

#irbを起動
% irb
irb(main):001:0> "Welcome! Ruby" #このように打ち込む
=>"Welcome! Ruby" #このように出力

今日は以上です。

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

railsでメール送信機能実装

はじめに

現場で使える Ruby on Rails 5速習実践ガイドでインプットを行っており、アウトプットで投稿しています。
サンプルは、タスク管理アプリです。今回の投稿では、ユーザーがタスクを登録したときに、「○○タスクが登録されました」というメールを送る機能を実装します。

目次

  1. メイラーの作成
  2. メソッドを追加
  3. テンプレート作成
  4. 送信処理実装
  5. 動作確認
  6. テスト作成
  7. 参考文献

メイラーの作成

メイラーとは、ActionMailerのことで、railsに搭載されているメールを送る機能です。controllerが、templateに情報を渡し画面出力を行っているのと同様に、Mailerもtemplateに情報を渡しメールを送信します。
まずは、今回実装するTaskMailerを作成します。以下のコマンドを実行します。

rails g mailer TaskMailer

メソッドを追加

先程のコマンドでapp/mailer/task_mailer.rbというファイルが作成されます。そこに、今回送信するメールのメソッド「creation_email」を定義します。

app/mailer/task_mailer.rb
def creation_email(task)
  @task = task
  mail(
    subject: 'タスク作成完了メール',
    to: 'user@example.com',
    from: 'task@example.com'
  )
end

こちらのメソッドを呼び出すときに、追加されたタスクを引数として渡してもらいます。taskの内容をtemplateで表示するのでインスタンス変数に格納しています。

テンプレート作成

次にテンプレートを作成します。ユーザーの受信環境によっては、html形式のメールを表示できない可能性があります。text形式のファイルを合わせて作成し、html形式と合わせて送信しますのでファイルを二種類作成します。

app/views/task_mailer/creation_email.html.slim
| 以下のタスクを作成しました

ul
  li
    | 名称:
    = @task.name
  li
    | 詳しい説明
    = simple_format(@task.description)

app/views/task_mailer/creation_email.text.slim
| 以下のタスクを作成しました
= "\n"
| 名称:
= @task.name
= "\n"
| 詳しい説明
= "\n"
= @task.description

送信処理実装

ここまで来たらあとは、送信する処理を記述するのみです!
今回は、タスクの保存処理と合わせてメールを送信するため、tasks_controllerのcreateメソッドに送信処理を書きます。

app/controllers/tasks_controller.rb
  
    if @task.save
      TaskMailer.creation_email(@task).deliver_now
      SampleJob.perform_later
      logger.debug "task:「#{@task.attributes.inspect}」を登録しました"
      redirect_to tasks_url, notice: "タスク「#{@task.name}」を登録しました。"
    else
      render :new
    end
  

deliver_nowは文字通り、即時に送信する命令です。5分後にメールを送りたいときは、deliver_later(wait: 5minutes)とします。

動作確認

メールが送信されたか、メールの内容は意図したとおりになっているか。これを確かめるために、「mailcatcher」というgemを使用します。

gem install mailcatcher

上記のコマンドを実行し、railsの設定ファイルに以下の記述を行います。

config/environments/development.rb
  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false
  # 以下二行を追記
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = { address: '127.0.0.1', port: 1025}

上記は開発環境の設定ですが、本番環境の設定は、procuction.rbに記述をします。

設定ファイルの記述が完了したら、サーバーを再起動してください。
確認の手順は、

  • ターミナルでmailcatcherを実行
  • メール送信のフローを実行後、http://127.0.0.1:1080/にアクセス

そうすると、送信メールの確認ができます。

テスト作成

まずは、mailerのSpec用にディレクトリを作成します。

mkdir spec/mailers

作成したフォルダの中にtask_mailer_spec.rbを作成し、以下のように記述します。

spec/mailers/task_mailer_spec.rb
require 'rails_helper'
describe TaskMailer, type: :mailer do
end

これで枠組みは完成なので、実際にテストを書いていきます。

spec/mailers/task_mailer_spec.rb
require "rails_helper"

RSpec.describe TaskMailer, type: :mailer do
  let(:task){FactoryBot.create(:task, name: 'メイラーSpecを書く', description: '送信したメールの内容を確認します')}
  let(:text_body) do
    part = mail.body.parts.detect{|part| part.content_type == 'text/plain; charset=UTF-8'}
    part.body.raw_source
  end
  let(:html_body) do
    part = mail.body.parts.detect{|part| part.content_type == 'text/html; charset=UTF-8'}
    part.body.raw_source
  end

  describe '#creation_email' do
    let(:mail){TaskMailer.creation_email(task)}

    it "想定通りのメールが生成されている" do
      expect(mail.subject).to eq('タスク作成完了メール')
      expect(mail.to).to eq(['user@example.com'])
      expect(mail.from).to eq(['taskleaf@example.com'])

      expect(text_body).to match('以下のタスクを作成しました')
      expect(text_body).to match('メイラーSpecを書く')
      expect(text_body).to match('送信したメールの内容を確認します')

      expect(html_body).to match('以下のタスクを作成しました')
      expect(html_body).to match('メイラーSpecを書く')
      expect(html_body).to match('送信したメールの内容を確認します')
    end
  end
end

以下のコマンドを実行し通るか確認します。

bundle exec rspec spec/mailers/task_mailer_spec.rb

参考文献

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

【Rails】deviseを使ったサインアップ機能 wrong number of arguments (given 0, expected 1)というエラー

エラー詳細

deviseを導入して、サインアップを実行したところ、このような内容のエラーが吐かれた。

ArgumentError (wrong number of arguments (given 0, expected 1)):
wrong number of arguments (given 0, expected 1)

解決法

user.rb
has_secure_password

を消す。
has_secure_passwordとは、パスワードをDBに保存する時に、暗号化して保存してくれるrailsの機能のこと。deviseを使う場合は不要なので、コメントアウトしておきましょう。

参考にした記事

https://qiita.com/kents1002/items/4079e3d05d322febe00e
https://qiita.com/shumpeism/items/4d8946ade2dbdccab31c

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

WSL2+Ubuntu20.04へのRubyインストール

個人的なメモ

必要なライブラリのインストール

sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install -y autoconf bison build-essential libssl-dev libyaml-dev libreadline-dev zlib1g zlib1g-dev sqlite3 libsqlite3-dev nodejs npm imagemagick git

yarnのインストール

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn -y

Rubyをインストール

git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
echo '[ -f "$HOME/.profile" ] && source "$HOME/.profile"' >> ~/.bash_profile
echo '[ -f "$HOME/.bashrc" ] && source "$HOME/.bashrc"' >> ~/.bash_profile
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile
~/.rbenv/bin/rbenv init
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source ~/.bash_profile

確認

rbenv -v

ruby-build をインストール

mkdir -p "$(rbenv root)"/plugins
git clone https://github.com/sstephenson/ruby-build.git "$(rbenv root)"/plugins/ruby-build

rubyをインストール

rbenv install -l
rbenv install 2.6.6

インストールしたrubyの確認

rbenv global 2.6.6
ruby -v

ドライブのマウント設定

sudo tee /etc/wsl.conf <<EOF >/dev/null
[automount]
options = "metadata"
EOF
echo 'export PATH="$(echo "$PATH" | sed -r -e '"'"'s;:/mnt/[^:]+;;g'"'"')"' >> ~/.bash_profile
source ~/.bash_profile

設定を有効にする為にWindowsを再起動する

参考

https://www.oiax.jp/books/insecure_world_writable_dir.html

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

Rails on Tiles(どう書く)

http://nabetani.sakura.ne.jp/hena/ord5railsontiles/

与えられたタイルのレールに沿って移動するとき、範囲外にはみ出てしまうまでのルートを求める問題です。

Ruby
module RailsOnTiles
  Tiles = [[2, 3, 0, 1], [1, 0, 3, 2], [3, 2, 1, 0]]
  Dirs = [[0, -1], [1, 0], [0, 1], [-1, 0]]
  module_function

  def go(input)
    field = input.chars.map(&:to_i).each_slice(3).to_a
    solve(field).map { |i| ("A".."I").to_a[i] }.join
  end

  def solve(field, x=1, y=-1, dir=2, route=[])
    dx, dy = Dirs[dir]
    x += dx
    y += dy
    return route if x < 0 || x > 2 || y < 0 || y > 2
    rev = Tiles[0][dir]
    nxt = Tiles[field[y][x]][rev]
    tile_num = y * 3 + x
    solve(field, x, y, nxt, route + [tile_num])
  end
end


if __FILE__ == $0
  require 'minitest/autorun'
  describe 'RailsOnTiles' do
    [
      ["101221102", "BEDGHIFEH"],
      ["000000000", "BEH"],
      ["111111111", "BCF"],
      ["222222222", "BAD"],
      ["000211112", "BEFIHEDGH"],
      ["221011102", "BADGHIFEBCF"],
      ["201100112", "BEHIFCBADEF"],
      ["000111222", "BEFIH"],
      ["012012012", "BC"],
      ["201120111", "BEDABCFI"],
      ["220111122", "BADEHGD"],
      ["221011022", "BADG"],
      ["111000112", "BCFIHEBA"],
      ["001211001", "BEFI"],
      ["111222012", "BCFEHIF"],
      ["220111211", "BADEHI"],
      ["211212212", "BCFEBAD"],
      ["002112210", "BEFC"],
      ["001010221", "BEF"],
      ["100211002", "BEFIHG"],
      ["201212121", "BEFCBAD"]
    ].each do |input, expect|
      it input do
        assert_equal RailsOnTiles.go(input), expect 
      end
    end
  end
end

fieldはタイルの配置を表していて、タイルの種類はTilesに格納してあります。方向の種類は、0,1,2,3 がそれぞれ上右下左を表します。前のタイルから下へ行けば、次のタイルでは上から来るという具合に「反転」するので、それがrevに入ります。あとは、fieldを外れるまで再帰し、結果を返します。

なお、道すじrouteはタイルの位置を数字で表しているので、最後にアルファベットに変換しています。

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

【初心者向け】Ruby on Rails チュートリアル第4版(Rails5.1)の環境構築を1時間以内で!

内容

言わずと知れたRuby on Railsチュートリアル、素晴らしい教材ですよね。全くの門外漢だった自分を駆け出しエンジニアのレベルまで引き上げてくれた神チュートリアルだと思っており、エンジニアとして就職した今も頻繁に見返す最高の教科書です。

Railsチュートリアルをはじめるにあたって最初に大きな壁として立ちはだかるのが環境構築です。私自身も約3ヶ月前に異業種からの転職でRubyエンジニアとして働きはじめたばかりなので、環境構築などの「プログラミング学習を始める以前の準備」に時間を取られた時のことは記憶に新しいです。

本記事では、一人でも多くのプログラミング初学者の方にRailsチュートリアルの魅力を感じて欲しいという思いから、無料で公開されているRailsチュートリアル第4版のための、すなわち「Ruby on Rails 5.1.6 開発環境を1時間以内に手に入れる」ための手順を紹介します。

※ Railsチュートリアルが推奨しているAWS Could9によるものではなく Virtual Box + Vagrant による環境構築となります

※ 本記事の姉妹記事はこちら
【環境構築】Ruby on Rails 6 開発環境を1時間以内に手に入れる

ゴール

Ruby on Rails 5.1.6 の開発環境を備えたUbuntuの仮想環境をmacOS上に構築する(目標1時間以内)。

前提環境

macOSCatalina バージョン 10.15.7

※ Windowsの方はゴメンナサイ。また、macOSの方でもバージョンの差異で多少の違いが発生する可能性はあります。

Virtual Box 6.1.16

※ インストール手順は後述

Vagrant 2.2.14

※ インストール手順は後述

想定する読者

・macOSユーザーの方
・macOSのターミナルを使った経験があり、基本的なLinuxコマンド(cd, mkdir, lsなど)の意味を知っている方
・ProgateなどでRuby on Railsの概要を学んだことがある方
・AWS Could9などのクラウドベースの統合開発環境に限界を感じている方
・過去にRuby on Railsの環境構築に挫折した経験のある方

仮想環境とは?

使っているOS(ホストOS:本記事ではmacOSを想定)の中に、あたかも別のOS(ゲストOS:本記事ではUbuntuを使用)が入っているような環境のことを言います。

ホストOS上に開発環境を直接構築する場合、誤った設定や削除を行ってしまったことによりホストOSに悪影響を与えてしまう可能性はありますが、仮想環境上で環境構築をする場合であれば、何かミスをしてしまった時はその仮想環境ごと削除してやり直せば良く、ホストOSに悪影響を与えることはありません。

また、AWS Could9などのクラウドベースの統合開発環境よりもリソース拡張の自由度が高く、CPUの性能限界やメモリ不足に悩まされることは(少なくともRailsでWebアプリケーションを開発するだけであれば)ほとんどないと言っていいと思います。

Virtual Box

仮想環境を構築するための「仮想化ソフト」として、まずはVirtualBoxをインストールします。

下記のダウンロードページから、(本記事ではmacOSを想定しているので)「OS X hosts」のリンクをクリックしてインストーラをダウンロードしてください。

ダウンロードしたインストーラを起動すれば、インストール手順がわかりやすく書いてあるので、それに従えばVirtual Boxのインストールは終了となります。

(※ 2021年1月7日現在の最新版は6.1.16なので、ここからはそのバージョンでの動作を前提としております)

Virtual Box ダウンロードページ

Vagrant

次に、仮想化ソフトを管理するツールであるVagrantをインストールします。
下記ダウンロードページにアクセスし、上記のVirtualBoxと同様に、インストーラのダウンロード、インストーラ起動、インストールという手順を踏めばほとんど迷うことなく完了すると思います。

仮想化ソフトである「VirtualBox」を操作するためのツールが「Vagrant」である、という認識を持っていただければとりあえず最低限の知識としてはOKです。

(※ 2021年1月7日現在の最新版は2.2.14なので、ここからはそのバージョンでの動作を前提としております)

Vagrant ダウンロードページ

環境構築手順

Virtual BoxとVagrantを問題なくインストールしたら、ここからは実際に仮想環境を構築して行きたいと思います。今回は、Ubuntu(18.04)というゲストOSが入っている仮想環境を構築します。

ここからの操作はmacOSのターミナルで行います。作業に入る前にvagrantが正しくインストールされていることを確認しましょう。ターミナルを開いてvagrant -vと入力してみてください。Vagrant 2.2.14という出力が返ってくれば準備はOKです、早速はじめていきましょう!

1. vagrant-vbguestのインストール

$ vagrant plugin install vagrant-vbguest

仮想マシンでの操作を簡単にしてくれる役割がある、という理解でとりあえずはOKです。

2. 任意の場所にディレクトリを作成

$ mkdir rails516_dev
$ cd rails516_dev

3. Vagrantfile作成

$ vagrant init

上記コマンドでデフォルトのVagrantfileが作成されます。

Vagrantfile(デフォルト|一部抜粋)
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  ~
  ~
  config.vm.box = "base"
  ~
  ~
  # config.vm.network "forwarded_port", guest: 80, host: 8080
  ~
  ~
  # config.vm.network "private_network", ip: "192.168.33.10"
  ~
  ~
end

上記のデフォルト状態のVagrantfileの中身を、下記のコードに書き換えてください。

Vagrantfile(書き換え後)
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  GUEST_RUBY_VERSION = '2.6.6'
  GUEST_RAILS_VERSION = '5.1.6'
  config.vm.box = "bento/ubuntu-18.04"
  config.vm.box_check_update = false
  config.vm.network "forwarded_port", guest: 3000, host: 3000
  config.vm.network "private_network", ip: "192.168.33.10"
  config.vm.synced_folder "./", "/home/vagrant/work"
  config.ssh.forward_agent = true
  config.vm.provider "virtualbox" do |vb|
      vb.gui = false
  end
  config.vm.provision "shell", inline: <<-SHELL
      echo '### installing tools ###'
      sudo timedatectl set-timezone Asia/Tokyo
      sudo apt update -y
      sudo apt upgrade -y
      sudo apt install build-essential -y
      sudo apt install -y libssl-dev libreadline-dev zlib1g-dev
      sudo apt install -y imagemagick
  SHELL
  config.vm.provision "shell", privileged: false, inline: <<-SHELL
      echo '### installing Ruby ###'
      git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
      echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
      echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
      source ~/.bash_profile
      git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
      rbenv install #{GUEST_RUBY_VERSION}
      rbenv global #{GUEST_RUBY_VERSION}
      echo '### installing Rails ###'
      gem install rails -v #{GUEST_RAILS_VERSION}
      echo '### installing SQLITE3 ###'
      sudo apt install libsqlite3-dev
      sudo apt install sqlite3
      echo '### installing NodeJS ###'
      sudo apt install -y nodejs npm
      sudo npm install n -g
      sudo n lts
      sudo apt purge -y nodejs npm
      sudo apt -y autoremove
      echo '### increasing inotify ###'
      sudo sh -c "echo fs.inotify.max_user_watches=524288 >> /etc/sysctl.conf"
      sudo sysctl -p
      echo ' -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-'
      echo 'You are now on Rails!'
      echo ' -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-'
  SHELL
end

本記事での解説は避けますが、このVagrantfileにはRailsを使えるようにするための設定が書いてあります。最初は難解に感じるかもしれませんが、ぜひ一度、読み解くことをオススメします。

もちろん私が書いたこのVagrantfileが唯一の正解では決してないので、「もっといい書き方があるよ」「こっちの方が使いやすいと思う」っていうご意見も大歓迎です!

4. 仮想環境構築

$ vagrant up

上記コマンドで仮想環境を構築します(PCのスペックにもよりますが、15〜30分ほどかかります)。

先ほどのVagrantfileに書かれた設定を1行ずつ実行していくことでRailsの開発環境が作られていっているということだけは抑えて頂けるといいかなと思います。

5. 仮想環境へSSH接続

$ vagrant ssh

上記コマンドで出来上がった仮想環境にSSH接続します。

SSH接続とは、ネットワークを経由して自身のPCから他のPC(今回は仮想環境)を安全に遠隔操作するための仕組みである、ということだけは抑えておいてください。

ここまでがmacOSのターミナルでの操作です。次の「6. Railsアプリケーション作成」と「8. ssh接続の終了」ではmacOSからssh接続をして、ゲストOSのUbuntuを操作しているということをご認識ください。

6. Railsアプリケーション作成

vagrant@vagrant:~$ cd work
vagrant@vagrant:~/work$ rails new sample_app
vagrant@vagrant:~/work$ cd sample_app

(補足) Railsアプリケーションの開発を進める方法

「2. 任意の場所にフォルダを作成」で作成したディレクトリ内に「6. Railsアプリケーション作成」で作成したRailsアプリと同名のファイルが作成されているはずなので、そこのコードを書き換えることで開発を進めます。

rails g controller Usersbundle install 等のコマンドはSSH接続をした状態で、作成したRailsアプリケーションのディレクトリ上で叩くことになります。

7. Gemfile編集

Gemfileを下記に書き換え(Railsチュートリアル第3章の内容)

Gemfile
source 'https://rubygems.org'

gem 'rails',        '5.1.6'
gem 'puma',         '3.9.1'
gem 'sass-rails',   '5.0.6'
gem 'uglifier',     '3.2.0'
gem 'coffee-rails', '4.2.2'
gem 'jquery-rails', '4.3.1'
gem 'turbolinks',   '5.0.1'
gem 'jbuilder',     '2.7.0'

group :development, :test do
  gem 'sqlite3', '1.3.13'
  gem 'byebug',  '9.0.6', platform: :mri
end

group :development do
  gem 'web-console',           '3.5.1'
  gem 'listen',                '3.1.5'
  gem 'spring',                '2.0.2'
  gem 'spring-watcher-listen', '2.0.1'
end

group :test do
  gem 'rails-controller-testing', '1.0.2'
  gem 'minitest',                 '5.10.3'
  gem 'minitest-reporters',       '1.1.14'
  gem 'guard',                    '2.16.2'
  gem 'guard-minitest',           '2.4.4'
end

group :production do
  gem 'pg', '0.20.0'
end

# Windows環境ではtzinfo-dataというgemを含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

上記の書き換えが終わった後に

bundle update
bundle install --without production
rails s -b 0.0.0.0

この状態でhttp://localhost:3000/にアクセスすると、Yay! You’re on Rails!のデフォルト画面が表示されます。

rails

立ち上げたRailsサーバーはcommand + Cで停止することができます。

8. ssh接続の終了

$ exit

上記コマンドでSSH接続を終了することができます。

9. 仮想環境のマシンをシャットダウンする

vagrant halt

上記コマンドで仮想環境上のゲストOSをシャットダウンすることができます。

再度立ち上げたい時は、vagrant upで立ち上げvagrant sshで接続します(2回目以降は数分で完了します)。

終わりに

いかがでしたでしょうか?スムーズに行けば、Yay! You’re on Rails!の表示まで1時間以内にいけるではないかなと思っております。

Rubyという言語、Railsというフレームワークは触っていると本当に面白くて飽きないものなので、この記事で環境構築をしたことをきっかけに少しでもその魅力にハマっていただければ嬉しい限りです。

本記事についての質問や改善点のご指摘等がございましたら、コメントやTwitterでのDMなどをいただければ幸いです!

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

rails基本情報

環境構築

Railsのバージョンアップをする

Railsのバージョンアップ

$ gem install rails -v 5.2.4

Railsのバージョンを確認

$ rails -v

ImageMagickをインストールする

ImageMagickは画像処理のためのソフトウェア。画像を扱うことがある場合はインストール。

username:~/environment $ sudo yum -y install libpng-devel libjpeg-devel libtiff-devel gcc
username:~/environment $ cd
username:~ $ wget http://www.imagemagick.org/download/ImageMagick.tar.gz
username:~ $ tar -vxf ImageMagick.tar.gz
username:~ $ ls
username:~ $ cd ImageMagick-x.x.x-xx
username:~/ImageMagick-x.x.x-xx $ ./configure
username:~/ImageMagick-x.x.x-xx $ make
username:~/ImageMagick-x.x.x-xx $ sudo make install

ImageMagickがインストールされているか確認.

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

Rails再び。Homebrewで早速詰まる。

唐突に思い出す

役1年半前、ひたすらRailsを勉強していたことを思い出した。そしておもむろに書籍を買ってみた。ちなみに前回勉強していたときは、Railsチュートリアルでherokuでデプロイでつまずき脱落。

Homebrewインストールで早速詰まる

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Homebrewのサイトからコピペでインストール

しかしこんな感じのエラー↓

Error:
homebrew-core is a shallow clone.
To brew update, first run:
git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow
This restriction has been made on GitHub’s request because updating shallow
clones is an extremely expensive operation due to the tree layout and traffic of
Homebrew/homebrew-core and Homebrew/homebrew-cask. We don’t do this for you
automatically to avoid repeatedly performing an expensive unshallow operation in
CI systems (which should instead be fixed to not use shallow clones). Sorry for
the inconvenience!

よくみると、アップデートするためにまずこれをやれ
っぽいノリなのでやってみた↓

git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow

ほんでもう一回インストールやり直したら、

==> Installation successful!

多分成功したのでしょう。寝ます。

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

「時間が経って、別の人が見ても使いやすいコードを書こう」( n回目 )

こんにちは、株式会社ベストティーチャーで元気に働く たわら です。

とある日

先輩 「 キャンペーンのダミーデータを作ってくれる? rakeタスクで 」
ボク 「 ハイ! 」

ボク 「 ゴニョゴニョ、、、よし」

  task campaign: :environment do
    Campaign.create(
      title: "ダミーキャンペーン", 
      message: "条件 2021年1月1日(金)〜1月31日(日)までにキャンペーンに参加",
      duration: Time.zone.parse('2021-01-01 00:00:00')..Time.zone.parse('2021-01-31 23:59:59'))
  end

ボク「 できました! 」

先輩 「 ...... 」
先輩 「 ...... 」
先輩 「 これ一回きりしか使えないね、、、汎用性があるかたちに改善しよう! 」
先輩 「 時間が経って、別の人が見ても使いやすいコードを書こう 」
先輩 「 たとえば、先に変数で定義するとか。範囲は実行時点の月初から月末にするとか、、、 」
ボク 「 ハイ! 」

  task campaign: :environment do
    start_day = Time.current.beginning_of_month
    end_day = Time.current.end_of_month
    str_start_day = start_day.strftime("%Y/%m/%d/%a")
    str_end_day = end_day.strftime("%Y/%m/%d/%a")

    Campaign.create(
      title: "ダミーキャンペーン", 
      message: " 条件 #{str_start_day}#{str_end_day}までにキャンペーンに参加 ",
      duration: start_day..end_day)
   end

先輩 「 、、、んー、よいでしょう! 」
先輩 「 こうすれば、すぐ使えるね。毎回書き直さなくてよいから、今後使う人に親切になるね 」
ボク 「 勉強になりました! 」

学び

時間が経って、自分以外の人がコードを見た場合の、可読性や利便性を考えて、コードを書きましょう!
エンジニアとしてガシガシ勉強してグングン成長したいです!

宣伝

オンラインで英語を「書く」「話す」能力を磨く機会を提供するサービスを提供しています。
iOSエンジニアを募集していますので、よかったらご検討ください。

英語を学びながら働きたいiOSエンジニア募集 - Qiita Jobs
https://jobs.qiita.com/employers/266/postings/1102

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