- 投稿日:2021-01-07T23:49:41+09:00
[Rails] RSpec エラー expected `User.count` to have changed by 1, but was not given a block
はじめに
Rails のテストツール Rspec を使ってみました。
使用中に、expectedUser.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 は使わない。
最後まで読んでくださり、ありがとうございました。
- 投稿日:2021-01-07T23:48:25+09:00
RailsにおけるURLの直打ちを阻止する方法
きっかけ
viewファイルに条件分岐を作っていても、そもそもUrlから直打ちされてしまうとページに飛ばれてしまうことに気づいたため。
結論
任意のコントローラーにbefore_actionを使って、アクションを起こす前段階の条件分岐を作っておく。
コード
items.controller.rbclass 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の流れを今一度再確認できた。
- 投稿日:2021-01-07T22:02:25+09:00
Ransackでor検索機能を作る。
使用環境
rails 6.0.2 ubuntu (WSL)手順
gemインストール
gem 'ransack'
bundle
を忘れずに。内容の検索
post_controller
は既に作ってあるものとします。posts_controller.rbdef 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"
- 投稿日:2021-01-07T20:58:25+09:00
【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の位 になる
参考文献
- 投稿日:2021-01-07T17:15:45+09:00
rubyとpythonのif文の書き方の違い
- 投稿日:2021-01-07T15:59:44+09:00
サーバーに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:ssAPI(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:59checkexpires.protosyntax = "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.rbthis_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 maincheckexpires.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が準備できました。
あとはクライアント側とサーバ側にそれぞれ固有な処理を書いていけばいい感じですね。
頑張れそうです。
- 投稿日:2021-01-07T13:55:40+09:00
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_controllerclass 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.rbclass 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.rbclass 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.rbhas_one :intromodels/intro.rbbelongs_to :userこのように1対1の関係を結びました。
2-2)ルーティングとアクションの設定
config/routes.rbresources :users, only: [:show] resources :intros, only: [:new, :create, :edit, :update]ルーティングは上記のようにし、ネストはしませんでした。
理由は、ユーザー編集用のテーブルはイメージとしてはツイートなどの投稿用のテーブルと近いような気がしたからです。アクションもそうですが、イメージとしては投稿機能を、ユーザー編集に置き換えるとイメージを持つと実装しやすいかと思います。
次にアクションの定義を行います。
controllers/intros_controller.rbclass 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日半かかったので、この記事を見て少しでも作業が進んだという方がいらっしゃれば幸いです。
参考文献
- 投稿日:2021-01-07T13:42:58+09:00
【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
- 投稿日:2021-01-07T13:38:10+09:00
deviseのlockableを使うときに意識しないといけないこと
つまり
config.unlock_strategy
を:time
や:none
にするときはよく考えてバージョン
- Rails 6.1.0
- devise 4.7.3
lockableについて
deviseのlockableは、ログインに何回か失敗したらアカウントロックを行う、というもの。
注意すべきは、「ログインがロックされる」のではなく、「アカウントがロックされる」のだということ。
これは、アカウントロック中にアクセスしたログイン中セッションは、全て強制ログアウトされる動作になる。これを利用して、次のようなイタズラをされると、結構困る。
- ユーザAがdeviseを使ったwebサービスにログインしている
- 他人がユーザAのメールアドレスでログインしようとするが連続で失敗して、ユーザAのアカウントロックされる
- ユーザAがアカウントロック中にwebサービスにアクセスする
- ログアウトされて、ユーザAはロック解除されるまでwebサービスが利用できなくなる
回避方法
同回避するか、3つほど考えた。
メールアドレスでアンロックできるようにする
deviseの設定に
unlock_strategy
という物があり、これには:time
,:both
,:none
が指定できる。
- アンロックするリンクをメールで送信する
:time
- 指定時間が経過するまでロックされる
:both
:time
で自然にアンロックされるか両方- デフォルトはこれなので、比較的安心か。
:none
- アンロック方法は無い。アンロックにはDB直接いじるしかなさそう?
このうち、
:both
の場合はロックされたアカウントの所有者が自分でロックを解除できるので、アカウントを使用できなくことに関する被害は少ない。
けど、:time
の場合は、イタズラでロックされたユーザがいた場合に、時間経過で解除されるのを待たないといけない(config.unlock_in
のデフォルトは1時間)ので、困ることも発生する。ログインだけロックする
「アカウントロック」ではなく、「ログインをロック」にする場合。
難易度が非常に高いからおすすめしないのだけれど、active_for_authentication?
をオーバーライドする方法がある。
めっちゃ単純なアプリでlockableのみを使っている場合は、次のようにしても良いかもしれない。def active_for_authentication? true endでも、confirmableとかも使っている場合は? 他にも色々なアカウントのアクティベーションに関する仕様がある場合は?
安易にactive_for_authentication?
をtrue
にしないようにしましょう、余計なセキュリティリスクが増えます。deviseを使わない
例えば、deviseを避ける
終わりに
セキュリティに関する話なので、間違っているとまずい・・。
厳しいご指摘お待ちしております。
- 投稿日:2021-01-07T12:51:46+09:00
「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の警告を無視する記述に関しては
以下が参考になるかと思います。(古いかも?)
- 投稿日:2021-01-07T12:18:06+09:00
devise gem気づきメモ: リダイレクトの仕様
アプリ開発で、トップページを作成する前にdeviseを用いてユーザー管理機能を実装していたところ、分かっているようで分かっていなかったdeviseのリダイレクトの仕様に気づいたので記録として残します。
deviseの仕様に気づいたきっかけ
トップページを作成する前に、deviseで新規登録機能を実装し、ローカル環境(localhost:3000)で動作確認をしたところ、新規登録後にRailsのお馴染みのトップページにリダイレクトしたことです。
新規登録の処理後はルートパスにリダイレクトする
新規登録機能(コントローラ:devise/registration#create)の処理後は、ルートパス(root_path)にリダイレクトされます。
route.rb
でルートパス(root_path)を設定していなかった場合、先程のRailsのお馴染みのトップページが表示されることになります。ログアウトの処理後はログイン画面にリダイレクトする
ログアウト機能(コントローラ:devise/sessions#destroy)の処理後は、ログイン画面(コントローラ:devise/sessions#new)にリダイレクトされます。
先述の新規登録→ルートパスへのリダイレクトに気づいて、「じゃあ、ログアウト後はどこにリダイレクトされるんだろう?」と思って確認したところ、ログイン画面にリダイレクトされることが分かりました。
- 投稿日:2021-01-07T12:03:52+09:00
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(ゆらぎ)
ってことらしいです
2. Enumerator を返そう?
それとは別に、こちらの記事で紹介されているテクニックも、どこかで使えないかな〜っと前から気になってました。
参考: Ruby: EnumerableをincludeするよりEnumeratorを返そう(翻訳)
記事の趣旨としては、「そのクラスがコレクションなら
include Enumerable
すればいいけど、そうじゃないならメソッドでEnumerator
オブジェクトを返すようにしとけばいいよ」ってことみたいです。
Exponential Backoff
とEnumerator
。...そう、点と点がつながりましたね ?
両者を組み合わせてコードにしてみました。
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ピタッと収まりました!
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)の指定が必要ですね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
を使っていないので小数は切り捨てです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]数値の増えたり減ったりが激しめですね
7. まとめ
今回の挑戦を通じて、Ruby の
Enumerator
クラスの理解が深まりました とても有用なクラスだと思いますので、積極的に使っていきたいと思います。リトライ処理については、今後は少なくとも「5秒ごとに最大5回」よりはちょっとはマシなリトライ処理を、最初から実装しておくことができそうです。
もちろん、どの遅延パターンが最も効果的かについては、ケースごとに効果測定する必要がありますが、Jitter を入れるだけでも、リトライのタイミングを分散させて、サーバーの負荷を減らす効果があるはずです tabun
みなさんもぜひ使ってみてください
- 投稿日:2021-01-07T09:44:26+09:00
プログラミングとは?
そもそもプログラミングとは何か?
コンピューターが実行する処理の手順をまとめたデータをプログラムと呼ぶ
プログラミング言語とは?
普段私たちが話している言葉はコンピューターは理解できない。
コンピューターがわかる言葉をプログラミング言語という。
プログラミング言語で書かれたテキストやファイルをソースコードと呼ぶプログラミング言語の種類
Ruby
java
php
Pythonなどがある。
プログラムを実行するにはターミナルにコマンドを打ち込んで実行する。
ここからプログラミング言語Rubyについて記述していきます。
irb
irbコマンドはターミナルから直接Rubyのプログラムを動かすことのできる機能(Interactive Ruby の略)
エディタでコードを記述してターミナルを実行するのではなく直接ターミナルで実行できる。ターミナル画面
#irbを起動 % irbirb(main):001:0> "Welcome! Ruby" #このように打ち込む =>"Welcome! Ruby" #このように出力今日は以上です。
- 投稿日:2021-01-07T08:25:49+09:00
railsでメール送信機能実装
はじめに
現場で使える Ruby on Rails 5速習実践ガイドでインプットを行っており、アウトプットで投稿しています。
サンプルは、タスク管理アプリです。今回の投稿では、ユーザーがタスクを登録したときに、「○○タスクが登録されました」というメールを送る機能を実装します。目次
メイラーの作成
メイラーとは、ActionMailerのことで、railsに搭載されているメールを送る機能です。controllerが、templateに情報を渡し画面出力を行っているのと同様に、Mailerもtemplateに情報を渡しメールを送信します。
まずは、今回実装するTaskMailerを作成します。以下のコマンドを実行します。rails g mailer TaskMailerメソッドを追加
先程のコマンドで
app/mailer/task_mailer.rb
というファイルが作成されます。そこに、今回送信するメールのメソッド「creation_email」を定義します。app/mailer/task_mailer.rbdef 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.rbrequire 'rails_helper' describe TaskMailer, type: :mailer do endこれで枠組みは完成なので、実際にテストを書いていきます。
spec/mailers/task_mailer_spec.rbrequire "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参考文献
- 投稿日:2021-01-07T06:03:05+09:00
【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.rbhas_secure_password
を消す。
has_secure_passwordとは、パスワードをDBに保存する時に、暗号化して保存してくれるrailsの機能のこと。deviseを使う場合は不要なので、コメントアウトしておきましょう。参考にした記事
https://qiita.com/kents1002/items/4079e3d05d322febe00e
https://qiita.com/shumpeism/items/4d8946ade2dbdccab31c
- 投稿日:2021-01-07T01:42:58+09:00
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 gityarnのインストール
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 -yRubyをインストール
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-buildrubyをインストール
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" EOFecho 'export PATH="$(echo "$PATH" | sed -r -e '"'"'s;:/mnt/[^:]+;;g'"'"')"' >> ~/.bash_profile source ~/.bash_profile設定を有効にする為にWindowsを再起動する
参考
- 投稿日:2021-01-07T01:40:25+09:00
Rails on Tiles(どう書く)
http://nabetani.sakura.ne.jp/hena/ord5railsontiles/
与えられたタイルのレールに沿って移動するとき、範囲外にはみ出てしまうまでのルートを求める問題です。
Rubymodule 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
はタイルの位置を数字で表しているので、最後にアルファベットに変換しています。
- 投稿日:2021-01-07T01:13:21+09:00
【初心者向け】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なので、ここからはそのバージョンでの動作を前提としております)Vagrant
次に、仮想化ソフトを管理するツールであるVagrantをインストールします。
下記ダウンロードページにアクセスし、上記のVirtualBoxと同様に、インストーラのダウンロード、インストーラ起動、インストールという手順を踏めばほとんど迷うことなく完了すると思います。
仮想化ソフトである「VirtualBox」を操作するためのツールが「Vagrant」である、という認識を持っていただければとりあえず最低限の知識としてはOKです。
(※ 2021年1月7日現在の最新版は2.2.14なので、ここからはそのバージョンでの動作を前提としております)環境構築手順
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_dev3. 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 Users
やbundle install
等のコマンドはSSH接続をした状態で、作成したRailsアプリケーションのディレクトリ上で叩くことになります。7. Gemfile編集
Gemfileを下記に書き換え(Railsチュートリアル第3章の内容)
Gemfilesource '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サーバーは
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などをいただければ幸いです!
- 投稿日:2021-01-07T01:12:21+09:00
rails基本情報
環境構築
Railsのバージョンアップをする
Railsのバージョンアップ
$ gem install rails -v 5.2.4Railsのバージョンを確認
$ rails -vImageMagickをインストールする
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 installImageMagickがインストールされているか確認.
$ convert -version
- 投稿日:2021-01-07T01:03:01+09:00
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!多分成功したのでしょう。寝ます。
- 投稿日:2021-01-07T00:44:00+09:00
「時間が経って、別の人が見ても使いやすいコードを書こう」( 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