- 投稿日:2020-01-12T23:43:58+09:00
Rspec, Factory_bot, Fakerを用いたモデル単体テスト
はじめに
- Rspec, Factory_bot, Fakerを用いたテストについてメモを残したいと思います。
- 今回はuserモデルでテストします。
- 開発環境
- ruby 2.5.1
- rails 5.2.4.1
gemの導入
Gemfilegroup :development, :test do gem 'rspec-rails' gem 'factory_bot_rails' end group :test do gem 'faker' endgemを追加したら、bundle installする。
Rspecの設定
terminal$ rails g rspec:install # Rspecの設定ファイルが作成される。 create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb
- spec/spec_helper.rb ・・・Rails無しでRspecを利用するときに使う。
- spec/rails_helper.rb ・・・RailsでRspecを利用するときに使う。
.rspec# 以下を追加することで出力を見やすくできる。 --format documentationFactory_botの設定
- specディレクトリ下に「factories」ディレクトリを作成。
- その中に「users.rb」ファイルを作成。
- spec/factories/users.rbを以下のように編集する。
spec/factories/users.rbFactoryBot.define do factory :user do name {"abe"} sequence(:email) {Faker::Internet.email} phone_number {Faker::PhoneNumber.phone_number} end end
- fakerを使用し、ダミーデータを作成。他にもいろいろなfakerがあります。
Factory_botの記述の省略
spec/rails_helper.rb# 上記省略 RSpec.configure do |config| config.include FactoryBot::Syntax::Methods #追加する # 下記省略 end「config.include FactoryBot::Syntax::Methods」 を追加することで以下のように省略できる。
# 追加前 user = FactoryBot.build(:user) # 追加後 user = build(:user)モデルテストの記述
- app/models/user.rb にバリデーションを記述します。
app/models/user.rbclass User < ApplicationRecord validates :name, presence: true, length: { maximum: 6 } validates :email, presence: true, uniqueness: true validates :phone_number, presence: true end
- spec/models/users_spec.rbにバリデーションが正常に機能しているか確かめるコードを記述します。
spec/models/users_spec.rbrequire 'rails_helper' describe User do describe '#create' do it "name, email, phone_numberがあれば有効" do user = build(:user) expect(user).to be_valid end it "nameがないと無効" do user = build(:user, nickname: nil) user.valid? expect(user.errors[:nickname]).to include("can't be blank") end it "emailがないと無効" do user = build(:user, email: nil) user.valid? expect(user.errors[:email]).to include("can't be blank") end it "phone_numberがないと無効" do user = build(:user, phone_number: nil) user.valid? expect(user.errors[:phone_number]).to include("can't be blank") end it "emailが重複していたら無効" do user = create(:user) another_user = build(:user, email: user.email) another_user.valid? expect(another_user.errors[:email]).to include("has already been taken") end it "nameが7以上だったら無効" do user = build(:user, nickname: "a" * 7) user.valid? expect(user.errors[:nickname]).to include("is too long (maximum is 6 characters)") end it "nameが6以下だったら有効" do user = build(:user, nickname: "a" * 6) expect(user).to be_valid end end endちなみに、lengthのバリデーションについて忘れないようにメモ。
validates :name, length: { maximum: 10 } # 10以下、 最高で10文字まで validates :name, length: { minimum: 6 } # 6以上、 最低でも6文字必要 validates :name, length: { in: 6..10 } # 6文字以上10以下 validates :name, length: { is: 6 } # 6文字のみRspecを実行する
terminal# 特定のファイルを実行 $ bundle exec rspec spec/models/users_spec.rb # すべてのファイルを実行 $ bundle exec rspecまとめ
間違っていることやアドバイスなどご指摘いただければ助かります。
よろしくお願いいたします。
- 投稿日:2020-01-12T22:03:13+09:00
【Rails入門】エラーと仲良くなれるかもしれない開発手法【読み物】
どんもー、@snskOgataです。
今回はみんな大好き、エラーとの付き合い方を書いていこうかなと思います。
まあ、ちょっとした読み物として楽しんでもらえたらと思います。
対象読者は以下の2つに当てはまる人です。
・エラーという存在が憎くて憎くてしょうがない
・Railsアプリを読み物を見ながら1, 2度作ってみたけど、実際に自分で何も見ずに作るとなったらどうやっていけばいいかわからない
エラーが出るとどうしても怒られているような感覚になってしまいますが、
そうじゃなくて本当は、エラーは正解への道筋を示してくれる相棒のようなものだということを感じてくれると嬉しいです。
実際やっていくこととしては、
エラーを吐かせながら、それに従ってアプリケーションを作っていくということをしていきます。
これによりひとつずつ着実に作業を進めることができます。
アプリの内容は、つぶやきを作成しその一覧を表示する簡単なアプリケーションです。
途中で大体もう流れがわかったら最後まで飛ばしてくれても大丈夫です(笑)1. Setup
まずはアプリケーションをビルドします。
Railsバージョンは5.2.3を使用します$ rails _5.2.3_ new dev-with-error $ cd dev-with-error $ bundle $ rails db:migrateやってることはRailsアプリケーションを立ち上げ、フォルダを移動して、gemファイルのインストール、DBの作成です。
ここまでやるとローカルサーバを立ち上げることができます。$ rails sWebブラウザで「localhost:3000」にアクセスすればお馴染みの画面が出てきます。
2.Tweet作成画面を作る
次にTweetの新規作成画面を作っていきたいと思います。
ここで通常であれば、ルーティングをして、コントローラとモデルを作って、適切なビューを作って...とやっていくと思いますが、
今回は冒頭でも書いたようにまずはエラーを吐かせてそれに従って作っていくというふうに作って行きます。実際にやることとしては、Tweetの新規作成画面ということで「localhost:3000/tweets/new」というアドレスにアクセスをします
まだアプリケーション本体に何も書き込んでいないわけなので当然エラーを吐かれます。
エラーの内容を見てみると、ルーティングがされていないよ!とのエラーが出ているので、実際にルーティング部分を書き加えます。config/routes.rbRails.application.routes.draw do resources :tweets, only: :new endもう一度先ほどのURLにアクセスすると次はこのようなエラーが出ます。
今度はTweetsControllerがないのでそれを教えてくれます。
エラーに従ってコントローラを作成しましょう。$ rails g controller tweetsnewアクションが見つからないということでTweetsControllerに追記します。
app/controllers/tweets_controller.rbclass TweetsController < ApplicationController def new end end保存をし再びページをリロードすると、
随分長いエラー文となっていますが、冒頭の「TweetsController#new is missing a template」から、newアクションに対応するビューのテンプレートがないというのがエラーの原因というのがわかります。
なので、Tweets#newに対応するように、「views/tweets/new.html.erb」を作成します。app/views/tweets/new.html.erbHello「localhost:3000/tweets/new」にアクセスすると、
おめでとう!これで新規作成画面の表示に成功しました!さて、新規投稿するためにインスタンス変数@tweetを用意しましょう。
app/controllers/tweets_controller.rbclass TweetsController < ApplicationController def new @tweet = Tweet.new end endここで再びページリロードをすると
Tweetというものがわからない!と言われるので、Tweetモデルを作成してあげます$ rails g model tweet content:string ...モデルが生成されましたが
問題を解決するには「rails db:migrate」して、と書かれています。
どうやらマイグレーションファイルをDBに適用していないのが問題みたいです。$ rails db:migrateこれで再びエラー無しの状態になりました!
次に、実際にビューに今回用意したインスタンス変数を使って、フォームを作成してみます。app/views/tweets/new.html.erb<%= form_with model: @tweet do |form| %> <%= form.text_field :content %> <%= form.submit %> <% end %>ここでのエラーはわかりづらいですが、form_withによってtweets_pathにポストがなされるのですが、そのメソッド(:create)が宣言されていないためにのエラーとなります。
ルーティングで「:create」を追加してあげましょう。config/routes.rbRails.application.routes.draw do resources :tweets, only: [:new, :create] end再びエラー無しに!
ただしcreateアクションを実際に実装していないため、フォームを入力しても何も変わりません。
なのでコントローラにcreateを追記してあげましょう。
ついでに新規投稿画面に、投稿された一覧を表示できるようにしましょう。app/controllers/tweets_controller.rbclass TweetsController < ApplicationController def new @tweet = Tweet.new @tweets = Tweet.all #追記:全投稿取得→表示に使用 end def create Tweet.create(tweet_params) # DBに保存 redirect_to new_tweet_path # 新規投稿画面に再び戻る end private # ストロングパラメータ def tweet_params params.require(:tweet).permit(:content) end endapp/views/tweets/new.html.erb<% # local: trueにすることで、送信後ページが更新される %> <%= form_with model: @tweet, local: true do |form| #local %> <%= form.text_field :content %> <%= form.submit %> <% end %> <p>Tweets</p> <% # Tweetの一覧表示 %> <%- @tweets.each do |tweet| %> <%= "#{tweet.content} <#{tweet.created_at}>" %> <br/> <% end %>同様にして編集ページや一覧表示ページ、あるいは新しいコントローラやモデルを作成していけば、ある程度のものであればエラーに従っていくだけで作れてしまうと思います!
3. 一気にRMVCを作ってしまう弊害
この手法だとエラーに従ってひとつずつパーツを揃えていくため、今コーディングしている部分が正しくなければ先に進むことができません。
すなわち、エラー部分が限定されて間違っている可能性がある部分というのがすごく限定的になります。Railsを始めたての人は、MVCを一度に実装してしまったために、エラーが発生したときに何処で起きているのかがわからなくなってしまう、という問題に遭遇してる人が多い様な気がします。
なので、こういうように着実に進められる方法もあるよ、エラーは怒ってるんじゃなくて導いてくれてるんだよ、ってのを少しでも実感してくれたら幸いです。4.テスト駆動開発
こういう開発手法を思いついたきっかけがテスト駆動開発(TDD)です。
この手法は、今回エラーを起こして進めて行ったような感じで、まずは通らないテストを書いて、それに通るように実装をしていくという手法です。
実際の現場でも使われている手法で1、一度は経験しておいて損はないかなと思います。
読みながらならある程度簡単なアプリなら作り方が理解できる方なら、Rails Tutorialという本が無料で公開されているので挑戦してみると良いかもしれません。(※Rails Tutorialはminitestというデフォルトのテストを用いているのでRSpecを使ってる方は違いには注意!)
Rails Tutorial:https://railstutorial.jp/chapters/beginning?version=5.1#cha-beginning加えてここではgem(deviseなど)を使わずにユーザ管理を実装するのですが、Deviseの作者も一度は自分の手でUserモデルを作成することを推奨しているので、その点でもオススメです。
追記:ついでにHerokuで簡単にデプロイする方法とか、自作ヘルパーメソッドの実装とかもすごくタメになります!
実際はコストの面との相談で使われなかったりする? テスト自体は非常に重要なことには変わりないですが! ↩
- 投稿日:2020-01-12T21:52:42+09:00
Ruby on Rails APIモードのCRUD実装 【初学者のReact✗Railsアプリ開発 第5回】
やったこと
- Ruby on RailsのAPIモードでCRUDを実装する(ただし、更新(U)はなし)
前回の記事
Reactのreduxを用いたログイン処理周りの実装【初学者のReact✗Railsアプリ開発第4回】
参考にさせていただいた記事
https://qiita.com/k-penguin-sato/items/adba7a1a1ecc3582a9c9
実装手順
モデルとコントローラーの作成
$ docker-compose run api rails g model post content:string $ docker-compose run api rails g controller api/v1/posts生成されたマイグレーションファイルを編集します。
db/migrate/XXX_create_posts.rbclass CreatePosts < ActiveRecord::Migration[5.2] def change create_table :posts do |t| t.string :content t.references :user, foreign_key: true t.timestamps end add_index :posts, [:user_id, :created_at] end end$ docker-compose run api rake db:migrateroute.rb
route.rbRails.application.routes.draw do namespace :api, defaults: { format: :json } do namespace :v1 do ##省略## resources :posts end end end
- resourcesで、GET, POSTなど複数のルーティングを一気に設定できる。resourceとの違いに注意。
posts_controller
posts_controllermodule Api module V1 class PostsController < ApplicationController before_action :set_post, only: [:index, :show, :update, :destroy] before_action :authenticate_api_v1_user! def index posts = Post.all render json: { status: 'SUCCESS', message: 'Loaded posts', data: posts} end def show @user = @post.user json_data = { 'post': @post, 'user': { 'name': @user.name, 'nickname': @user.nickname, 'image': @user.image } } render json: { status: 'SUCCESS', message: 'Loaded the post', data: json_data} end def create post = Post.new(post_params) if post.save render json: { status: 'SUCCESS', data: post} else render json: { status: 'ERROR', data: post.errors } end end def destroy @post.destroy render json: { status: 'SUCCESS', message: 'Delete the post', data: @post} end def update end private def set_post @post = Post.find(params[:id]) end def post_params params.require(:post).permit(:content, :user_id) end end end endmodels/post.rb
post.rbclass Post < ApplicationRecord belongs_to :user endPostmanを用いてAPIの動作確認をする
create
chromeのデベロッパーツール->Application->Local Storageからauth_tokenとかをコピーして、
Postmanに貼り付ける。
そしてlocalhost:3000/api/v1/postsにPOSTすると
postが作成されたことが確認できます。index
localhost:3000/api/v1/postsにGETすると
show
localhost:3000/api/v1/posts/1にGETすると、idが1のpostが返されます。
destroy
localhost:3000/api/v1/posts/1にDELETEすると、idが1のpostが消えます。
indexで確認すると、消えています。
- 投稿日:2020-01-12T21:32:15+09:00
【Ruby】ある整数の約数(要素の数、和)を計算する
興味本位で書いてみた結果の、備忘録用です。
(メソッドの定義文については、幸運にもscivola様からアドバイスを頂きました、1行でのリファクタリングが可能です。是非ご参照ください。)divisor.rbdef divisor(num) result = [] i = 1 while i <= num do remainder = num % i if remainder == 0 result << i end i += 1 end return result end puts "約数を算出したい整数を入力してください" num = gets.to_i r = divisor(num) puts r puts "約数の数は#{r.length}です" puts "約数の合計は#{r.sum}です"
- 投稿日:2020-01-12T21:32:15+09:00
【Ruby】ある整数の約数(要素の数、和)を求める
興味本位で書いてみた結果の、備忘録用です。
(メソッドの定義文については、幸運にもscivola様からアドバイスを頂きました。1行でのリファクタリングが可能です。是非コメント欄をご参照ください。)divisor.rbdef divisor(num) result = [] i = 1 while i <= num do remainder = num % i if remainder == 0 result << i end i += 1 end return result end puts "約数を算出したい整数を入力してください" num = gets.to_i r = divisor(num) puts r puts "約数の数は#{r.length}です" puts "約数の合計は#{r.sum}です"
- 投稿日:2020-01-12T21:03:51+09:00
Railsチュートリアルメモ - 第7章
第7章 ユーザー登録
7.1 ユーザーを表示する
ポイント
デバッグ
<%= debug(params) if Rails.env.development? %>
で画面にデバッグ情報を表示できる。
if Rails.env.development?
で開発環境のみに表示するよう制限している- コントローラー内にdebuggerを記載して画面を表示すると、rails serverを起動しているコンソールがコマンド待受け状態になり、debuggerが呼び出された状態のままでrails consoleの操作を実行できる
Sassのミックスイン
@mixin box_sizing { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } /* miscellaneous */ .debug_dump { clear: both; float: left; width: 100%; margin-top: 45px; @include box_sizing; }ルーティング
config/routes.rb
にresourceを追加すると、名前付きルートが使用可能になり、RESTに従ったいくつかのURLが使用可能になるconfig/routes.rbRails.application.routes.draw do resources :users endGravatar
RailsというよりGravatarの使用方法の説明なので割愛
7.2 ユーザー登録フォーム
ポイント
form_forによるフォームの作成
.html.erb内でform_forを使用すると、フォームを生成してくれる
.html.erb
<%= form_for(@user) do |f| %> ... <%= f.label :name %> <%= f.text_field :name %> ... <% end %>↓
html<form action="/users" class="new_user" id="new_user" method="post"> ... <label for="user_name">Name</label> <input id="user_name" name="user[name]" type="text" /> ... </form>
- また、emailであれば、モバイル端末から入力フォームをタップすると、メールアドレスに最適化された特別なキーボードが表示される。passwordであれば、文字が隠蔽されて表示されるようにHTMLを生成してくれる。
- CSRF対策のトークンの生成も行ってくれる
paramsハッシュでの値の受け渡し
フォームで指定した値は
params
というハッシュに保存されてコントローラーに渡されるルールになっている(チュートリアルでの説明があまりにさらっとしすぎていて、params
がどこで定義されているのかわからず少し混乱した)7.3 ユーザー登録失敗
ポイント
Strong Parameters
マスアサインメント:
DB登録・更新で複数のカラムを一括で指定して登録すること。メソッドにハッシュを渡して更新カラムと値を指定する。
意図的に重要なカラムの値を書き換えられる危険性があるため、Strong Parametersというテクニックを使ってController層で対策することが推奨されている(かつてはModel層で対策していたらしい)params.require(:user).permit(:name, :email, :password, :password_confirmation)privateキーワード
クラス内で
private
と記載された後に記載された要素はprivate属性となる(外部クラスからは見えない)エラー表示
- Rails全般の慣習として、複数のビューで使われるパーシャルは専用のディレクトリ「shared」によく置かれる
- pluralizeメソッドによって、英語の単数系と複数形を良い感じに処理して表示してくれる
- e.g. 0 errors, 1 error, 2 errors
- Railsは、無効な内容の送信によって元のページに戻されると、CSSクラスfield_with_errorsを持ったdivタグでエラー箇所を自動的に囲んでくれる
- (エラー表示とは関係ないが、)
class form-control
を追加すると、BootstrapでフォームがきれいになるSassの@extend関数
@extendで特定の位置に属性を追加できる
.field_with_errors { @extend .has-error; .form-control { color: $state-danger-text; } }統合テスト
rails generate integration_test users_signup
で統合テストの作成assert_no_difference, assert_difference
以下のように記載することで、ブロック内の実行前後で引き数が変化していないこと、変化していることを検証できる
assert_no_difference 'User.count' do end assert_difference 'User.count' do endassert_template
以下のように記載することで、意図どおりページが再描画されているかどうかを検証できる(エラーメッセージの表示によるDOMの差異は無視される?)
assert_template 'users/new'7.4 ユーザー登録成功
ポイント
redirect_to
コントローラーに以下のように記載すると、自動的に名前付きルート付きで解釈してくれる
if @user.save redirect_to @user else render 'new' end↓
if @user.save redirect_to user_url(@user) else render 'new' endflash変数
flash変数に代入したメッセージは、リダイレクトした直後のページでのみ表示できる(二回目は表示されない)
シンボル => 文字列の自動変換
Railsではシンボルをテンプレート(.html.erb)内に表示しようとすると、文字列に自動変換する
e.g. :success => "success"7.5 プロのデプロイ
ポイント
WebサーバPumaの導入
config/environments/production.rb
のconfig.force_ssl = true
をコメントインすると、本番環境でのSSL化が有効になる- Rails4.2までは
config/puma.rb
の手動作成が必要だったが、Rails5以降はデフォルトで作成済み- Procfileをプロジェクトルートに作成してherokuにgit pushすれば反映される
./Procfile
web: bundle exec puma -C config/puma.rb
- 投稿日:2020-01-12T20:16:57+09:00
【初心者向け】Rubyで超簡単ドリンク注文アプリケーションを作る
メソッドを使用して、ドリンク注文アプリケーションを作ってみる
条件
・実行時、どのようなドリンクがあるかを表示させる
・ドリンクには番号をつける
・選ばれた番号と同じドリンク名を表示させる擬似コードを書く
def otya お茶が選ばれたときのメソッド end def coffee コーヒーが選ばれた時のメソッド end def beer ビールが選べれた時のメソッド end def saida サイダーが選べれた時のメソッド end puts "何を飲みたいですか?" お茶、コーヒー、ビール、サイダーと、それぞれのドリンクメニューを表示させる 1の時お茶 2の時コーヒー 3の時ビール 4の時サイダー それ以外は "無効な入力値です"と表示させる end・それぞれのドリンクが選ばれた際のメソッドを用意しておく(今回はただ、文字を表示させるだけ)
・ドリンクメニューはeach.with_indexを使用し、メニューの左に1から番号をつける
・case whenを使用し、条件分岐を書いて、それぞれのメソッドが動くように書く実際にコーディングしていく
def otya puts "あなたが選んだのはお茶" end def coffee puts "あなたが選んだのはコーヒー" end def beer puts "あなたが選んだのはビール" end def saida puts "あなたが選んだのは三ツ矢サイダー" end puts "何を飲みたいですか?" drink_menu = ["お茶","コーヒー","ビール","三ツ矢サイダー"] drink_menu.each.with_index(1) do |drink_name, number| puts "#{number}:#{drink_name}" end case gets.to_i when 1 otya when 2 coffee when 3 beer when 4 saida else puts "無効な入力値です" end動作確認
何を飲みたいですか? 1:お茶 2:コーヒー 3:ビール 4:三ツ矢サイダー 1 あなたが選んだのはお茶メソッド名は本当はよくないんですが、適当です。。。
今回、メソッドを使ったアプリケーションを作ってみたかった。。。まだまだ未熟ですが、プログラム考えてる時ってワクワクして楽しいですよね。
次はもうちょっと複雑な処理もやってみようと思います。最後までみていただきありがとうございます。
- 投稿日:2020-01-12T20:16:57+09:00
【初心者向け】超簡単ドリンク注文アプリケーション
メソッドを使用して、ドリンク注文アプリケーションを作ってみる
条件
・実行時、どのようなドリンクがあるかを表示させる
・ドリンクには番号をつける
・選ばれた番号と同じドリンク名を表示させる擬似コードを書く
def otya お茶が選ばれたときのメソッド end def coffee コーヒーが選ばれた時のメソッド end def beer ビールが選べれた時のメソッド end def saida サイダーが選べれた時のメソッド end puts "何を飲みたいですか?" お茶、コーヒー、ビール、サイダーと、それぞれのドリンクメニューを表示させる 1の時お茶 2の時コーヒー 3の時ビール 4の時サイダー それ以外は "無効な入力値です"と表示させる end・それぞれのドリンクが選ばれた際のメソッドを用意しておく(今回はただ、文字を表示させるだけ)
・ドリンクメニューはeach.with_indexを使用し、メニューの左に1から番号をつける
・case whenを使用し、条件分岐を書いて、それぞれのメソッドが動くように書く実際にコーディングしていく
def otya puts "あなたが選んだのはお茶" end def coffee puts "あなたが選んだのはコーヒー" end def beer puts "あなたが選んだのはビール" end def saida puts "あなたが選んだのは三ツ矢サイダー" end puts "何を飲みたいですか?" drink_menu = ["お茶","コーヒー","ビール","三ツ矢サイダー"] drink_menu.each.with_index(1) do |drink_name, number| puts "#{number}:#{drink_name}" end case gets.to_i when 1 otya when 2 coffee when 3 beer when 4 saida else puts "無効な入力値です" end動作確認
何を飲みたいですか? 1:お茶 2:コーヒー 3:ビール 4:三ツ矢サイダー 1 あなたが選んだのはお茶メソッド名は本当はよくないんですが、適当です。。。
今回、メソッドを使ったアプリケーションを作ってみたかった。。。まだまだ未熟ですが、プログラム考えてる時ってワクワクして楽しいですよね。
次はもうちょっと複雑な処理もやってみようと思います。最後までみていただきありがとうございます。
- 投稿日:2020-01-12T18:06:42+09:00
RailsのActiveStorageで画像が表示されずTypeError(no implicit conversion of nil into String)がでるようになった
状況
ローカルで開発していたアプリをDockerで構築し直していたらActiveStorageを使用して表示していた画像が表示されなくなった
ログを見ると以下のようにTypeError (no implicit conversion of nil into String)が発生してるStarted GET "/rails/active_storage ~ (省略)~ Processing by ActiveStorage::DiskController#show as PNG Parameters: {"content_type"=>"image/png", "disposition"=>"inline; ~(省略)~ Completed 500 Internal Server Error in 1ms (ActiveRecord: 0.0ms) TypeError (no implicit conversion of nil into String): rack (2.1.0) lib/rack/files.rb:25:in `expand_path' rack (2.1.0) lib/rack/files.rb:25:in `initialize'原因
最新版のRack(2.1.0)で変更された部分が原因
Rails “TypeError (no implicit conversion of nil into String)” when loading images using image_tag, activeStorage and “has_many_attached”
Rack 2.1.0 breaks ActiveStorage #1464解決策
次のマイナーアップデートで修正されるみたいですが取り急ぎはダウングレードすればよい
Gemfileを編集してGemfilegem 'rack', '~> 2.0.8'bundle updateで解決
- 投稿日:2020-01-12T17:35:03+09:00
Laragonで簡単に開発環境を構築する
皆さん、学習するとき、開発をするとき、ローカルで確認を行うときの環境構築はどうされていますか?
恐らくMacを所有している方が多くDockerを使われている方が多いと思います。自分が現在、所有しているはHPのSPECTRE X360 13、Windows 10 Homeです。Windows 10 Homeなのです。
ラップトップと言っても非力というほどではありませんがVagrantを立ち上げている間、かなりファンが回り気になります。
それに決して速いとは言えません。さらに現時点ではまだWSL2とDocker for WSLは正式リリースされていません。ではどうするか?
https://laragon.org/Laragonです。
Windows向けのAMP環境構築ソフトで
PHP、Node.js、Python、Java、Go、Rubyをサポートし、
ApacheとNginxのどちらを使用するかを切り替えることができます。
では早速、インストールを行っていきましょう。1. インストール
https://github.com/leokhoa/laragon/releasesGithubから最新版を落としてきましょう。
環境構築の経験がない、初学者の方はlaragon-full.exeを選んでいきましょう。
以後はこれをベースに進めます。インストール先は拘りがなければわかりやすいようデフォルトのC:\Laragonにしておきます。
VSCode等を使用する際は"Sublime Text & Terminalを追加する"のチェックが外れた状態にしておきます。
ちなみにNotepad++、WinSCP、Putty、HeidiSQL Portable等がインストールされるのでこの1回のインストールだけで開発が出来るようになります。2. 起動
では起動してみましょう。
このスクリーンショットでPHP7.4になっているのは自身で後から追加ができるためです。
"ウェブ"を押すとホスト名を"localhost"ポート番号を8080で設定しているので
"http://localhost:8080/"
の形でブラウザが開きます。
"データベース"でHeidiSQL
"ターミナル"でCmderを起動します。
"ルート"は設定されたルートフォルダを表示します。"ウェブ"で表示される場所も同じものになります。
設定は右上にの歯車マークから表示できる設定で行います。3. バージョンの追加
今回はPHPを例に紹介します。
https://windows.php.net/download/
から使用するものをダウンロードしてきます。
フォルダを解凍後、
C:\laragon\bin\php\へ追加します。
PythonであればC:\laragon\bin\python\のようになります。
追加後に使用するバージョンを左上のメニューから選択します。
ここから起動時に使用する言語やバージョン等を切り替えることが出来ます。4.ガンガン使いましょう!
Macには現時点で対応していませんし、万能とまではいかないまでも
軽く使いやすいので空いた時間でサクッと作業を行うのに役立ちます。
OSがWindows 10 Homeで開発環境で悩んでいる方に特にオススメです。
- 投稿日:2020-01-12T14:59:29+09:00
Railsチュートリアルメモ - 第6章
6.1 Userモデル
Active Recordの使用方法についての章。Active RecordはたぶんRailsの中で最も核となるライブラリ。
ポイント
- 以下コマンドでモデルを生成し、DBに反映できる
rails generate model User name:string email:string
rails db:migrate
- Active Recordを使うことで、RailsからDBの詳細を隠蔽し、切り離すことができる。
- SQLについて学ばずにDBに対してCRUD操作を行える。
6.2 ユーザーを検証する
バリデーションの実装方法についての章。
ポイント
- テストクラスのsetupメソッド内に書かれた処理は、各テストが走る直前に実行される
- modelの検証でエラーになった内容は
user.errors.full_messages
で確認できる- %w[]で文字列の配列を作れる e.g.
%w[foo bar baz] => ["foo", "bar", "baz"]
- callbackメソッド => ある特定の時点で呼び出されるメソッド
6.3 セキュアなパスワードを追加する
パスワードのハッシュ化保存の実装
ポイント
has_secure_passwordについて
- Railsでは
has_secure_password
を記載するだけでパスワードのハッシュ化保存を実装できる(実際は別のライブラリを使うことが多いらしい)has_secure_password
をモデルに記載するだけで実装できるが、使用するには以下2点を満たす必要がある
- モデルに
password_digest:string
を追加する必要がある- 'bcrypt'gemが必要なので、Gemfileに追記してbundle installしておく
has_secure_password
を追加すると、authenticate
メソッドが使えるようになるマイグレーション(カラムの追加)
rails generate migration add_password_digest_to_users password_digest:string
rails generate migration
で指定するマイグレーション名の最後に_to_users
を付与しておくと、usersテーブルへのカラム追加だと判断してファイルを作成してくれる。基本文法
- 多重代入 (Multiple Assignment)
- 次のような文法で2箇所に同時に同じ値を代入できる
@user.password = @user.password_confirmation = "a" * 5
- モデルのvalidateの文法
validates :name, presence: true, length: { maximum: 50 }
- 投稿日:2020-01-12T14:52:34+09:00
ヒアドキュメント【Ruby】
今回はRuby技術者認定試験で意外と狙われるヒアドキュメントについて書きます。
そもそもヒアドキュメントとは?
ヒアドキュメントは改行コードを含めて複数行の文字列を書くことができます。
ヒアドキュメントを使わない場合。
puts "ヒアドキュメントを使わない場合はこのようにクオーテーションで囲い、\n改行したい箇所に\\nを書くことで改行できる。\nちなみにバックスラッシュを二つ使う\\ことで改行(\\n)をエスケープできる"【実行結果】
ヒアドキュメントを使わない場合はこのようにクオーテーションで囲い、 改行したい箇所に\nを書くことで改行できる。 ちなみにバックスラッシュを二つ使う\\ことで改行(\n)をエスケープできる => nilただしヒアドキュメントを使うと
here.rbdoq = <<EOS 昨日は雨 今日は晴れ 明日は曇りらしい EOS puts doq【実行結果】
昨日は雨 今日は晴れ 明日は曇りらしい上記のように\nを書かずに改行の文字列を記載することができます。
ちなみに習慣で識別子にEOSとEOLがよく使われるそうです。
意味はEOSは(end of string)、EOL(end of line)の略です。
他の文字を使っても大丈夫です。
というより、識別子に指定する文字列はヒアドキュメントの内容が理解できるものの方が良いそうです。
here.rbdoq = <<WEATHER 昨日は雨 今日は晴れ 明日は曇りらしい WEATHER puts doq p doq【実行結果】
昨日は雨 今日は晴れ 明日は曇りらしい "昨日は雨\n\n今日は晴れ\n\n明日は曇りらしい\n"<<-
<<-はスペースを書く場合やメソッドチェーンにするときに使います。
here.rbdoq = <<-WEATHER.lines #linesは各行を配列にするstringsのメソッド 昨日は雨 今日は晴れ 明日は曇りらしい WEATHER昨日は雨 今日は晴れ 明日は曇りらしい [" 昨日は雨\n", "\n", " 今日は晴れ\n", "\n", "明日は曇りらしい\n"]""
ダブルクォートを使うことで式展開ができる
here.rba = '雨' b = '晴れ' c = '曇り' doq = <<"WEATHER" 昨日は#{a} 今日は#{b} 明日は#{c}らしい WEATHER puts doq p doq【実行結果】
昨日は雨 今日は晴れ 明日は曇りらしい "昨日は雨\n\n今日は晴れ\n\n明日は曇りらしい\n"エラー
ちなみに下記はエラーになる
here.rbdoq = <<WEATHER 昨日は雨 今日は晴れ 明日は曇りらしい WEATHER puts doq p doq【実行結果】
here.rb:4: can't find string "WEATHER" anywhere before EOF原因は終端のWEATHERにスペースがあるから。
これを解決するにはスペースをなくすか、<<-にする
here.rbdoq = <<-WEATHER 昨日は雨 今日は晴れ 明日は曇りらしい WEATHER puts doq p doq【実行結果】
昨日は雨 今日は晴れ 明日は曇りらしい "昨日は雨\n\n今日は晴れ\n\n明日は曇りらしい\n"以上がヒアドキュメントでした。
参考
今回は参考図書にRuby技術者認定試験合格教本(Silver/Gold対応)Ruby公式資格教科書を使用しました。
- 投稿日:2020-01-12T14:22:56+09:00
resourcesを多段階ネストさせた時のform_forメソッド
概要
resourcesを多段階ネストさせた時のform_forメソッドの使用方法に関する記事があまりなかったので、備忘録として纏めます。
スポット(Post)ごとに写真(Image)が投稿でき、写真ごとにコメント(Comment)が投稿できる旅行カタログアプリケーションを作成しております。
ビューはHamlで書いております。
作成途中のため、細かいところはご容赦くださいませ。
ルーティング
routes.rbresources :posts, only: [:index, :new, :create, :edit, :update] do resources :images, only: [:index, :new, :create, :show] do resources :comments, only: [:index, :create] end endコントローラ
1つ目のネストのimageは下記の通り。
images_controller.rbclass ImagesController < ApplicationController before_action :set_post def index @images = @post.images.includes(:user) end def new @image = Image.new end def create @image = @post.images.new(image_params) if @image.save redirect_to post_images_path(@post) else @images = @post.images.includes(:user) render :index end end private def image_params params.require(:image).permit(:text, :image).merge(user_id: current_user.id) end def set_post @post = Post.find(params[:post_id]) end end2つ目のネストのcommentは下記となる。
PostのIDを定義する必要があることに気付くのに時間がかかりました・・・comments_controller.rbclass CommentsController < ApplicationController before_action :set_image def index @post = Post.find(params[:post_id]) @comment = Comment.new @comments = @image.comments.includes(:user) end def create @post = Post.find(params[:post_id]) @comment = @image.comments.new(comment_params) if @comment.save redirect_to post_image_comments_path(@post, @image) else @comments = @image.comments.includes(:user) render :index end end private def comment_params params.require(:comment).permit(:content).merge(user_id: current_user.id) end def set_image @image = Image.find(params[:image_id]) end endモデル
image.rbclass Image < ApplicationRecord belongs_to :post belongs_to :user has_many :comments validates :image, presence: true endcomment.rbclass Comment < ApplicationRecord belongs_to :post belongs_to :user validates :content, presence: true endビュー
new-image.html.haml.new-image__form = form_for [@post, @image] do |f| = f.label :image, class: 'form__image' do = icon('fas', 'image', class: 'icon') = f.file_field :image, class: 'hidden' .new-image__form__mask = f.text_field :text, class: 'form__text', placeholder: 'type a caption' = f.submit 'Send', class: 'form__submit'new-comment.html.haml.comments__content__list__form = form_for [@post, @image, @comment ] do |f| .comments__content__list__form__new-comment .comments__content__list__form__new-comment__input-box = f.text_field :content, class: 'new-comment__input-box__text', placeholder: 'コメントを追加...', inputtype: "text", style:"border:none" = f.submit 'Send', class: 'new-comment__submit-btn'2段ネストした場合のform_forの引数は、親、子、孫の関係になるため、post, image, commentの3つを渡しています。
以上です
間違いあればご指摘お願いいたします!
- 投稿日:2020-01-12T14:20:37+09:00
RSpecで言語非依存の壊れにくいバリデーションテストを作る
はじめに
RSpecでバリデーションのテストを書くとき、下記のように書くことが多いと思います。
spec/models/user_spec.rbit "is invalid without a name" do user = User.new(name: nil) user.valid? expect(user.errors[:name]).to include("can't be blank") endかのEverydayRailsにも同様のサンプルが掲載されています。
しかし、I18nを導入してエラーメッセージなどを日本語化したときにこのテストは通過しなくなってしまいます。
そこで、次の様に修正します。spec/models/user_spec.rbit "is invalid without a name" do user = User.new(name: nil) user.valid? expect(user.errors[:name]).to include("を入力してください") endこれでテストを通過することが出来ました。
しかしこれでは言語設定を変更するたびにテストが壊れて修正が必要になってしまうという問題が残ります。
解決方法
I18nの導入
ここでは記述しません。
[初学者]Railsのi18nによる日本語化対応などが参考になると思います。I18n.translate メソッドを使用する
Railsガイド-基本的な参照、スコープ、ネストしたキーの項によると、次のようにしてエラーメッセージを呼び出すことが出来ます。
I18n.translate "activerecord.errors.messages.record_invalid"
アプリケーションのlocaleを日本に指定している状態で、
rails c
で実行すると>> I18n.translate "activerecord.errors.messages.record_invalid" => "バリデーションに失敗しました: %{errors}"この様になります。
冒頭のバリデーションテストをこの方法で書き換えると次のようになります。spec/models/user_spec.rbit "is invalid without a name" do user = User.new(name: nil) user.valid? expect(user.errors[:name]).to include(I18n.t('errors.messages.blank')) end上記では
I18n.translate
のエイリアスであるI18n.t
を代わりに使用しています。
これで、言語設定が変わっても壊れないバリデーションテストを書くことができました。エラーメッセージの呼び出し方色々
先程も参照したRailsガイド-基本的な参照、スコープ、ネストしたキーの項によると、下記の呼び出しはすべて等価ですので、好みの方法を使って書いてみてください。
I18n.t 'activerecord.errors.messages.record_invalid'
I18n.t 'errors.messages.record_invalid', scope: :activerecord
I18n.t :record_invalid, scope: 'activerecord.errors.messages'
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]他のエラーメッセージを呼び出すには
Railsガイド-エラーメッセージ内での式展開の項に表でまとめられています。
I18n.translate "activerecord.errors.messages.record_invalid"
のrecord_invalid
の部分を表のメッセージ列の値に置き換えることで他のエラーメッセージを呼び出すことが出来ます引数付きのエラーメッセージ呼び出し
rails c
でI18n.t('errors.messages.too_short')
を実行すると>> I18n.t('errors.messages.too_short') => "は%{count}文字以上で入力してください"このような式展開が使われていることから引数に
count: [最低文字数]
を付加すれば良いと分かります。>> I18n.t('errors.messages.too_short', count: 6) => "は6文字以上で入力してください"以上からパスワード作成時などに6文字以上のバリデーションを設定しているときは、次のようにテストすることが出来ます。
spec/models/user_spec.rbit "is invalid without a shoot password" do user = User.new(password: 'a' * 5) user.valid? expect(user.errors[:password]).to include(I18n.t('errors.messages.too_short', count: 6)) endRailsガイド-エラーメッセージ内での式展開の項の表の式展開列に使用されている式展開が書かれているのでご参照ください。
おわりに
I18nで日本語化した時に、バリデーションテストでエラーメッセージをベタ書きしてチェックする方法以外のやり方を調べましたが、ピンポイントで書かれている記事がなかったため書いてみました。
同じ疑問を持った誰かのお役に立てればと思います。参考
- 投稿日:2020-01-12T13:04:38+09:00
Rails超基本コマンドチートシート(モデル/ビュー/コントローラーの追加)
Ruby/Rails エンジニアとして働き始め2ヶ月ほど立ちますが、まだ Rails のコマンドを業務では使ったことがありませんのでほとんど忘れてしまっています。就職前に学習したので調べれば思い出せるとはいえ、この程度のコマンドならば、業務で必要になった時にわざわざ1つずつ調べるのは時間がかかりすぎます。
そこで、必要な時にサクッと思い出せるよう、チートシートとしてにまとめます。前提
各コマンドの意味や内容は割愛しますので、詳細は、 Rails ガイド(https://railsguides.jp/) などをお読みください。
モデル関連
モデル生成
モデルとマイグレーション生成する
$ rails generate model Name column_name:column_type例
$ rails generate model User name:string number:integerマイグレーション実行
マイグレーションを実行する
$ rails db:migrateカラム追加
$ rails generate migration AddColumunNameToModelName例
$ rails generate migration AddAccountIdToUsersコントローラー・ビュー関連
コントローラー生成
コントローラーとビューが生成する
$ rails generate controller Name action例
$ rails generate controllser Users index show new editStrongParameters
モデルのインスタンスにどのパラメーターを保存してよいかを指定する
1つのパラーメーターの場合
例
def create User.create(params[:name]) end複数のパラメーターの場合
例
def create User.create(user_params) end private def user_params params.permit(:name, :number) endルーティング関連
テーブル名とアクション名を指定する
resource :table_name, only:[:action]
例
resources :users, only: [:index, :show, :new, :create, :edit, :update, :destroy]
まとめ
その他にも多用するコマンドがあれば、随時追加していきます
- 投稿日:2020-01-12T11:49:22+09:00
はじめてAWSでデプロイする方法⑥(EC2サーバーにAppをクローンしてアップロード)
前回までの記事
はじめてAWSでデプロイする方法①(インスタンスの作成)
はじめてAWSでデプロイする方法②(Elastic IPの作成と紐付け)
はじめてAWSでデプロイする方法③(AWSセキュリティグループの設定)
はじめてAWSでデプロイする方法④(EC2インスンタンスにSSHログイン)
はじめてAWSでデプロイする方法⑤(EC2の環境構築、Ruby, MySQL)EC2インスタンス(サーバー)を作成し、パブリックIPをElastic IPで固定。
一般ユーザーがアクセスできるように、セキュリティグループの設定を追加(入り口を作成)
IDとPWを使って、EC2にログインして、環境構築をしました。ざっくり説明すると、こんなところです。
今回はWEB AppをEC2インスタンスにアップロードしていきます。
WEB AppをEC2にクローンする
現段階
EC2サーバにアプリケーションのコードをクローンしようとしてもpermission deniedとエラーが出てしまいます。
原因
Githubから見てこの許可していないEC2インスタンスを拒否する
対策
EC2インスタンスのSSH公開鍵をGithubに登録する。
SSH鍵をGithubに登録すると、Githubはそれを認証してクローンを許可をだす
作業
EC2サーバのSSH鍵ペアを作成
- EC2にログイン
- キーペア作成のためコマンドを入力
[ec2-user@ip-172-31-23-189 ~]$ ssh-keygen -t rsa -b 40963.下記が表示されるので、エンターを押す
Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa):4.さらにエンターを押す(2回)
Enter passphrase (empty for no passphrase): Enter same passphrase again:これで下記の表示ができれば、成功してます。
Your identification has been saved in /home/ec2-user/.ssh/id_rsa. Your public key has been saved in /home/ec2-user/.ssh/id_rsa.pub. The key fingerprint is: 3a:8c:1d:d1:a9:22:c7:6e:6b:43:22:31:0f:ca:63:fa ec2-user@ip-172-31-23-189 The key's randomart image is: +--[ RSA 4096]----+ | + | | . . = | | = . o . | | * o . o | |= * S | |.* + . | | * + | | .E+ . | | .o | +-----------------+5.SSH公開鍵を表示し、値をコピーするため、下記コマンドを実装
[ec2-user@ip-172-31-23-189 ~]$ cat ~/.ssh/id_rsa.pubコピーした公開鍵をGithubにアクセスして登録する
2. 画面右上の緑色の『 NEW SSH KEY 』をクリック
3. タイトルを記入する(なんでも可能)
4. 公開鍵(ssh-rsaから)を貼り付け
エラー「Key is invalid. You must supply a key in OpenSSH public key format」が表示された場合、
貼り付けたコードに『 ssh-rsa 』が含まれているかご確認ください
5. 『 Add SSH KEY 』をクリックして保存。
6. GithubのPWを入力
7. 完了
8. 登録できているか確認[ec2-user@ip-172-31-23-189 ~]$ ssh -T git@github.com下記の表示が出た場合: 『 yes 』を選択
The authenticity of host 'github.com (IP ADDRESS)' can't be established. RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48. Are you sure you want to continue connecting (yes/no)?この際に
Warning: Permanently added the RSA host key for IP address '52.111.11.11' to the list of known hosts.と表示された場合は, EC2に入り直しましょう。更新されたのでエラーなく入れます。
成功すると、下記の表示になるはずです。または、下記が表示された場合: 『 yes 』を選択
The authenticity of host 'github.com (IP ADDRESS)' can't be established. RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8. Are you sure you want to continue connecting (yes/no)?成功すると下記の表示が出る
[ec2-user@ip-172-31-23-189 ~]$ ssh -T git@github.com Hi <Githubユーザー名>! You've successfully authenticated, but GitHub does not provide shell access.App側でUnicornのインストール
EC2にGit クローンする前に、準備としてUnicornをインストールさせましょう
Gemfileにgem'unicorn'を追加Gemfile.group :production do gem 'unicorn', '5.4.1' endbundle installでインストール
$ bundle installconfig/unicorn.rbを作成
追加したunicorn.rbに下記を記述unicorn.rbapp_path = File.expand_path('../../', __FILE__) #アプリケーションサーバの性能を決定する worker_processes 1 #アプリケーションの設置されているディレクトリを指定 working_directory app_path #Unicornの起動に必要なファイルの設置場所を指定 pid "#{app_path}/tmp/pids/unicorn.pid" #ポート番号を指定 listen 3000 #エラーのログを記録するファイルを指定 stderr_path "#{app_path}/log/unicorn.stderr.log" #通常のログを記録するファイルを指定 stdout_path "#{app_path}/log/unicorn.stdout.log" #Railsアプリケーションの応答を待つ上限時間を設定 timeout 60 #以下は応用的な設定なので説明は割愛 preload_app true GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true check_client_connection false run_once = true before_fork do |server, worker| defined?(ActiveRecord::Base) && ActiveRecord::Base.connection.disconnect! if run_once run_once = false # prevent from firing again end old_pid = "#{server.config[:pid]}.oldbin" if File.exist?(old_pid) && server.pid != old_pid begin sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU Process.kill(sig, File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH => e logger.error e end end end after_fork do |_server, _worker| defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection endproduction.rbを開き、下記の記述をコメントアウトする
config/environments/production.rbconfig.assets.js_compressor = :uglifierconfig/environments/production.rb#config.assets.js_compressor = :uglifier
アプリケーションの保存先となるディレクトリを作成
ディレクトリの作成
#/var/wwwディレクトリを作成(後述するCapistranoの初期値がwwwなので、ディレクトリをwwwに設定しています) [ec2-user@ip-172-31-23-189 ~]$ sudo mkdir /var/www/作成したディレクトリをchownコマンドで権限設定
#作成したwwwディレクトリの権限をec2-userに変更 [ec2-user@ip-172-31-23-189 ~]$ sudo chown ec2-user /var/www/作成したディレクトリに移行
[ec2-user@ip-172-31-23-189 ~]$ cd /var/www/git clone でAppをEC2にダウンロード
GithubからGit cloneするためのリポジトリURLを取得
git clone で作成したディレクトリにappをクローン
[ec2-user@ip-172-31-23-189 www]$ git clone リポジトリURLGithubのアカウント名とPWを入力し、
ダウロードが開始されるremote: Enumerating objects: 298, done. remote: Counting objects: 100% (298/298), done. remote: Compressing objects: 100% (190/190), done. remote: Total 298 (delta 109), reused 274 (delta 86), pack-reused 0 Receiving objects: 100% (298/298), 58.53 KiB | 365.00 KiB/s, done. Resolving deltas: 100% (109/109), done.完了
これで、EC2にAppがクローンされています。
次回はEC2にgemをインストールと設定の変更
- 投稿日:2020-01-12T10:35:09+09:00
#Rspec + #Ruby でhelper メソッドを定義して共通化する
Globalなメソッドではなくちゃんと describe / context の中に作用を閉じ込めてくれるみたいだ
# https://relishapp.com/rspec/rspec-core/v/3-8/docs/helper-methods/arbitrary-helper-methods # # Arbitrary helper methods # # You can define methods in any example group using Ruby's def keyword or # define_method method. These helper methods are exposed to examples in the # group in which they are defined and groups nested within that group, but not # parent or sibling groups. # PASS RSpec.describe "flat define method" do def help :available end it do expect(help).to be(:available) end end # PASS RSpec.describe "define method and use in child describe" do def help :available end describe do it do expect(help).to be(:available) end end end # ERROR RSpec.describe "define method chuld describe and use it in parent describe" do describe do def help :available end end # NameError: # undefined local variable or method `help' for #<RSpec::ExampleGroups::AnExample_3:0x00007f95d5a6c9e0> it do expect(help).to be(:available) end end # ERROR RSpec.describe "not defined method in describe" do # has access to methods defined in its group (FAILED - 1) it do expect(help).to be(:available) end end # PASS # OTHER USAGE RSpec.describe "expectation in helper" do def expect_true_is_true expect(true).to eq true end it do expect_true_is_true end end # $ rspec /Users/yumainaura/.ghq/github.com/YumaInaura/YumaInaura/rspec/def-helper-method.rb # flat define method # is expected to equal :available # define method and use in child describe # is expected to equal :available # define method chuld describe and use it in parent describe # example at /Users/yumainaura/.ghq/github.com/YumaInaura/YumaInaura/rspec/def-helper-method.rb:44 (FAILED - 1) # not defined method in describe # example at /Users/yumainaura/.ghq/github.com/YumaInaura/YumaInaura/rspec/def-helper-method.rb:52 (FAILED - 2) # expectation in helper # is expected to eq true # Failures: # 1) define method chuld describe and use it in parent describe # Failure/Error: expect(help).to be(:available) # NameError: # undefined local variable or method `help' for #<RSpec::ExampleGroups::DefineMethodChuldDescribeAndUseItInParentDescribe:0x00007fd91e12d230> # # /Users/yumainaura/.ghq/github.com/YumaInaura/YumaInaura/rspec/def-helper-method.rb:45:in `block (2 levels) in <top (required)>' # 2) not defined method in describe # Failure/Error: expect(help).to be(:available) # NameError: # undefined local variable or method `help' for #<RSpec::ExampleGroups::NotDefinedMethodInDescribe:0x00007fd91e17e518> # # /Users/yumainaura/.ghq/github.com/YumaInaura/YumaInaura/rspec/def-helper-method.rb:53:in `block (2 levels) in <top (required)>' # Finished in 0.00882 seconds (files took 0.16091 seconds to load) # 5 examples, 2 failures # Failed examples: # rspec /Users/yumainaura/.ghq/github.com/YumaInaura/YumaInaura/rspec/def-helper-method.rb:44 # define method chuld describe and use it in parent describe # rspec /Users/yumainaura/.ghq/github.com/YumaInaura/YumaInaura/rspec/def-helper-method.rb:52 # not defined method in describeOriginal by Github issue
- 投稿日:2020-01-12T08:53:16+09:00
Rspec の expect と with で ネストの深いハッシュや引数をゆるくテストする / #Ruby #Rspec #Rails
Complexed hash fuzzy match
example 'complexed match' do expect(SomeClass).to receive(:call).with( 'X', hash_including( y1: 'Y1', y2: (be_a String), y4: hash_including( y4_1: array_including(1, 3), y4_3: contain_exactly(9, 7, 8), ) ), any_args ) subject endExample
# Doc # https://relishapp.com/rspec/rspec-mocks/v/3-2/docs/setting-constraints/matching-arguments class SomeClass def self.call(x, y, z) end end describe 'simple case' do subject do SomeClass.call('X','Y','Z') end it 'calls with exactly multiple args' do expect(SomeClass).to receive(:call).with('X', 'Y', 'Z') subject end end describe 'complexed case' do subject do SomeClass.call( 'X', { y1: 'Y1', y2: 'Y2', y3: 'Y3', }, 'Z' ) end example 'exactly match' do expect(SomeClass).to receive(:call).with( 'X', { y1: 'Y1', y2: 'Y2', y3: 'Y3', }, 'Z' ) subject end it 'partly match with hash in one arg' do expect(SomeClass).to receive(:call).with( 'X', hash_including( y1: 'Y1', y2: 'Y2', ), 'Z' ) subject end end describe 'random value case' do subject do SomeClass.call( 'X', { y1: 'Y1', y2: rand(999_999).to_s, y3: rand(999_999), }, 'Z' ) end example 'fuzzy match on one arg' do expect(SomeClass).to receive(:call).with( 'X', any_args, 'Z' ) subject end example 'exactly match and expect anything value' do expect(SomeClass).to receive(:call).with( 'X', { y1: anything, y2: anything, y3: anything, }, 'Z' ) subject end example 'exactly match and expect anything value' do expect(SomeClass).to receive(:call).with( 'X', { y1: (be_a String), y2: (be_a String), y3: (be_a Integer), }, 'Z' ) subject end example 'partly fuzzy match' do expect(SomeClass).to receive(:call).with( 'X', hash_including( y1: 'Y1', y2: (be_a String), ), any_args ) subject end end describe 'deep conplexed case' do subject do SomeClass.call( 'X', { y1: 'Y1', y2: rand(999_999).to_s, y3: rand(999_999), y4: { y4_1: [1,2,3], y4_2: [4,5,6], y4_3: [7,8,9], } }, 'Z' ) end example 'complexed match' do expect(SomeClass).to receive(:call).with( 'X', hash_including( y1: 'Y1', y2: (be_a String), y4: hash_including( y4_1: array_including(1, 3), y4_3: contain_exactly(9, 7, 8), ) ), any_args ) subject end end # $ rspec -fd /Users/yumainaura/.ghq/github.com/YumaInaura/YumaInaura/rspec/with.rb # simple case # calls with exactly multiple args # complexed case # exactly match # partly match with hash in one arg # random value case # fuzzy match on one arg # exactly match and expect anything value # exactly match and expect anything value # partly fuzzy match # more conplexed case # complexed match # Finished in 0.01357 seconds (files took 0.15283 seconds to load) # 8 examples, 0 failuresRef
Matching arguments - Setting constraints - RSpec Mocks - RSpec - Relish
https://relishapp.com/rspec/rspec-mocks/v/3-2/docs/setting-constraints/matching-arguments#basic-exampleRef
使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」 - Qiita
https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5Original by Github issue
- 投稿日:2020-01-12T08:26:06+09:00
#Rspec + #Ruby / Complexed Nested Hash arguments / Fuzzy match / Use expect receive syntax and "with" matcher
Complexed hash fuzzy match
example 'complexed match' do expect(SomeClass).to receive(:call).with( 'X', hash_including( y1: 'Y1', y2: (be_a String), y4: hash_including( y4_1: array_including(1, 3), y4_3: contain_exactly(9, 7, 8), ) ), any_args ) subject endExample
# Doc # https://relishapp.com/rspec/rspec-mocks/v/3-2/docs/setting-constraints/matching-arguments class SomeClass def self.call(x, y, z) end end describe 'simple case' do subject do SomeClass.call('X','Y','Z') end it 'calls with exactly multiple args' do expect(SomeClass).to receive(:call).with('X', 'Y', 'Z') subject end end describe 'complexed case' do subject do SomeClass.call( 'X', { y1: 'Y1', y2: 'Y2', y3: 'Y3', }, 'Z' ) end example 'exactly match' do expect(SomeClass).to receive(:call).with( 'X', { y1: 'Y1', y2: 'Y2', y3: 'Y3', }, 'Z' ) subject end it 'partly match with hash in one arg' do expect(SomeClass).to receive(:call).with( 'X', hash_including( y1: 'Y1', y2: 'Y2', ), 'Z' ) subject end end describe 'random value case' do subject do SomeClass.call( 'X', { y1: 'Y1', y2: rand(999_999).to_s, y3: rand(999_999), }, 'Z' ) end example 'fuzzy match on one arg' do expect(SomeClass).to receive(:call).with( 'X', any_args, 'Z' ) subject end example 'exactly match and expect anything value' do expect(SomeClass).to receive(:call).with( 'X', { y1: anything, y2: anything, y3: anything, }, 'Z' ) subject end example 'exactly match and expect anything value' do expect(SomeClass).to receive(:call).with( 'X', { y1: (be_a String), y2: (be_a String), y3: (be_a Integer), }, 'Z' ) subject end example 'partly fuzzy match' do expect(SomeClass).to receive(:call).with( 'X', hash_including( y1: 'Y1', y2: (be_a String), ), any_args ) subject end end describe 'deep conplexed case' do subject do SomeClass.call( 'X', { y1: 'Y1', y2: rand(999_999).to_s, y3: rand(999_999), y4: { y4_1: [1,2,3], y4_2: [4,5,6], y4_3: [7,8,9], } }, 'Z' ) end example 'complexed match' do expect(SomeClass).to receive(:call).with( 'X', hash_including( y1: 'Y1', y2: (be_a String), y4: hash_including( y4_1: array_including(1, 3), y4_3: contain_exactly(9, 7, 8), ) ), any_args ) subject end end # $ rspec /Users/yumainaura/.ghq/github.com/YumaInaura/rspec/with.rb # simple case # calls with exactly multiple args # complexed case # exactly match # partly match with hash in one arg # random value case # fuzzy match on one arg # exactly match and expect anything value # exactly match and expect anything value # partly fuzzy match # Finished in 0.01604 seconds (files took 0.15593 seconds to load) # 7 examples, 0 failuresRef
Matching arguments - Setting constraints - RSpec Mocks - RSpec - Relish
https://relishapp.com/rspec/rspec-mocks/v/3-2/docs/setting-constraints/matching-arguments#basic-exampleOriginal by Github issue
- 投稿日:2020-01-12T00:42:21+09:00
libssl.1.0.0.dylibがないことでエラーになった時の対処法 dyld: lazy symbol binding failed: Symbol not found: _SSL_library_init
Railsを久しぶりに起動しようとしたらなんかエラーが起きていた。
masashi-no-mbp:MarimoKing pcuser$ rails s dyld: lazy symbol binding failed: Symbol not found: _SSL_library_init Referenced from: /Users/pcuser/.rbenv/versions/2.6.0/lib/ruby/2.6.0/x86_64-darwin18/openssl.bundle Expected in: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib結果としては、rbenvで入れていたRubyのリンクの設定が原因でダメだったみたい。
対処法
Rubyを入れ直すことで解消した。
$ rbenv uninstall 2.6.0 && rbenv install 2.6.0
参考リンク
[MEMO] gem コマンドで Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib が出たときの対処法
- 投稿日:2020-01-12T00:38:03+09:00
Procのススメ(番外編)
はじめに
Ruby Advent Calendar 2020 の13日目の記事です。
昨日は、@jerrywdlee さんの Rubyでロックファイルによる簡易的排他制御 でした。
昨年の11/14に開催された平成Ruby会議で、「Procのススメ」というテーマで登壇したんですが、その番外編を1年以上経った 今更 、Advent Calendarという機会に便乗して書きます。
当初発表内容に盛り込もうと思っていたけど、時間の都合で省いたトピックについて記事にまとめていきます。※1. 知識はあるけど自分でもなかなか利用場面に恵まれないテクニックが結構あることは内緒
※2. 時間の都合上、一部に関しては「使ってみる」 の部分は用意できませんでした![]()
1. Enumerator::Yielder#to_proc
発表当時の構想にはなかったんですが、次の「独自に
to_proc
を定義」の部分の都合上、Ruby2.7 がリリースされた現在だと先に説明しておいた方が良さそうなので最初に説明しておきます。Enumerator::Yielder#to_procとは
Enumearator::Yielder#to_proc
は Ruby2.7 から追加されたメソッドです。
Enumearator::Yielder
自体がかなりマニアックなので、るりまのサンプルコードを見ながら説明していきます。text = <<-END Hello こんにちは END enum = Enumerator.new do |y| text.each_line(&y) end enum.each do |line| p line end # => "Hello\n" # "こんにちは\n"上記のコードの
new
に渡しているブロックのブロック引数で渡ってくるのがEnumerator::Yielder
のインスタンスです。enum = Enumerator.new do |y| # <= こいつが `Enumerator::Yielder` text.each_line(&y) endこの部分を
&修飾子
とto_proc
を利用しない等価なコードを書くと以下の様になります。enum = Enumerator.new do |y| text.each_line { |line| y.yield line } # <= y.yield でブロックを評価する際に line をブロック引数として渡す endまた、
Enumerator
がeach
でイテレーション(繰り返し処理)を実行する際に渡されるブロックがEnumerator::Yielder
としてブロック引数で渡ってきます。enum.each do |line| # <= このブロックの部分が `Enumerator::Yielder` として渡ってくる p line endつまり、要素をブロック引数にそのまま引き渡すのが
Enumerator::Yielder#to_proc
です。2. 独自に to_proc を定義
&修飾子と to_proc
平成Ruby会議の際のスライドにある通り、実引数の最後で
&修飾子
を使うとその実引数に対してto_proc
が呼ばれます。Procのススメ/recommendation-of-proc - Speaker Deck
デフォルトで
to_proc
が定義されているのはスライドに記載しているProc(lambda)
・Method
・Symbol
・Hash
、それに Ruby2.7 からは先程紹介したEnumerator::Yielder
が加わります。
それ以外は普通は例外が発生します。[1, 2, 3].map(&"to_proc") # => TypeError (wrong argument type String (expected Proc))ただ、以下の条件を満たせば他のクラスでも &修飾子 が利用できます。
to_proc
が定義されているto_proc
がProc(lambda)
を返すclass String def to_proc -> (n) { "#{self.upcase}#{n}" } end end [1, 2, 3].map(&"to_proc") # => ["TO_PROC1", "TO_PROC2", "TO_PROC3"]使ってみる
to_proc
を実装するテクニックを使って簡単なロガーを作ってみます。
出力対象は以下のコードです。(出力対象の設計はめちゃくちゃ適当なのでご容赦を)
module Readable;end module Writable;end class User @@role = 'general' include Readable def initialize(name) @name = name end def self.all;end def name;end end class Admin < User @@role = 'admin' ADDRESS = 'Tokyo' include Writable def manage;end endロガーの実装は以下の通り。
デバッグ情報を色々ぶち込んだハッシュをpp
で出力します。
また、今回はインスタンスではなくLogger
クラスを直接渡す実装にしてみました。
&修飾子
をつける実引数がto_proc
を実装していればいいので、to_proc
を特異メソッドにすれば OK です。class Logger def self.to_proc -> (obj) do klass = obj.class pp( { inspect: obj.inspect, class: klass.name, ancestors: klass.ancestors, class_variables: klass.class_variables, constants: klass.constants, included_modules: klass.included_modules, singleton_methods: klass.singleton_methods(false), instance_methods: klass.instance_methods(false) } ) end end endあとは
each
に&修飾子
をつけたLogger
を渡してやれば OK です。[Admin.new("Alice"), User.new("Bob")].each(&Logger) # => {:inspect=>"#<Admin:0x00007ff6c8892bc0 @name=\"Alice\">", # :class=>"Admin", # :ancestors=>[Admin, Writable, User, Readable, Object, Kernel, BasicObject], # :class_variables=>[:@@role], # :constants=>[:ADDRESS], # :included_modules=>[Writable, Readable, Kernel], # :singleton_methods=>[], # :instance_methods=>[:manage]} # {:inspect=>"#<User:0x00007ff6c8892b70 @name=\"Bob\">", # :class=>"User", # :ancestors=>[User, Readable, Object, PP::ObjectMixin, Kernel, BasicObject], # :class_variables=>[:@@role], # :constants=>[], # :included_modules=>[Readable, PP::ObjectMixin, Kernel], # :singleton_methods=>[:all], # :instance_methods=>[:name]}
.irbrc
とか.pryrc
にこんな感じの便利クラスを用意しておくと便利(かも?)3. 関数合成(Proc#>>, Proc#<<, Method#>>, Method#<<)
関数合成について
例として、別々の処理をする
lambda
を返すメソッドが3つあり、それを配列に対して適用していきたいケースを考えます。def convert_integer -> (n) { n.to_i } end def count_up -> (n) { n.succ } end def output -> (n) { p n } end愚直に
each
で実装すると次のようになります。%w[1 2 3].each do |n| output.call(count_up.call(convert_integer.call(n))) endこれを関数合成を使うと以下のように書けます。
%w[1 2 3].each(&convert_integer >> count_up >> output)配列の要素に対してどのような処理が適用されていくのかが左から右に流れるように読めて読みやすくなりました。
![]()
ちなみに<<
もあるので逆方向の関数合成をすることもできます。%w[1 2 3].each(&output << count_up << convert_integer)使ってみる
先程は
lambda
を返すメソッドを定義していましたが、実際は以下のように定義されている事が多いかと思います。def convert_integer(n) n.to_i end def count_up(n) n.succ end def output(n) p n endまた、可読性重視のためにループごとの処理を少なくし、以下のように書くこともあると思います。
%w[1 2 3] .map { |n| convert_integer(n) } .map { |n| count_up(n) } .each { |n| output(n) }ただ、これだと配列の要素が多い場合にループ回数が3倍になってしまいます。
ここで関数合成が使えます。
今回はeach
で実行したい処理は全てメソッドなので、Method
クラスを利用します。
Method
にも>>
と<<
が実装されているので、関数合成ができます。
使い方・動きはProc#>>
とProc#<<
と同じです。%w[1 2 3].each(&method(:convert_integer) >> method(:count_up) >> method(:output))4. メソッドの引数にブロック(proc)を渡す
Proc(lambda)
はオブジェクトなので、引数として渡すことができます。
これをうまく使うと、メインとなる処理の前後に前処理や後処理を必要に応じて差し込むような柔軟な実装ができます。def hoge(before: nil, after: nil) before.call unless before.nil? yield if block_given? after.call unless after.nil? end # 上記と下記は等価なコード def hoge(before: nil, after: nil, &block) before.call unless before.nil? block.call unless block.nil? after.call unless after.nil? end実行側は下記のようになります。
hoge( before: -> { p 1 }, after: -> { p 2 } ) { p 3 } # 1 # 3 # 2 # => 25. Enumerator::Lazy
Enumerator::Lazy とは
るりまを見ると下記のように書いてあります。
map や select などのメソッドの遅延評価版を提供するためのクラス。
動作は通常の Enumerator と同じですが、以下のメソッドが遅延評価を行う (つまり、配列ではなく Enumerator を返す) ように再定義されています。これはコードを見るとピンとくるかもです。
下記は単純に奇数のみの配列を返すコードです。[1, 2, 3].select(&:odd?) # => [1, 3]よく見かける
map
やselect
等のメソッドはEnumerator
クラスに定義されています。
これらのメソッドにブロック(またはProc
)を渡すと、渡したブロックを評価して配列を返します。
また、ブロックを渡さなかったときはEnumerator
クラスのインスタンスを返します。
普段は意識していないかもしれませんが、下記のようなコードを書くときにこの挙動を使っています。%w[a b c].map.with_index(1) { |char, n| "#{n}: #{char}" } # => ["1: a", "2: b", "3: c"]一方、
Enumerator::Lazy
を使う場合下記のようなコードになります。[1, 2, 3].select.lazy # => #<Enumerator::Lazy: #<Enumerator: [1, 2, 3]:select>>
Enumerator::Lazy
はforce
またはfirst
またはto_a
が呼ばれるまで値が確定せず、ループも回りません。def hoge(x) x.tap { p :hoge } end def fuga(x) x.tap { p :fuga } end # :hogeも:fugaも1度しか出力されない [1, 2, 3].lazy.map(&method(:hoge)).map(&method(:fuga)).take(1).to_a # :hoge # :fuga # => [1]この挙動を利用するとループ回数を減らせるのでパフォーマンス改善に利用できそうです。
ただ、残念ながら「lazy は基本的には遅くなる」そうです。(ruby-jp情報)
Enumerator::Lazy
は以下のようなものに対して利用するといいそうです。(これもruby-jp情報)
- 大きすぎて全体をメモリに乗せられないもの 例:数GBのファイル
- 終わりがわからないもの 例:ネットワーク越しにやってくるデータ
- 終わりがないもの 例: (
Date.today..
)最後に
書くと言っておきながら1年以上ずっと書けていなかった記事をようやく書けました。
今夜は赤飯です(なお実際はカレー
でした)
(
Proc#curry
について書いていればきれいにオチつけれたのにOTL)