20200907のRubyに関する記事は26件です。

【Rails】paranoia(gem)を使ってイベント終了機能(論理削除)を実装する

そもそも論理削除とは?

RailsのActiveRecordの削除(delete)は、通常、物理削除になっており、実際にデータがデータベースから削除されます。それに対して、論理削除は実際にはデータを削除せずに、削除されたと見なすフラッグと呼ばれるカラムを設定して、ユーザーには削除しているかのように振る舞い、必要時には元の状態に戻せるものです。「ユーザーが退会した後も、システム上一定期間はユーザーのデータを保持しなければならない。」や「違反等があり、一時的にアカウントを凍結したい」などの場合に、論理削除が使えると思います。
Railsではparanoiaというgemライブラリを利用することで簡単に実装することができます。このgemはacts_as_paranoidというgemを再実装したものです。

引用 (https://remonote.jp/rails-gem-paranoia)

分かりやすい記事があったので引用させて戴きました。通常は上述されている様に管理ユーザーが違反等があり、一時的にアカウントを凍結したいなどの場合に使われる機能みたいですが僕の場合はイベントの削除機能と終了機能を分けるために論理削除を実装しました。

どういうことかと申しますと、イベント参加アプリケーションを実装しているときにユーザーが過去に参加したイベントが見れたらいいよね。となりました。イベントが終われば当然、イベントのデータは削除されてイベント一覧では見れない様にするのですが僕が実装したかったのは一覧には表示されないけどユーザーのマイページで参加したイベントが見れるというもの。つまり表面的には削除されていながらレコードの情報は残っているということです。ですが、どの様に実装したらいいのか分かりませんでした。

最初は、イベントを削除したときに違うテーブルにそのイベントの情報を丸々移動出来ないかと模索していましたがなかなか情報が見つからないときに論理削除が簡単に実装できてしまうparanoiaというgemを見つけました。

前置きが長くなりましたが、早速導入~実装までの流れをまとめていきたいと思います。

step1: 導入

gem 'paranoia'

例の如くbundle install

続いて、カラムを追加します。

rails g migration AddDeletedAtToEvents deleted_at:datetime:index

イベントモデルにdeleted_atというカラムを追加します。

class AddDeletedAtToEvents < ActiveRecord::Migration[6.0]
  def change
    add_column :studies, :deleted_at, :datetime
    add_index :studies, :deleted_at
  end
end
rails db:migrate

最後に、対象モデルに acts_as_paranoid を追記します。

Event.rb
class Event < ApplicationRecord
  acts_as_paranoid
end

導入完了!!

step2: ルーティングを設定する

routes.rb
Rails.application.routes.draw do
  root "events#index"
  resources :events do
    member do
      delete 'finish'
    end
  end
end

resourcesで生成される七つのアクションの中に終了するアクションは無いのでイベントにネストする形でfinishアクションのルーティングを設定します。

step:3 コントローラーにfinishアクションを定義する

events_controller.rb
 def destroy
   @event = current_user.events.find(params[:id])
   @event.really_destroy!
   redirect_to root_path, notice: "イベントを削除しました"
 end

 def finish
   @event = current_user.events.find(params[:id])
   @event.destroy!
   redirect_to root_path, notice: "イベントを終了しました"
  end

上記のdestroyアクションとfinishアクションの違いは@eventの後に、本当にレコードから削除したい場合はreally_destroyと記述しています。
では、finishアクションでは普通にdestroyと記述していますが実際データーベースにどの様な形で残っているのでしょうか?

746eace97e67b286975d2e087964212e.png
destroyされたレコードは画像の様にdeleted_atというカラムに論理削除された時間が残ります。

step:4 論理削除されたデータをfindメソッドで取ってくる

users_controller.rb
  def show
     @user=User.find(params[:id])
     @events = @user.events.only_deleted
  end

ユーザーの詳細ページで過去のイベントを表示したいのでユーザーコントローラーにonly_deletedと記述することで論理削除されたレコードだけを取ってくることができます。

最後に、、

paranoiaは他にも色々なメソッドが使えるみたいなので是非、公式ドキュメントを参考にしてください^^
公式リンク(https://github.com/rubysherpas/paranoia)

最後まで見て頂きありがとうございました^^

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

windows10,Ruby2.6.6でrails serverコマンドを実行して「cannot load such file -- sqlite3/sqlite3_native」がでた

はじめに

@shuheyさんの記事の通りにやればほぼ解決します.
Ruby2.6.6でやったときにうまくいかなかったのでメモ程度に...

環境

windows10 Home
Ruby+Devkit 2.6.6-1 (x64)
Rails 5.1.6

直したはずなのにエラーが

Ruby on RailsチュートリアルRails Girls インストール・レシピ (Windows 用セットアップ(WSLが使えない方向け))の通りに環境構築をしてrails serverコマンドを実行したところ以下のようなエラーが出ました.(@shuheyさんの記事ではRuby26-x64 -> Ruby25-x642.6.0 -> 2.5.0に変わってます)

C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/sqlite3-1.3.13-x64-mingw32/lib/sqlite3.rb:6:in `require': cannot load such file -- sqlite3/sqlite3_native (LoadError)

ここで@shuheyさんの記事に倣ってC:\Ruby26-x64\lib\ruby\gems\2.6.0\gems\sqlite3-1.3.13-x64-mingw32\lib\sqlite32.5ディレクトリを新規作成し,その中にsqlite3_native.soをコピーしてもう一度rails serverコマンドを実行!

これで解決かと思いきやまだ同じエラーが...

解決方法

さっきのエラーメッセージのC:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/sqlite3-1.3.13-x64-mingw32/lib/sqlite3.rbを見てみる.すると3,4行目に

sqlite3.rb
  RUBY_VERSION =~ /(\d+\.\d+)/
  require "sqlite3/#{$1}/sqlite3_native"

なるほど.
2.5ディレクトリはRubyのバージョンのことだったのか!(なんで気づけなかった笑)

というわけで2.6ディレクトリを新規作成し,sqlite3_native.soをコピーしてrails serverコマンドを実行します.
やっとRailsアプリの起動に成功.

=> Booting Puma
=> Rails 5.1.6 application starting in development
=> Run `rails server -h` for more startup options
*** SIGUSR2 not implemented, signal based restart unavailable!
*** SIGUSR1 not implemented, signal based restart unavailable!
*** SIGHUP not implemented, signal based logs reopening unavailable!
Puma starting in single mode...
* Version 3.9.1 (ruby 2.6.6-p146), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

ブラウザでlocalhost:3000にアクセスして動作確認

キャプチャ.PNG

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

【Ruby on Rails】投稿したユーザーのみが編集できるようにする

目標

  • URLのベタ打ちでの他ユーザーの編集を防ぐ
  • view画面にそもそも表示させない

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

※ ▶◯◯ を選択すると、説明等が出てきますので、
  よくわからない場合の参考にしていただければと思います。

考え方

基本的には、
①if文でcurrent_userなどで絞るか、
②current_userに紐づくモデルを抽出するかで
投稿したユーザーのみが編集可能。
※viewでは前者を、controllerでは後者で実装。

controllerの編集

前提の状態での記述

app/controllers/posts_controller.rb
    def edit
        @post = Post.find(params[:id])
    end

    def update
    @post = Post.find(params[:id])
        if @post.update(post_params)
      redirect_to new_post_path
    else
      render :edit
    end
    end

    def destroy
        @posts = Post.all
        @post = Post.find(params[:id])
        @post.destroy
    end

このままではURLをベタ打ちすると編集、削除が可能に。
そこで下記のように記述。

app/controllers/posts_controller.rb
  def edit
    @post = Post.find(params[:id])
    unless @post.user == current_user
      redirect_to  new_post_path
    end
  end

  def update
    @post = Post.find(params[:id])
    if @post.user != current_user
      redirect_to  new_post_path
    else
      if @post.update(post_params)
        redirect_to new_post_path
      else
        render :edit
      end
    end
  end

  def destroy
    @posts = Post.all
    @post = Post.find(params[:id])
    if @post.user != current_user
      redirect_to  new_post_path
    else
      @post.destroy
    end
  end

補足
unless 条件 => 条件がfalseの場合を定義。

ただしこれでは記述が多くなり、修正する場合に手間が必要。
そこでbefore_actionを活用。

app/controllers/posts_controller.rb
before_action :ensure_user, only: [:edit, :update, :destroy]

...

  def edit
  end

  def update
    if @post.update(post_params)
      redirect_to new_post_path
    else
      render :edit
    end
  end

  def destroy
    @post.destroy
    redirect_to new_post_path
  end

  private
  def ensure_user
    @posts = current_user.posts
    @post = @posts.find_by(id: params[:id])
    redirect_to new_post_path unless @post
  end

...

補足
before_actionを活用することにより、アクションが読み込まれる前に行う動作を指定することができる。
定義はストロングパラメーターで行い、current_userでない場合はreditrct_toを定義。

viewsの編集

URLベタ打ち以前にそもそもユーザー誤認を防ぐために画面で表示をしない方が望ましい。

app/views/posts/new.html.erb
<% @posts.each do |post| %>
    <tr>
        <td><%= post.user.name %></td>
        <td><%= post.title %></td>
        <td><%= post.body %></td>
        <td><%= link_to "詳細", post_path(post) %></td>
        <td><%= link_to "編集", edit_post_path(post) %></td>
        <td><%= link_to "削除", post_path(post), method: :delete %></td>
    </tr>
<% end %>

この部分を修正。

app/views/posts/new.html.erb
<% @posts.each do |post| %>
  <tr>
    <td><%= post.user.name %></td>
    <td><%= post.title %></td>
    <td><%= post.body %></td>
    <td><%= link_to "詳細", post_path(post) %></td>
    <% if post.user == current_user %>
      <td><%= link_to "編集", edit_post_path(post) %></td>
      <td><%= link_to "削除", post_path(post), method: :delete %></td>
    <% else %>
      <td></td>
      <td></td>
    <% end %>
  </tr>
<% end %>

結論

 controllerとviewを編集することにより、他ユーザーの編集を防ぐことが可能。

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

git push heroku masterする際のエラー解消方法

【概要】

1.結論

2.どのようなエラーか

3.なぜエラーが起きるのか

4.どのように解決するのか

1.結論

githubの左上メニューでCompare to Blanchを押してmasterにmargeする!

2.どのようなエラーか

ターミナル
%  git push heroku master
To https://git.heroku.com/*****.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'https://git.heroku.com/*****.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

このようなエラーが出てしまいました。
ローカルレポジトリ(master)からherokuへのmasterにpushしようとした際に拒否されてしまったエラーです。
hintが書いてありますが全く解決できずで、調べてもなかなか解決には至りませんでした。


3.なぜエラーが起きるのか

masterとローカルレポジトリにあるブランチが反映されていない状態になっており食違いがあったことが原因でした。

4.どのように解決するのか

なのでgithubの左上メニューでCompare to Blanchを押してmasterにmargeしたところ、エラーは無事に解決しました。

❶github desktopを開きますとMacですと左上にメニューバーが出てきます。そこで"Branch"を押下します。
スクリーンショット 2020-09-07 21.47.57.png

❷そしてCompare to Blanchを押します。
スクリーンショット 2020-09-07 21.48.06.png

❸そしてgithub desktopの画面に戻ってhistoryを見ていきます。

❹”1(1以上になっている)⬇︎”になっていると思うのでそれをmasterに統合します。
スクリーンショット 2020-09-07 21.48.35.png
写真では0⬇︎になっていますが、エラーが起きていた際は1⬇︎になっていました。その証拠に隣が1⬆︎になっています。
そうすることで再度、ターミナルで” git push heroku master”してあげるとうまく行きました。

そこで、いろいろと思い返しました。
Pay.jpでjavascriptに環境変数を記載して、herokuにpushする際にわざとプログラムを変更して読み込ませるようにしました。変更を加えた際に、ブランチを作ったような気がしました。やはり気のせいではなくブランチを作ってありました。


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

[rails sエラー]md5.bundleとmysqlインストールエラー

目的地 

作成済みアプリで問題なくrails s実行

現在

$ rails s
省略)
/Users/ユーザーの名前/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require': dlopen(/Users/ユーザーの名前/.rbenv/versions/2.5.1/lib/ruby/2.5.0/x86_64-darwin18/digest/md5.bundle, 9): Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib (LoadError)
Referenced from: /Users/ユーザーの名前/.rbenv/versions/2.5.1/lib/ruby/2.5.0/x86_64-darwin18/digest/md5.bundle
Reason: image not found - /Users/ユーザーの名前/.rbenv/versions/2.5.1/lib/ruby/2.5.0/x86_64-darwin18/digest/md5.bundle

原因

pythonの環境構築したことでopensslのバージョンが変わってしまったこと?

やったこと

  • rubyの再インストール
  • bundlerのバージョン調整
  • mysqlインストール

rubyの再インストール

rubyが参照していたバージョンのOpenSSLがなくなったことにより、エラー発生
どうやら再インストールするしかないらしい

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin18]
$ rbenv uninstall 2.5.1
$ rbenv install 2.5.1
$ruby -v 
# ruby 2.5.1p0  ←のように表示されればOK

bundle installしてみる

$ bundle install
Traceback (most recent call last):
    2: from /Users/<user名>/.rbenv/versions/2.5.3/bin/bundle:23:in `<main>'
    1: from /Users/<user名>/.rbenv/versions/2.5.3/lib/ruby/2.5.0/rubygems.rb:308:in `activate_bin_path'
/Users/<user名>/.rbenv/versions/2.5.3/lib/ruby/2.5.0/rubygems.rb:289:in `find_spec_for_exe': can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)

can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)

。。。

アプリディレクトリ下のbundleのバージョンとglobalのバージョンが違うらしい

bundlerのバージョン調整

アプリ内のgemfileを確認

gemfile.lock
RUBY VERSION
   ruby 2.5.1p57

BUNDLED WITH
   2.0.2

現在のバージョン確認

bundler -v
Bundler version 2.0.1

合わせる

$ gem uninstall bundler -v 2.0.1
$ gem install bundler -v 2.0.2

bundle installしてみる

$ bundle install

(色々なgemのインストールのログが出てくる)
.
.
.
Installing ...
Fetching mysql2 0.5.2
Installing mysql2 0.5.2 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/fukudatakumi/tabelog/vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2/ext/mysql2
/Users/fukudatakumi/.rbenv/versions/2.5.1/bin/ruby -I /Users/fukudatakumi/.rbenv/versions/2.5.1/lib/ruby/site_ruby/2.5.0 -r ./siteconf20190321-35638-onb5bd.rb extconf.rb
--with-opt-lib\=/usr/local/opt/openssl/lib\ --with-opt-include\=-I/usr/local/opt/openssl/include
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
-----
Using mysql_config at /usr/local/opt/mysql@5.7/bin/mysql_config
-----
checking for mysql.h... yes
checking for errmsg.h... yes
checking for SSL_MODE_DISABLED in mysql.h... yes
checking for SSL_MODE_PREFERRED in mysql.h... yes
checking for SSL_MODE_REQUIRED in mysql.h... yes
checking for SSL_MODE_VERIFY_CA in mysql.h... yes
checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes
checking for MYSQL.net.vio in mysql.h... yes
checking for MYSQL.net.pvio in mysql.h... no
checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes
checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes
checking for my_bool in mysql.h... yes
-----
Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
-----
-----
Setting libpath to /usr/local/opt/mysql@5.7/lib
-----
creating Makefile

current directory: /Users/fukudatakumi/tabelog/vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2/ext/mysql2
make "DESTDIR=" clean

current directory: /Users/fukudatakumi/tabelog/vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2/ext/mysql2
make "DESTDIR="
compiling client.c
compiling infile.c
compiling mysql2_ext.c
compiling result.c
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: warning: directory not found for option '-L/usr/local/opt/openssl/lib --with-opt-include=-I/usr/local/opt/openssl/include'
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/fukudatakumi/tabelog/vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2 for inspection.
Results logged to /Users/fukudatakumi/tabelog/vendor/bundle/ruby/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/mysql2-0.5.2/gem_make.out

An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling.

エラー変わったけど、、、
Make sure that gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/' succeeds before bundling.
らしいので
mysqlインストールするかぁ

mysqlインストール

指示されたコード打ってみる

$ gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'
Fetching mysql2 0.5.3
Installing mysql2 0.5.3 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/name/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/ext/mysql2
/Users/name/.rbenv/versions/2.5.3/bin/ruby -r ./siteconf20181128-2670-lgcxlu.rb extconf.rb
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
-----
Using mysql_config at /usr/local/bin/mysql_config
-----
checking for mysql.h... yes
checking for errmsg.h... yes
checking for SSL_MODE_DISABLED in mysql.h... yes
checking for SSL_MODE_PREFERRED in mysql.h... yes
checking for SSL_MODE_REQUIRED in mysql.h... yes
checking for SSL_MODE_VERIFY_CA in mysql.h... yes
checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes
checking for MYSQL.net.vio in mysql.h... yes
checking for MYSQL.net.pvio in mysql.h... no
checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes
checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes
checking for my_bool in mysql.h... no
-----
Dont know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
-----
-----
Setting libpath to /usr/local/Cellar/mysql/8.0.12/lib
-----
creating Makefile

current directory: /Users/name/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/ext/mysql2
make "DESTDIR=" clean

current directory: /Users/name/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/ext/mysql2
make "DESTDIR="
compiling client.c
compiling infile.c
compiling mysql2_ext.c
compiling result.c
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/name/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3 for inspection.
Results logged to /Users/name/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/mysql2-0.5.3/gem_make.out

An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  mysql2

どうやら、
ld: library not found for -lssl
がエラー起こしてるらしい

https://qiita.com/HrsUed/items/ca2e0aee6a2402571cf6
を参考にさしてもらい

terminal
$ brew info openssl
openssl@1.1: stable 1.1.1g (bottled) [keg-only]
Cryptography and SSL/TLS Toolkit
https://openssl.org/
/usr/local/Cellar/openssl@1.1/1.1.1g (8,059 files, 18MB)
  Poured from bottle on 2020-05-20 at 17:52:32
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/openssl@1.1.rb
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> /Users/kento/.bash_profile

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
For pkg-config to find openssl@1.1 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"

==> Analytics
install: 578,162 (30 days), 1,761,588 (90 days), 7,253,878 (365 days)
install-on-request: 61,776 (30 days), 207,766 (90 days), 910,622 (365 days)
build-error: 0 (30 days)

export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
このオプションの値を指定してインストールする

gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/' -- --with-cppflags=-I/usr/local/opt/openssl@1.1/include --with-ldflags=-L/usr/local/opt/openssl@1.1/lib

bundle install してみる
rails s してみる

おわり

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

[rails sエラー]md5.bundle

目的地 

作成済みアプリで問題なくrails s実行

現在

$ rails s
省略)
/Users/ユーザーの名前/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require': dlopen(/Users/ユーザーの名前/.rbenv/versions/2.5.1/lib/ruby/2.5.0/x86_64-darwin18/digest/md5.bundle, 9): Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib (LoadError)
Referenced from: /Users/ユーザーの名前/.rbenv/versions/2.5.1/lib/ruby/2.5.0/x86_64-darwin18/digest/md5.bundle
Reason: image not found - /Users/ユーザーの名前/.rbenv/versions/2.5.1/lib/ruby/2.5.0/x86_64-darwin18/digest/md5.bundle

原因

pythonの環境構築したことでopensslのバージョンが変わってしまったこと?

やったこと

  • rubyの再インストール
  • bundlerのバージョン調整
  • mysqlインストール

rubyの再インストール

rubyが参照していたバージョンのOpenSSLがなくなったことにより、エラー発生
どうやら再インストールするしかないらしい

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin18]
$ rbenv uninstall 2.5.1
$ rbenv install 2.5.1
$ruby -v 
# ruby 2.5.1p0  ←のように表示されればOK

bundle installしてみる

$ bundle install
Traceback (most recent call last):
    2: from /Users/<user名>/.rbenv/versions/2.5.3/bin/bundle:23:in `<main>'
    1: from /Users/<user名>/.rbenv/versions/2.5.3/lib/ruby/2.5.0/rubygems.rb:308:in `activate_bin_path'
/Users/<user名>/.rbenv/versions/2.5.3/lib/ruby/2.5.0/rubygems.rb:289:in `find_spec_for_exe': can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)

can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)

。。。

アプリディレクトリ下のbundleのバージョンとglobalのバージョンが違うらしい

bundlerのバージョン調整

アプリ内のgemfileを確認

gemfile.lock
RUBY VERSION
   ruby 2.5.1p57

BUNDLED WITH
   2.0.2

現在のバージョン確認

bundler -v
Bundler version 2.0.1

合わせる

$ gem uninstall bundler -v 2.0.1
$ gem install bundler -v 2.0.2

bundle installしてみる

$ bundle install

(色々なgemのインストールのログが出てくる)
.
.
.
Installing ...
Fetching mysql2 0.5.2
Installing mysql2 0.5.2 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/fukudatakumi/tabelog/vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2/ext/mysql2
/Users/fukudatakumi/.rbenv/versions/2.5.1/bin/ruby -I /Users/fukudatakumi/.rbenv/versions/2.5.1/lib/ruby/site_ruby/2.5.0 -r ./siteconf20190321-35638-onb5bd.rb extconf.rb
--with-opt-lib\=/usr/local/opt/openssl/lib\ --with-opt-include\=-I/usr/local/opt/openssl/include
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
-----
Using mysql_config at /usr/local/opt/mysql@5.7/bin/mysql_config
-----
checking for mysql.h... yes
checking for errmsg.h... yes
checking for SSL_MODE_DISABLED in mysql.h... yes
checking for SSL_MODE_PREFERRED in mysql.h... yes
checking for SSL_MODE_REQUIRED in mysql.h... yes
checking for SSL_MODE_VERIFY_CA in mysql.h... yes
checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes
checking for MYSQL.net.vio in mysql.h... yes
checking for MYSQL.net.pvio in mysql.h... no
checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes
checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes
checking for my_bool in mysql.h... yes
-----
Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
-----
-----
Setting libpath to /usr/local/opt/mysql@5.7/lib
-----
creating Makefile

current directory: /Users/fukudatakumi/tabelog/vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2/ext/mysql2
make "DESTDIR=" clean

current directory: /Users/fukudatakumi/tabelog/vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2/ext/mysql2
make "DESTDIR="
compiling client.c
compiling infile.c
compiling mysql2_ext.c
compiling result.c
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: warning: directory not found for option '-L/usr/local/opt/openssl/lib --with-opt-include=-I/usr/local/opt/openssl/include'
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/fukudatakumi/tabelog/vendor/bundle/ruby/2.5.0/gems/mysql2-0.5.2 for inspection.
Results logged to /Users/fukudatakumi/tabelog/vendor/bundle/ruby/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/mysql2-0.5.2/gem_make.out

An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling.

エラー変わったけど、、、
Make sure that gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/' succeeds before bundling.
らしいので
mysqlインストールするかぁ

mysqlインストール

指示されたコード打ってみる

$ gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'
Fetching mysql2 0.5.3
Installing mysql2 0.5.3 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/name/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/ext/mysql2
/Users/name/.rbenv/versions/2.5.3/bin/ruby -r ./siteconf20181128-2670-lgcxlu.rb extconf.rb
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
-----
Using mysql_config at /usr/local/bin/mysql_config
-----
checking for mysql.h... yes
checking for errmsg.h... yes
checking for SSL_MODE_DISABLED in mysql.h... yes
checking for SSL_MODE_PREFERRED in mysql.h... yes
checking for SSL_MODE_REQUIRED in mysql.h... yes
checking for SSL_MODE_VERIFY_CA in mysql.h... yes
checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes
checking for MYSQL.net.vio in mysql.h... yes
checking for MYSQL.net.pvio in mysql.h... no
checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes
checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes
checking for my_bool in mysql.h... no
-----
Dont know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
-----
-----
Setting libpath to /usr/local/Cellar/mysql/8.0.12/lib
-----
creating Makefile

current directory: /Users/name/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/ext/mysql2
make "DESTDIR=" clean

current directory: /Users/name/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3/ext/mysql2
make "DESTDIR="
compiling client.c
compiling infile.c
compiling mysql2_ext.c
compiling result.c
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/name/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/gems/mysql2-0.5.3 for inspection.
Results logged to /Users/name/.rbenv/versions/2.5.3/lib/ruby/gems/2.5.0/extensions/x86_64-darwin-18/2.5.0-static/mysql2-0.5.3/gem_make.out

An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  mysql2

どうやら、
ld: library not found for -lssl
がエラー起こしてるらしい

https://qiita.com/HrsUed/items/ca2e0aee6a2402571cf6
を参考にさしてもらい

terminal
$ brew info openssl
openssl@1.1: stable 1.1.1g (bottled) [keg-only]
Cryptography and SSL/TLS Toolkit
https://openssl.org/
/usr/local/Cellar/openssl@1.1/1.1.1g (8,059 files, 18MB)
  Poured from bottle on 2020-05-20 at 17:52:32
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/openssl@1.1.rb
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> /Users/kento/.bash_profile

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
For pkg-config to find openssl@1.1 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"

==> Analytics
install: 578,162 (30 days), 1,761,588 (90 days), 7,253,878 (365 days)
install-on-request: 61,776 (30 days), 207,766 (90 days), 910,622 (365 days)
build-error: 0 (30 days)

export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
このオプションの値を指定してインストールする

gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/' -- --with-cppflags=-I/usr/local/opt/openssl@1.1/include --with-ldflags=-L/usr/local/opt/openssl@1.1/lib

bundle install してみる
rails s してみる

おわり

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

Twitterでかっこよくソースコードを表示するCarbonを君は知っているか。

こんにちは、現在フリーのweb制作者として活動していますしょーごと申します。

さてみなさん、ソースコードのハイライトは見ていて気持ちが良いですよね。

VSCodeのコードハイライトだと、class名が緑色になるのが好きだったりします。

スクリーンショット 2020-09-07 20.47.45.jpg

これに慣れてしまうと、もう質素なメモ帳など使えなくなりますよね。

しかし、Twitterにはエディター機能はないので、ツイートしたくてもそのままソースコードを貼り付けると、すごく味気なくなります(文字数制限もきつい)

スクリーンショット 2020-09-07 20.51.48.jpg

私はTwitterを初めて8年、エンジニアを初めて2年立ちますが、2020年からイカしたサービスを活用しています。

コードハイライト大好き教のあなた、今日からCarbonを使うのです。

Carbonとは

Twitterできれいにソースコードを共有できるサービスです。

Carbon

スクショで貼るより綺麗です。

スクリーンショット 2020-09-07 20.10.48.jpg

様々な言語、テーマに対応しているのが写真でわかるかと思います。

上記の写真でのテーマはVSCodeですが、私はsetiというテーマが好きなので、setiで表示していきます。

Ruby

スクリーンショット 2020-09-07 20.16.29.jpg

JavaScript(Vue)

スクリーンショット 2020-09-07 20.19.38.jpg

Sass

スクリーンショット 2020-09-07 20.23.08.jpg

PHP

スクリーンショット 2020-09-07 20.40.51.jpg

簡単投稿、画像保存も可能

Twitterでサインインすればすぐツイートできますし、画像としてpngなどでエクスポートすることも可能です。

スクリーンショット 2020-09-07 20.34.36.jpg

投稿時にはCreated with @carbon_appは消しても問題ありません。
pic.twitter.com以下があれば問題ありません。

スクリーンショット 2020-09-07 20.23.34.jpg

スクリーンショット 2020-09-07 20.32.28.jpg

うーん、おしゃれ!!!

Twitterにソースコードを共有する心理としては、

「私のこのスマートなコードを見てくれ!これ便利でおすすめだぜ!!」

pose_makasenasai_boy.png

という心理かと思いますので(独断と偏見)

訴求力で見ても、とてもスマートなソースコードの共有方法だと思います。

Carbon

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

Webアプリケーションにおけるセキュリティ

セキュリティ対策をしていないwebアプリケーションの開発は非常に危険です。
セキュリティとは何なのか。
セキュリティ対策をしていないと
・なぜ危険なのか
・どういった事が起こるのか
・どのように対策するのか
について学んだ内容をまとめました。(Rubyでの開発)

前提
プログラミング初学者(1~2ヶ月)が学んだ内容です。
実際の現場で通用しないことや間違った内容が含まれている可能性があります。
間違っている部分や浅い部分については、追記やご指摘いただけると幸いです。

セキュリティ対策していないとなぜ危険なのか

セキュリティ対策をしていないwebサービスはなぜ危険なのか、何が起こるのか。
結論
利用者は個人情報が漏れる。
管理者はwebページを勝手にいじられる。

もう少し細かく見ると、
・個人情報を勝手に閲覧される
・webページの内容が改ざんされる
・webページ自体が利用不可能になる
その結果
・webサービス利用者へ大きな損害
・公開したwebページの社会的信用を失う

webサービスにおけるセキュリティとは何か

脆弱性があるwebサービスはセキュリティが低いと言えます。

脆弱性

脆弱性とはセキュリティホールとも呼ばれ、ソフトウェアの起こりうる欠陥や使用上の問題点のこと。
悪意のある人に攻撃を仕掛けられる際の弱点といえます。
この脆弱性があると開発者利用者どちらにも様々な被害が生じます。

セキュリティ攻撃の種類

・JavaScriptによるセキュリティ攻撃
・セッションを用いた攻撃
・不正なリクエストを行う攻撃
・SQLによる攻撃
攻撃というのは個人情報を抜き取る行為と思って下さい。

JavaScriptによる攻撃

JavaScriptはクライアントサイドで動くプログラミング言語です。
何らかの方法で悪意あるJavaScriptを埋め込めば、その埋め込んだページを表示した時にユーザーにそのプログラムを実行させることができます。
このような攻撃方法をXSS(クロスサイトスクリプティング)と呼びます。
スクリプトを埋め込んで実行させることをスクリプティングといいます。
その内容は個人情報を悪意ある人のサーバーへ送付するものが多いようです。

JavaScriptによる攻撃

JavaScriptはクライアントサイドで動くプログラミング言語
何らかの方法で悪意のあるJavaScriptをユーザーがwebアプリケーションを表示したブラウザに埋め込めば、ユーザーにそのプログラムを実行させることができる。
このような攻撃をXSSという

XSS(クロスサイトスクリプティング)

攻撃者が脆弱性のあるwebアプリケーション上に悪意のあるJavaScriptのプログラム(スクリプト)を埋め込み、そのサイトの利用者を攻撃する手法
悪意あるスクリプトをwebアプリケーションを表示した時に実行させることをゴールとした手法がXSS
スクリプトを埋め込んで実行させることを「スクリプティング」という。
内容は、ユーザーの個人情報などを悪意ある人のサーバーなどへ送付するスクリプトが多い。

XSSの攻撃パターン

XSSの攻撃パターンには反射型XSSと格納型XSSの2タイプがあります。

反射型XSS

ユーザーが悪意のあるURLをクリックすると、脆弱性のあるwebアプリケーション上でスクリプトが実行されるようにする手法
手順
①ユーザーにURLをクリックさせる
攻撃者が、ユーザーに悪意のあるURL(脆弱性のあるwebアプリケーションへ遷移するURLに細工をしたもの)をクリックさせる。
→脆弱性のあるアプリケーションは攻撃者のもと?それとも全く関係ないサイト?
前者なら納得、後者なら後者のサイトはセキュリティが弱くて利用されているだけ?
②ユーザーがクリックすると、脆弱性のあるwebアプリケーション上でスクリプトが動く
悪意のあるURLでは脆弱性のあるwebアプリケーション上で発火するスクリプトが仕込まれている。そのため、脆弱性のあるwebアプリを開くとスクリプトが発火する。
③スクリプトが発火すると、攻撃者のサーバーへ個人情報が送られる
発火(発動)したスクリプトは個人情報を抜き取るようなものでした。
クリックしたユーザーの個人情報が攻撃者のサーバーへ記録されてしまいます。

このようにURLをクリックしてレスポンスがあると即座にスクリプトが実行されることから反射型と呼ばれる。
別サイト(メールなども含む)から脆弱性のあるwebアプリケーションへ遷移した後にスクリプトが実行される。
よくチャットアプリなんかでURLが貼られると、そのページをクリックする前に
安全なサイトですか?
といった確認が出ると思います。
この反射型XSSを防ぐために、表示させるようにしているのだと思います。
これがクロスサイトスクリプティングのクロスサイト(webサイト間をまたぐ)を意味しています。

格納型XSS

脆弱性のあるwebアプリケーションの投稿などに悪意のあるスクリプトを埋め込み、ユーザーがそのwebアプリケーションを訪れた時にスクリプトが発火するような攻撃手法
例えば、twitterの投稿で悪意のあるスクリプトを投稿します。
その投稿が見られるページに遷移した途端スクリプトが発火します。
その投稿をみようとしなくても、その投稿があるページを開いた瞬間攻撃されます。
手順
①攻撃者が、脆弱性のあるwebアプリケーションにスクリプト付きの投稿をする
フォームからコンテンツを投稿する際にスクリプトを埋め込んで投稿する。
②ユーザーがその投稿のあるページを訪れるとスクリプトが発火する。
スクリプトが埋め込まれた投稿ページを開くとそのスクリプトが発火する。
画像にマウスを合わせるとスクリプトが発火するような仕掛けになっていることもある。
③スクリプトが発火すると攻撃者のサーバーへ個人情報が送られる。
脆弱性のあるwebアプリケーションにスクリプトを格納し、ユーザーが訪れたタイミングで発火するようになっていることから格納型と呼ばれる。

XSSの対処

XSSは現在でも多く事例が報告されている。
脆弱性をつく攻撃の被害はXSSに起因するものが最も多くの割合を占めている。

対処方法

XSSが発生する主要因として、フォームから入力されたスクリプトタグ()などがスクリプトとしてそのままブラウザに反映されてしまっていることがあげられる。
XSSを防ぐためにHTMLにおいて意味を持つ"や<を何らかの別の文字列に変換する。この変換の際に使われる記法を文字参照という。

文字参照

ブラウザ上で表示できない特殊文字を表記する際に用いられる記法。

変換前 変換後
< & It;
> & gt;
& & amp;
" & quot;
' & #39;

このようにプログラムを記述している時に使っている記号を特殊文字に変換すると、外部から埋め込まれたスクリプトが実行されにくくなります。

Ruby on Railsにおける対処法

ヘルパーメソッド<% %>でXSSの対策を行うことができる。
この中に書かれた記述は文字列として表示されるため、プログラムの記述にはならない。
このプログラムとしてではなく、文字列として特殊文字を表示することをエスケープ?というようです。

まとめ

反射型
特定のURLにアクセスすることででスクリプトを発火させる。
格納型
投稿自体をみるとスクリプトが発火するので、普通のサイトで悪意のあるユーザーの投稿にスクリプトが埋め込まれていて、それがあるページにあくせすしてしまうと発火する。
Rubyの場合はRuby on Railsのヘルパーメソッドで回避できる。

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

Sinatra アプリを CSS で装飾

はじめに

前回の記事 - Qiitaで更に味を占めたので、CSSでチョットだけ色を付けてみました

完成画面

20200907A.png
heroku アプリ

前回の反省点

err.or
app.rb:2:in `require': cannot load such file -- sinatra/reloader (LoadError)

単に、sinatra-contribgroup :developmentになっているだけでした。

CSS

ずらずらと並んでいるのはカッコ悪いので、社名のみを表示し、路線名はクリックして表示させることにしました。

style.css
.btm {
    transition: 0.2s;
    height: 0;
    overflow: hidden;
    padding: 0 10px;
}
.container input:checked + .btm {
    height: auto;
    padding: 0 10px 10px 10px;
    background-color: lightsalmon;
}

height0になっているので隠れているんだと思います結果オーライ

まとめ

  • 初アプリ修正
  • CSS に詳しくなった

参照したサイト
【CSS】CSSだけでブロック要素の表示非表示
【初学者向け】Ruby 遅延鉄道路線名の入手方法 - Qiita

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

railsのハッシュタグ検索の実装

今回は、railsでハッシュタグ機能を実装しました。
https://glodia.jp/blog/3936/
のサイトを参考にさせていただきました。

完成図
商品を出品する時にタグをつけると

スクリーンショット 2020-09-07 18.44.13.png

リンクが踏めるハッシュタグが投稿詳細の際に確認でき、
スクリーンショット 2020-09-07 18.45.37.png

リンクの先には同じタグを持った商品が並ぶ
スクリーンショット 2020-09-07 18.57.37.png

実装方法
1 商品テーブルitemsとタグテーブルtagsは中間テーブルで結びついている
2 ヘルパーに、タグ名をリンクにするための記述をする

items_helper.rb
def render_with_hashtags(tag_name, tag_id)
tag_name.gsub(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/){|word| link_to word, "/item/hashtag/#{word.delete("#")}?tag_id=#{tag_id}"}.html_safe
end

第一引数でタグの名前、第二引数で中間テーブルに保存されているタグのidを渡してます。
gsubメソッドでurlに置換しhtml_safeメソッドをつけることでエスケープ処理を避け、ビューに「#名前」の形でリンクが踏めるようになります。
?tag_id=#{tag_id} の記述で、タグのidをリンクの中に仕込む

3 ルーティングの実装

routes.rb
 get '/item/hashtag/:name', to: "items#hashtag"

本来はitemコントローラーの中にネストするのがベストだと思います

4 コントローラーの実装

items_controller.rb
def hashtag
  @tag = Tag.find(params[:tag_id])
  @items = @tag.items
end

2でリンクの中に仕込んだtag_idをparamsで渡し、該当するタグを探す
該当するタグを持つ商品を変数に入れる

5 投稿詳細一覧を作る時と同様、eachメソッドを@itemsを使用してビューを実装

完成です!

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

Rails on Dockerの環境構築手順

環境

  • MacOS Catalina 10.15.6
  • Docker 2.3.0.3
  • Ruby 2.7.1
  • Rails 6.0.3.2
  • Bundler 2.1.4
  • Gem 3.1.2
  • Yarn 1.22.5

手順

予めDockerをインストールしておいて下さい。

Rubyイメージの取得

$ docker pull ruby

Dockerコンテナを起動

<NAME><DIRECTORY>は適時変更して下さい。

$ docker run -i -t --name <NAME> -p 3000:3000 -v "$PWD":<DIRECTORY> /bin/bash

Railsの導入

作業ディレクトリへ移動します。

$ cd <DIRECTORY>

次に、Gemfileの生成

$ bundle init

生成されていることを確認。

$ ls
Gemfile

編集用にvimをインストール。
(ホストPCのディレクトリから直接編集してもOK)

$ apt-get install vim-tiny

編集します。
# gem "rails"のコメントアウト#を外します。

$ vim.tiny Gemfile
編集後
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails"

Gemをインストール。

$ bundle install

新規Railsプロジェクトの作成。

$ bundle exec rails new .

NodeJSをインストール。

$ apt-get install nodejs

Webpackerをインストールしますが、その前にYarnが必要なのでインストールします。
しかし、そのままapt-get installすると0.32+gitというバージョンでインストールされてしまい、
webpack:installした際に行われるyarnのバージョンチェック時にエラーが出てしまうようです。
参考文献: 【Rails】ArgumentError: Malformed version number string 0.32+gitでwebpacker:installが実行できない場合の対処方法

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

Webpackerをインストール。

rails webpacker:install

Rails Server起動

起動できるはずです。

$ bundle exec rails server

自分の環境の場合、うまくローカルで接続できなかったので明示的に指定すると無事接続できました。

$ bundle exec rails server -b 0.0.0.0
=> Booting Puma
=> Rails 6.0.3.2 application starting in development 
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.6 (ruby 2.7.1-p83), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
Started GET "/" for 172.17.0.1 at 2020-09-07 09:47:16 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
   (1.5ms)  SELECT sqlite_version(*)
Processing by Rails::WelcomeController#index as HTML
  Rendering /usr/local/bundle/gems/railties-6.0.3.2/lib/rails/templates/rails/welcome/index.html.erb
  Rendered /usr/local/bundle/gems/railties-6.0.3.2/lib/rails/templates/rails/welcome/index.html.erb (Duration: 6.3ms | Allocations: 295)
Completed 200 OK in 21ms (Views: 12.1ms | ActiveRecord: 0.0ms | Allocations: 1651)

Yay! You're on Rails!

image.png

おわり

参考文献: 知識0から始めるRails on Docker

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

preventDefaultはなぜ必要なのか

動作環境
Ruby 2.6.5
Rails 6.0.3.2

JavaScriptにおけるpreventDefaultがどういった働きをしているかをようやく理解することができたので、整理するために投稿してみました。

preventDefaultの意味

preventDefaultの必要性について話していく前に、そもそもpreventDefaultが何をしているのかを説明します。

default(デフォルト)の挙動をprevent(妨害)しています。

説明になっていないと思うかもしれませんが、厳密な説明をしようとすると、おそらくかなり長い行数が必要となってしまいますし、具体的な例を提示した方がわかりやすいと思い、端的に説明しました。早速、具体的な例を元に説明していきます。

preventDefaultの具体例

index.rb
<%= form_with url:  "/posts", method: :post do |form| %>
  <%= form.text_field :fuga  %>
  <%= form.submit '投稿する' , id: "submit" %>
<% end %>

index.rbにて投稿するボタンをクリックすると、fugaに入力した内容が投稿されるとします。
このままだと、投稿するボタンをクリックすると、ページの読み込みが始まってしまうので、下記のJavaScriptを記載することで非同期通信にしたとします。

function hoge() {
  const submit = document.getElementById("submit");
  submit.addEventListener("click", (e) => {
    // 投稿するボタンをクリックした時と同じ挙動のコード(長くなってしまうので、省略)
  });
};
window.addEventListener("load", hoge);

しかし、ここで1つ問題が生じてしまいます。それは、非同期通信にしたことによって同じ挙動が追加で行われてしまうので、1回の投稿で2回分投稿したことになってしまうということです。
具体的に説明すると、投稿するボタンをクリックすることで非同期通信が行われ、ページの読み込みなしで投稿が1つ増え、ページの再読み込みをすることで、本来の投稿するボタンをクリックした時の挙動が反映されて、投稿が1つ増えます。そのため、結果として同じ投稿が2回されるということが起きてしまいます。

この問題を解決するためにprevent.Defaultを使います。以下のように1行追加するだけです。

function hoge() {
  const submit = document.getElementById("submit");
  submit.addEventListener("click", (e) => {
    e.preventDefault();
    // 投稿するボタンをクリックした時と同じ挙動のコード(長くなってしまうので、省略)
  });
};
window.addEventListener("load", hoge);

これによって、デフォルトの挙動を妨害することができるので、1回の投稿で2回分投稿することがなくなりました。
しかし、ここで私は「そもそもデフォルトの挙動って何?」「なぜ都合よく非同期通信だけの挙動が妨害されていないの?」
といった疑問を抱いておりました。

デフォルトの挙動とは?

基本的にRuby on Railsでのデフォルトの挙動は、ビューでの操作によって、ビューからコントローラーに情報が送られ、コントローラー内のいずれかのアクションが動くという認識で問題ないようです。
今回でいうと、投稿するボタンをクリックしたことでcreateアクションが動き、fugaを保存するということがデフォルトの挙動です。通常のRuby on Railsの流れですね。
そのため私は、preventDefaultを「通常のRuby on Railsの流れを妨害するもの」と捉えております。

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

rubyオブジェクトをJSON形式に変換

引数にハッシュ、配列等のrubyオブジェクトを入れJSON形式で返してくれます

JSON.generate

pry(main)> JSON.generate({"hoge"=>{"fuga"=>1, "fugafuga"=>2}})
=> "{\"hoge\":{\"fuga\":1,\"fugafuga\":2}}"

JSON.pretty_generate

pry(main)> JSON.pretty_generate({"hoge"=>{"fuga"=>1, "fugafuga"=>2}})
=> "{\n  \"hoge\": {\n    \"fuga\": 1,\n    \"fugafuga\": 2\n  }\n}"

JSON.generateに比べて見やすい文字列に返してくれます

人が読むものではない限りgenerateのほうが容量が軽いためgenerateを使用した方がいいとのことでした

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

Ruby カロリー計算 アウトプット

自分用のアウトプットです。

食べた食品のカロリー計算をするプログラムをしました。

M = 食品の品数
N = 人数
food = 1gあたりのカロリー
eat = 何g食べたか
carolie = トータルカロリー

qiita.rb
M,N = gets.chomp.split(" ").map{|i| i.to_i}

food = []
i = 0
M.times do 
  i = (gets.to_f / 100)
  food.push(i)
end


eat = []

N.times do
  i = gets.chomp.split(" ").map{|i| i.to_i}
  eat.push(i)
end

i = 0
f = 0
total = []
count = N
count1 = M - 1
calorie = 0

while count > 0 do
  for s in 0..count1 do
    calorie += (food[i] * eat[f][s]).floor
    i = i + 1
  end
  total.push(calorie)
  calorie = 0
  i = 0
  f = f + 1
  count = count - 1
end


puts total

あまりfor文使い慣れていないのでその点今回使えたのはよかったですかね。
おそらく今回2次元配列を使えばもっと凝縮できた感がありましたが、うまくいきませんでした。。。

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

RSpecとminitestのおおまかな違い

はじめに

railsで最もよく使われるテスティングフレームワークはRSpecという話をよく聞きますが、
ruby標準で使用できるminitestとの違いについてまとめてみました。

RSpecについて

RSpecを使う理由は「最初からいろんな機能が全部入ってて便利だから」。
ちょっとテストコードのここだけを共通化したい、とか、モックで少し凝ったことをやりたい等、RSpecの標準機能でほとんど完結させる事ができる。

minitestについて

簡単なRubyプログラムの動作確認をしたりするときに使う。
デフォルトでRubyにインストールされるので、すぐに使えて便利。
RSpec並みに凝ったことをやろうとすると、プラグインをたくさん入れたり、継承、モジュール等を駆使することになるため、業務レベルのテストコードではminitestはあまり使われていない。

統合テスティング環境としての RSpec

実利的な観点から minitest よりも RSpec のほうが好ましい点がある。
minitestが 「テスティングフレームワーク」であるのに対して、Rspec が「統合テスティン グ環境」であることを目指しているという点。

RSpec は、振舞定義用の DSL の提供に加えて、様々なテスト関連ライブラリや周辺ツールを統合したり、標準サポートしたりといった試みを積極的に続けている。以下にその代表的なものを記述する。

・RSpec 実行結果の色付け (レッドバー/ グリーンバー表示) の標準サポート
・モック や スタブ といったテスト技法の支援、関連ライブラリとの統合
・保留 (pending) のサポート
・Rake や RCov、Heckle、AutoTest (ZenTest) といった周辺ツールとの統合
・Ruby on Rails のサポート
・様々な形式による実行結果レポート (例:HTML 出力) 。
・TextMate との統合
・などなど

※各項目について詳しくはRSpec 公式サイトの該当ページ参照

上記周辺ツールへの積極的な統合・サポートにより、RSpecの標準機能でできる事が多いため、広く使用されているのではないかと推測できる。

今後、RSpecを使用したテストについて学んで行きたい。

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

enumからセレクトボックスを作る際に、表示する文言を変えたいとき

あまり無いケースだとは思うが、enumからセレクトボックスを作成する際に、文言を変えたい場合の方法をメモしておく。
前提として、gemに enumerizereform を使用している。

product_form.rb
extend Enumerize

enumerize :fluit, in: {
  apple:      1,
  orange:     2,
  peach:      3,
  strawberry: 4,
  cherry:     5,
}, predicates: { prefix: true }

property :fluit
ja.yml
product:
  fluit:
    apple: りんご
    orange: オレンジ
    peach: もも
    strawberry: いちご
    cherry: さくらんぼ

普通に書けば、これで良い。

new.slim
= f.select :fluit, f.object.class.fluit.options, {prompt: '果物を選択'}

しかし、「『りんご』と『さくらんぼ』は『(特別セール中)』という文言を入れたい」と言われたとする。
とはいえ、ja.ymlを変えたくはない。
f.object.class.fluit.options を使わずに1つずつ書くのも、メンテナンス性が悪い。

そこで、f.object.class.fluit.options が生成する Array を map で回して、特定の value の時のみ、文言を足して返すメソッドを作った。

product_form.rb
def fruit_select_display
  self.class.fluit.options.map do |key, value|
    if value == 'apple' || value == 'cherry'
      [key + '(特別セール中)', value]
    else
      [key, value]
    end
  end
end
new.slim
= f.select :identification_document_type, f.object.fruit_select_display, {prompt: '果物を選択'}

これでいったん用は足せた。

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

Rails link_toにパラメータを渡す

link_toを使ってgetsでコントローラにパラメータを受け渡したい

やりたいこと

view側からコントローラにリンクする際に、値を受け渡したい

結論

以下のようにパスの引数にハッシュの形で追加してやる

※例)top/indexからtop/link1へ値を渡す場合

top/index.html.erb
  <%= link_to "リンク1へ", top_link1_path(name: "piyo") %>

パラメータを受け取る

top_controller
class TopController < ApplicationController
  def link1
    @params = params[:name]
  end
end

色々と調べてみましたが、備忘録として

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

Rails6で標準搭載されたwebpackerでハマる

発端

「ん?こっちで見ましょうか?」

から、なかなか気づかずに恥ずかしいハマり方してたので備忘録。

npmやらyarnなどのお話はたくさん情報あるのでそちらで。

対象システムのwebpackerでのbuild対象

application.html.erb
<%= stylesheet_pack_tag 'application' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

としてるので、js,css 共にbuild対象。

ファイル構成(必要部分のみ)

app/
└ javascript/
  └ packs/
  | └ application.js
  └ stylesheets/
    └ application.css
    └ custom.min.css
application.js
~~ 省略 ~~
import "../stylesheets/application";
application.css
@import "~bootstrap/scss/bootstrap";
@import "./custom.min";

ふむふむ。

$ bin/webpack
~~ 省略 ~~
ERROR in ./app/javascript/stylesheets/application.css
Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
ModuleBuildError: Module build failed (from ./node_modules/postcss-loader/src/index.js):
Error: Failed to find '~bootstrap/scss/bootstrap'
~~ 省略 ~~

この辺含めてファイル構成だったり確認したけれど特に問題なし、と。

改めてエラーを確認したところ。。。

ERROR in ./app/javascript/stylesheets/application.css

application.css!!

$ mv app/javascript/stylesheets/application.css app/javascript/stylesheets/application.scss

$ bin/webpack

無事build成功。
トホホ。。

結論

拡張子には気をつけよう。

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

Railsで現在いるページの判定をする

current_page?メソッド

viewで使用

# http://hoge.com/fugaかどうか
current_page?(http://hoge.com/fuga)

# /hogehogeかどうか
current_page?(/hogehoge)

# hoge_pathかどうか
current_page?(hoge_path)

# 任意のアクションかどうか
current_page?(action: 'new')

# 任意のコントローラ/アクションかどうか
current_page?(controller: 'hoge', action: 'new')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsで現在いるページの判定をする

current_page?メソッド

viewで使用

# http://hoge.com/fugaかどうか
current_page?(http://hoge.com/fuga)

# /hogehogeかどうか
current_page?(/hogehoge)

# hoge_pathかどうか
current_page?(hoge_path)

# 任意のアクションかどうか
current_page?(action: 'new')

# 任意のコントローラ/アクションかどうか
current_page?(controller: 'hoge', action: 'new')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]モデルの単体テスト実装方法

前提条件

今回は新規ユーザー登録に関するモデルの単体テストを実装していきながら、方法をアウトプットします。ユーザー登録の仕様書は以下のとおり。

ニックネームが必須       
メールアドレスは一意である
メールアドレスは@とドメインを含む必要がある
パスワードが必須
パスワードは7文字以上
パスワードは確認用を含めて2回入力する
ユーザー本名が、名字と名前でそれぞれ必須
ユーザー本名は全角で入力させる
ユーザー本名のフリガナが、名字と名前でそれぞれ必須
ユーザー本名のフリガナは全角で入力させる
生年月日が必須

マイグレーションファイルは以下のとおり。

20200823050614_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  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: ""
      t.string  :family_name,           null: false
      t.string  :family_name_kana,      null: false
      t.string  :first_name,            null: false
      t.string  :first_name_kana,       null: false
      t.date    :birth_day,             null: false

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at
      t.timestamps null: false
    end

    add_index :users, :email,                 unique: true
    add_index :users, :reset_password_token,  unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

モデルは以下のとおり。

user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

    # バリデーションの設定(空の文字列を保存させない為と一意性制約)
    validates :nickname,               presence: true
    validates :encrypted_password,     presence: true
    validates :family_name,            presence: true
    validates :family_name_kana,       presence: true
    validates :first_name,             presence: true
    validates :first_name_kana,        presence: true
    validates :birth_day,              presence: true

    # アソシエーション
    has_many :cards,          dependent: :destroy
    has_many :shipping_infos, dependent: :destroy
    has_many :comments,       dependent: :destroy
    has_many :items
end

実装手順

① RSpecの準備
② RSpecの設定
③ factory_botを導入
④ factory_botの実装
⑤ 正規表現の記述
⑥ テストコードの実装
⑦ テスコードの実行

RSpecの準備

まずGemをインストールします。

Gemfile.rb
group :development, :test do

  # 省略

  gem 'rspec-rails'
end

group :development do
  gem 'web-console'
end

# web_console は既に記述がある場合は、記述場所を移動するだけでOK。追記してしまうと重複してしまう可能性があるため注意してください。

Gemfileを編集したら、忘れずにbundle installを実行。

RSpecの設定

続いてRSpecの基本設定を行う。
まずはRSpec用の設定ファイルを作成する。

ターミナル.
$ rails g rspec:install

# 以下のようにファイルが作成されれば成功 ▼
 create  .rspec
 create  spec
 create  spec/spec_helper.rb
 create  spec/rails_helper.rb

次に、.rspecに以下を追加。

rspec.
--format documentation

続いて、RSpec用ディレクトリを設定します。

RSpecによるテストコードが書かれたファイルのことを、specファイルと呼び、全てのspecファイルは、先程のrails g rspec:installコマンドで生成されたspecディレクトリの中に格納するので、ここで作成しておきましょう。

モデルに関するテスト用ファイルであればspec/models/以下に、コントローラーに関するテスト用ファイルであればspec/controllers/以下に格納されます。appディレクトリ以下にあるテストの対象となるコードの在り処は全てこのディレクトリです。

specファイルは対応するクラスを持つファイル名spec.rbという名前になります。今回はまず「user.rb」に関するspecファイルを作成するので、その場合の名前は`「userspec.rb」`になります。

ディレクトリの作成が完了したら、この時点で一度RSpecが正常に利用できるかの確認を行いましょう。

ターミナル.
$ bundle exec rspec

# 以下のような結果になれば正常に動作しているのでOK
No examples found.

Finished in 0.00031 seconds (files took 0.19956 seconds to load)
0 examples, 0 failures

factory_botを導入

続いてfactory_botを導入します。
Gemをインストール。

Gemfile.rb
group :development, :test do

  # 省略

  gem 'rspec-rails'
  gem 'factory_bot_rails'
end

その後、bundle installを実行。

factory_botの実装

次に、specディレクトリ直下にfactoriesというディレクトリを作成し,その中に、作成したインスタンスの複数形のファイル名でRubyのファイルを作成します。今回の場合は、users.rbになります。

users.rbを以下のように編集。

users.rb
FactoryBot.define do

  factory :user do
    nickname              {"tarou"}
    email                 {"sample@gmail.com"}
    password              {"1234567"}
    encrypted_password    {"1234567"}
    family_name           {"山田"}
    family_name_kana      {"ヤマダ"}
    first_name            {"太郎"}
    first_name_kana       {"タロウ"}
    birth_day             {"2000-01-01"}
  end
end

この記述は、仕様書に沿った内容をダミーデーターとして作成しており、こうすると、specファイルの中で特定のメソッド(buildやcreate)により簡単にインスタンスを生成したり、DBに保存したりできるようになります。

正規表現記述

続いて、仕様書に沿って、バリデーションへ正規表現を設定していきます。
必要な正規表現は以下のとおり。

パスワードは7文字以上
ユーザー本名は全角で入力させる
ユーザー本名のフリガナは全角で入力させる

これらを踏まえてモデルへ記述すると以下のとおり。

user.rb
class User < ApplicationRecord
    # 省略

    validates :nickname,               presence: true
    validates :encrypted_password,     presence: true, length: { minimum: 7 } # ここが文字数の正規表現
    validates :family_name,            presence: true, format: {with: /\A[ぁ-んァ-ン一-龥]/ } # ここがユーザー本名全角の正規表現
    validates :family_name_kana,       presence: true, format: {with: /\A[ァ-ヶー-]+\z/ } # ここがフリガナ全角の正規表現
    validates :first_name,             presence: true, format: {with: /\A[ぁ-んァ-ン一-龥]/ } # ここがユーザー本名全角の正規表現
    validates :first_name_kana,        presence: true, format: {with: /\A[ァ-ヶー-]+\z/ } # ここがフリガナ全角の正規表現

    # 省略
end

Rubyの正規表現は下記サイトを参考にしてください。
https://gist.github.com/nashirox/38323d5b51063ede1d41

テストコードの実装

それではいよいよテストコードの実装です!
テスコードを記述するときに心掛ける点は以下のとおり。

①各exampleで期待する値は1つ
②期待する結果をはっきりわかりやすく記述
③起きて欲しいことと起きてほしくないこと両方をテストする
④可読性を考えつつ、適度にDRYにする

user_spec.rbに以下のコードを記述。

user_spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

  end
end

今回はUserモデルの単体テストなのでdescribe User doとなり、新規ユーザーを作成するのでdescribe '#create' doとしており、2行目のdescribの中にテストコードを記述します。

まず、全ての項目の入力が存在すれば登録できることをテストします。
コードとその解説は以下の通り。

user_spec.rb
require 'rails_helper'

describe User do
  describe '#create' do
    # 入力されている場合のテスト ▼

    it "全ての項目の入力が存在すれば登録できること" do # テストしたいことの内容
      user = build(:user)  # 変数userにbuildメソッドを使用して、factory_botのダミーデータを代入
      expect(user).to be_valid # 変数userの情報で登録がされるかどうかのテストを実行
    end
  end
end

続いてテストすることは以下の通り。

nicknameがない場合は登録できないこと
emailがない場合は登録できないこと
passwordがない場合は登録できないこと
encrypted_passwordがない場合は登録できないこと
family_nameがない場合は登録できないこと
family_name_kanaがない場合は登録できないこと
first_nameがない場合は登録できないこと
first_name_kanaがない場合は登録できないこと
birth_dayがない場合は登録できないこと

これらの条件は仕様書の必須項目(null:false, presence: true)をテストする為のものです。
それでは記述していきましょう。解説はnicknameがない場合は登録できないことのテスト箇所で行います。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 入力されている場合のテスト ▼

      it "全ての項目の入力が存在すれば登録できること" do
        user = build(:user)
        expect(user).to be_valid
      end

      # nul:false, presence: true のテスト ▼

      it "nicknameがない場合は登録できないこと" do # テストしたいことの内容
        user = build(:user, nickname: nil) # 変数userにbuildメソッドを使用して、factory_botのダミーデータを代入(今回の場合は意図的にnicknameの値をからに設定)
        user.valid? #バリデーションメソッドを使用して「バリデーションにより保存ができない状態であるか」をテスト
        expect(user.errors[:nickname]).to include("を入力してください") # errorsメソッドを使用して、「バリデーションにより保存ができない状態である場合なぜできないのか」を確認し、.to include("を入力してください")でエラー文を記述(今回のRailsを日本語化しているのでエラー文も日本語)
      end

      it "emailがない場合は登録できないこと" do
        user = build(:user, email: nil)
        user.valid?
        expect(user.errors[:email]).to include("を入力してください")
      end

      it "passwordがない場合は登録できないこと" do
        user = build(:user, password: nil)
        user.valid?
        expect(user.errors[:password]).to include("を入力してください")
      end

      it "encrypted_passwordがない場合は登録できないこと" do
        user = build(:user, encrypted_password: nil)
        user.valid?
        expect(user.errors[:encrypted_password]).to include("を入力してください")
      end

      it "family_nameがない場合は登録できないこと" do
        user = build(:user, family_name: nil)
        user.valid?
        expect(user.errors[:family_name]).to include("を入力してください")
      end

      it "family_name_kanaがない場合は登録できないこと" do
        user = build(:user, family_name_kana: nil)
        user.valid?
        expect(user.errors[:family_name_kana]).to include("を入力してください")
      end

      it "first_nameがない場合は登録できないこと" do
        user = build(:user, first_name: nil)
        user.valid?
        expect(user.errors[:first_name]).to include("を入力してください")
      end

      it "first_name_kanaがない場合は登録できないこと" do
        user = build(:user, first_name_kana: nil)
        user.valid?
        expect(user.errors[:first_name_kana]).to include("を入力してください")
      end

      it "birth_dayがない場合は登録できないこと" do
        user = build(:user, birth_day: nil)
        user.valid?
        expect(user.errors[:birth_day]).to include("を入力してください")
      end
  end
end

続いて、emailの一意性制約のテストを行います。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 省略

      # email 一意性制約のテスト ▼

      it "重複したemailが存在する場合登録できないこと" do
        user = create(:user) # createメソッドを使用して変数userとデータベースにfactory_botのダミーデータを保存
        another_user = build(:user, email: user.email) # 2人目のanother_userを変数として作成し、buildメソッドを使用して、意図的にemailの内容を重複させる
        another_user.valid? # another_userの「バリデーションにより保存ができない状態であるか」をテスト
        expect(another_user.errors[:email]).to include("はすでに存在します") # errorsメソッドを使用して、emailの「バリデーションにより保存ができない状態である場合なぜできないのか」を確認し、その原因のエラー文を記述
      end
  end
end

続いて、確認用パスワードがなければ登録できないテストを実行します。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 省略

      # 確認用パスワードが必要であるテスト ▼

      it "passwordが存在してもencrypted_passwordがない場合は登録できないこと" do
        user = build(:user, encrypted_password: "") # 意図的に確認用パスワードに値を空にする
        user.valid?
        expect(user.errors[:encrypted_password]).to include("を入力してください", "は7文字以上で入力してください")
      end
  end
end

ここまでで、以下のテストは完了しました。

# 完了したテスト▼

ニックネームが必須 # 完了
メールアドレスは一意である # 完了
メールアドレスは@とドメインを含む必要がある # 完了
パスワードが必須 # 完了
パスワードは確認用を含めて2回入力する # 完了
ユーザー本名が、名字と名前でそれぞれ必須 # 完了
ユーザー本名のフリガナが、名字と名前でそれぞれ必須 # 完了
生年月日が必須 # 完了

# これから実装するテスト▼

パスワードは7文字以上
ユーザー本名は全角で入力させる
ユーザー本名のフリガナは全角で入力させる

それでは、ここからは先ほど記述した正規表現のテストを行います。
まずパスワードは7文字以上のテストから。

起きて欲しいことと起きてほしくないことの両方をテストします。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 省略

      # パスワードの文字数テスト ▼

      it "passwordが7文字以上であれば登録できること" do
        user = build(:user, password: "1234567", encrypted_password: "1234567") # buildメソッドを使用して7文字のパスワードを設定
        expect(user).to be_valid
      end

      it "passwordが7文字以下であれば登録できないこと" do
        user = build(:user, password: "123456", encrypted_password: "123456") # 意図的に6文字のパスワードを設定してエラーが出るかをテスト 
        user.valid?
        expect(user.errors[:encrypted_password]).to include("は7文字以上で入力してください")
      end
  end
end

最後に全角入力と全角カナ入力のテストを行います。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 省略

      # 名前全角入力のテスト ▼

      it 'family_nameが全角入力でなければ登録できないこと' do
        user = build(:user, family_name: "アイウエオ") # 意図的に半角入力を行いエラーを発生させる
        user.valid?
        expect(user.errors[:family_name]).to include("は不正な値です")
      end

      it 'first_nameが全角入力でなければ登録できないこと' do
        user = build(:user, first_name: "アイウエオ") # 意図的に半角入力を行いエラーを発生させる
        user.valid?
        expect(user.errors[:first_name]).to include("は不正な値です")
      end

      # カタカナ全角入力のテスト ▼

      it 'family_name_kanaが全角カタカナでなければ登録できないこと' do
        user = build(:user, family_name_kana: "あいうえお") # 意図的にひらがな入力を行いエラーを発生させる
        user.valid?
        expect(user.errors[:family_name_kana]).to include("は不正な値です")
      end

      it 'first_name_kanaが全角カタカナでなければ登録できないこと' do
        user = build(:user, first_name_kana: "あいうえお") # 意図的にひらがな入力を行いエラーを発生させる
        user.valid?
        expect(user.errors[:first_name_kana]).to include("は不正な値です")
      end
  end
end

これで仕様書に沿った機能のテストは以上です。
最終的には以下のようになります。

user.spec.rb
require 'rails_helper'

describe User do
  describe '#create' do

      # 入力されている場合のテスト ▼

      it "全ての項目の入力が存在すれば登録できること" do
        user = build(:user)
        expect(user).to be_valid
      end

      # nul:false, presence: true のテスト ▼

      it "nicknameがない場合は登録できないこと" do
        user = build(:user, nickname: nil)
        user.valid?
        expect(user.errors[:nickname]).to include("を入力してください")
      end

      it "emailがない場合は登録できないこと" do
        user = build(:user, email: nil)
        user.valid?
        expect(user.errors[:email]).to include("を入力してください")
      end

      it "passwordがない場合は登録できないこと" do
        user = build(:user, password: nil)
        user.valid?
        expect(user.errors[:password]).to include("を入力してください")
      end

      it "encrypted_passwordがない場合は登録できないこと" do
        user = build(:user, encrypted_password: nil)
        user.valid?
        expect(user.errors[:encrypted_password]).to include("を入力してください")
      end

      it "family_nameがない場合は登録できないこと" do
        user = build(:user, family_name: nil)
        user.valid?
        expect(user.errors[:family_name]).to include("を入力してください")
      end

      it "family_name_kanaがない場合は登録できないこと" do
        user = build(:user, family_name_kana: nil)
        user.valid?
        expect(user.errors[:family_name_kana]).to include("を入力してください")
      end

      it "first_nameがない場合は登録できないこと" do
        user = build(:user, first_name: nil)
        user.valid?
        expect(user.errors[:first_name]).to include("を入力してください")
      end

      it "first_name_kanaがない場合は登録できないこと" do
        user = build(:user, first_name_kana: nil)
        user.valid?
        expect(user.errors[:first_name_kana]).to include("を入力してください")
      end

      it "birth_dayがない場合は登録できないこと" do
        user = build(:user, birth_day: nil)
        user.valid?
        expect(user.errors[:birth_day]).to include("を入力してください")
      end

      # パスワードの文字数テスト ▼

      it "passwordが7文字以上であれば登録できること" do
        user = build(:user, password: "1234567", encrypted_password: "1234567")
        expect(user).to be_valid
      end

      it "passwordが7文字以下であれば登録できないこと" do
        user = build(:user, password: "123456", encrypted_password: "123456")
        user.valid?
        expect(user.errors[:encrypted_password]).to include("は7文字以上で入力してください")
      end

      # email 一意性制約のテスト ▼

      it "重複したemailが存在する場合登録できないこと" do
        user = create(:user)
        another_user = build(:user, email: user.email)
        another_user.valid?
        expect(another_user.errors[:email]).to include("はすでに存在します")
      end

      # 確認用パスワードが必要であるテスト ▼

      it "passwordが存在してもencrypted_passwordがない場合は登録できないこと" do
        user = build(:user, encrypted_password: "")
        user.valid?
        expect(user.errors[:encrypted_password]).to include("を入力してください", "は7文字以上で入力してください")
      end

      # 本人確認名前全角入力のテスト ▼

      it 'family_nameが全角入力でなければ登録できないこと' do
        user = build(:user, family_name: "アイウエオ")
        user.valid?
        expect(user.errors[:family_name]).to include("は不正な値です")
      end

      it 'first_nameが全角入力でなければ登録できないこと' do
        user = build(:user, first_name: "アイウエオ")
        user.valid?
        expect(user.errors[:first_name]).to include("は不正な値です")
      end

      # 本人確認カタカナ全角入力のテスト ▼

      it 'family_name_kanaが全角カタカナでなければ登録できないこと' do
        user = build(:user, family_name_kana: "あいうえお")
        user.valid?
        expect(user.errors[:family_name_kana]).to include("は不正な値です")
      end

      it 'first_name_kanaが全角カタカナでなければ登録できないこと' do
        user = build(:user, first_name_kana: "あいうえお")
        user.valid?
        expect(user.errors[:first_name_kana]).to include("は不正な値です")
      end
  end
end

 テスコードの実行

テストを記述したアプリのディレクトリのターミナルにて下記コマンドを実行。

ターミナル.
$ bundle exec rspec         

すると下記画像のように処理が行われればテストが成功しています。
image.png

最後に

モデルの単体テストは基本文法と、エラーを発生させる処理と発生させない処理を把握し、それぞれのメソッドを理解することができれば新たに機能が追加された場合でもコードを記述できると思います。

何か気になる点がございましたらコメント欄にてお申し付けください!
最後までご覧いただきありがとうございました!

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

byebugの導入から使い方まで

Railsの勉強の際にデバッグツールは使っていますか?
デバッグツールとは、プログラミングにつきもののデバッグ作業をサポートしてくれるツールのことで、ステップ実行といって一行ごとにプログラムを実行してその時に宣言されている変数の中身をみながら動作を確認する事が出来ます。

それではRailsの開発に使っているbyebugについて紹介していきます。

まずはbyebugのgemをインストールして、デバックしたいコードに「byebug」というメソッドを仕込んでおくと、プログラムがそこを通過した時に止まり、デバッグモードが開始、対話形式でコマンドを打ち込んでおくと自由にメソッドを実行したり変数の値を確認したり出来ます。

インストール方法

インストールはGemfaileに

gem 'byebug'

を追記するだけです。 (railsをインストールしている場合はデフォルトでインストールされています)

追記したら

bundle install

でgemをインストールしてきます。

byebugの使い方

まずbyebugを開始するには、デバッグをしたいところにbyebugというメソッドを書いて上げます。
(今回の場所は例となります。)

スクリーンショット 2020-09-03 10.44.03.png

今回はstatic_pages/homeページの部分にbyebugを挿入してみます。

byebugを挟み込んだら、実際にメソッドを叩いてみます。

スクリーンショット 2020-09-03 10.45.14.png

このように処理が止まり入力プロンプトが出てきます。
ここでnextを入力してEnterを押すと

スクリーンショット 2020-09-03 10.46.37.png

次の行に移動しました。ここでhelpメソッドを調べる為に入力してEnterを押すと

スクリーンショット 2020-09-03 10.48.24.png

helpの中身を確認する事が出来ます。(変数の中身も見れます)
実際のデバッグ作業ではこのように変数の値やメソッドを実行してみて、期待通りの動きをしているかを確認していきます。

よく使うコマンド一覧

next       一行進む
continue   次のブレイクポイントに進む
step       メソッドの内部にステップインする
list       ソースコードを表示する
up         ソースコードの上を表示する
down       ソースコードの下を表示する

他にも使ってみたいコマンドがあればhelpを入力すると表示されるので確認してみて下さい♪

ここまで覚えてしまえば、あとは実際に書いてみて慣れるのが一番早いと思います。

まとめ

byebugの紹介をしましたが、いかがでしたでしょうか?
byebugではそれぞれの変数の型も見れるのでブラックボックス化しがちなプログラミングの細かいところを見れるようになり、開発スピードの高速化と一石二鳥です。

慣れればとても使えるツールだと思うのでぜひ使ってみてください♪

駆け出しエンジニアなので間違っている部分やわかりにくい点などあるかと思います。
その場合はコメントなど入れていただけるとこれからの成長につながりますのでよろしければお願いします。
【LGTM】を押していただけると飛び跳ねて喜びます。
Twitteの方でも呟いたりしておりますのでもしよろしければ遊びにきてください♪

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

Ruby始めました

こんにちは

今日から主にRubyに関して記事を書いていけたらなあと思います。

普段は検索して記事を見つけて勉強していましたが、メモ代わりに書き出していきたいと思います。

ruby.rb
puts "hello Qiita!"

うん、見やすい!

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

Rubyの基礎文法を簡単にまとめてみた

個人的に理解が浅い部分を抜粋してまとめています。
詳細はページ下部リンクからご覧ください。

ハッシュ

#key:valueをひとかたまりに格納する
fruits = {"a":"apple", "b":"grape", "c":"orange"} 
puts fruits
puts fruits[:a] #キー値で取り出す

#実行結果
{:a=>"apple", :b=>"grape", :c=>"orange"}
apple

条件分岐

if 条件A
  条件に一致した場合の処理
elsif 条件B
  条件Aと一致せず、条件Bに一致したの時の処理
else
  条件A, Bのどちらにも一致しない時の処理
end

インデント

rubyでは、tabキーでインデントはNG!

スペースキーでインデント作る!

繰り返し処理

for文

list = [1, 2, 3, 4, 5]
for item in list
    puts item
end

#実行結果
1
2
3
4
5

while文

a = 1
while a <= 10 do
    puts a
    a += 1
end

#実行結果
1
2
3
4
5
6
7
8
9
10

例外処理

begin

begin
実行するコード
rescue
例外が発生したときだけ実行されるコード
else
例外が発生しなかったときだけ実行されるコード
end

rescue

begin
実行するコード
rescue
例外が発生したときだけ実行されるコード
else
例外が発生しなかったときだけ実行されるコード
ensure
例外の有無にかかわらず 最後に実行されるコード
end

raise

raise エラーの種類

メソッド

#メソッド
def drinkServer(fruit)
    drink = fruit + 'ジュース'
    return drink
end

puts drinkServer('りんご')

#出力結果
りんごジュース

クラス

メソッドなどの処理全体のひとかたまり

#円についての処理をするクラスを宣言
class Circle #クラス名の最初は大文字

  def area_circle
    puts @radius * @radius * 3.14
  end

  #関数などが連続するときは間に空行を1行入れること
  def circumference
    puts @radius * 2 * 3.14
  end

  def radius=(radius)
    @radius = radius
  end
end

#インスタンスを生成
circle1 = Circle.new

#半径を入力
circle1.radius = 3

#関数を呼び出す
circle1.area_circle
circle1.circumference

継承クラス

class クラス名 < 継承したいクラス名

end

参考記事

初心者のためのRuby入門徹底ガイド【基礎からわかりやすく解説】 | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

とほほのRuby入門 - とほほのWWW入門

20分ではじめるRuby

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

Rails 6で認証認可入り掲示板APIを構築する #2 gitとrubocop導入

Githubに上げる

Rails 6で認証認可入り掲示板APIを構築する #1 環境構築
前回の続きから。
とりあえず動くところまでいったのでGithubにpushしましょう。

New repositoryを選択。

その後適当な名前を付けて作成。
他の人からソースコードを見られたくなければPrivateに。問題なければPublicに。

rep.png

Cloud9はデフォルトでgitが入っているのでそのままgitコマンドが使えます。

$ git add -A
$ git commit -m 'initial commit'
$ git remote add origin https://github.com/{YOUR_ACCOUNT}/{YOUR_REPOSITORY}.git
$ git branch -M master
$ git push -u origin master

上記コマンドのYOUR_部分は作ったばかりのリポジトリに書いてあるのでそこを参照してください。

キーペアを作成し、公開鍵をGithubに登録する

上記対応だと、pushするたびにGithubのユーザーIDとパスワードを聞かれます。

$ ssh-keygen -t rsa

Enterキー連打でキーペアを作ります。

$ cat ~/.ssh/id_rsa.pub 

で表示される文字列が公開鍵なのでコピーします。

Githubに移動し、右上の自分のアイコンから『Settings』→『SSH and GPG keys』→『New SSH key』と遷移。
タイトルは分かりやすいものを付けて、keyに先程の公開鍵を貼り付けて保存。

ただ、これでもpush時にまだIDパスを聞かれるはず。

$ git remote set-url origin git@github.com:{YOUR_ACCOUNT}/{YOUR_REPOSITORY}.git

とすればOK。
これで、次回以降pushやpull時にIDパスを都度聞かれなくなります。

参考:GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~

testディレクトリを消す

今回はRSpecを使ってテスト実装していくので、minitest用であるtestディレクトリを消します。

$ git rm -r test/

rubocopを入れる

まずは静的解析してくれるrubocopを入れましょう。
というのもコーディング規約をチェックしてくれるgemなのですが、後から入れるとコーディング規約に反する指摘が大量に出て手がつけられなくなるためです。

Gemfile
...

+ gem 'rubocop-rails'

...
インストール
$ bundle

rubocop設定ファイルを作る

.rubocop.yml
AllCops:
  Exclude:
    - bin/*
    - db/schema.rb

# 日本語コメント許可
AsciiComments:
  Enabled: false

# ダブルクォーテーションに統一
StringLiterals:
  EnforcedStyle: double_quotes

アプリケーションのrootディレクトリ(/home/ec2-user/environment/bbs)に配置してください。
ファイル名の先頭にドット付けるのを忘れずに。

rubocop指摘箇所を直す

$ rubocop -a

実行すると40個前後のエラーを吐きますが、ほぼ全て以下エラーのはず。

C: Style/FrozenStringLiteralComment: Missing frozen string literal comment.

これは

各Rubyファイルの1行目に追記
+ # frozen_string_literal: true
+

これでOKです。
意味するところはRubyだと文字列がデフォルトでミュータブルだけど、意図しない不具合を生んだりするのでfreezeしてイミュータブルにするといいよね
Ruby3ではデフォルトでイミュータブルになることが決まっているよ、それに備えてこの記述を入れておけばファイル中の文字列がイミュータブルになるよ

的な感じです。

数十ファイルを開いて編集保存して…をやるのが面倒であれば、

# Gemfileの先頭に # frozen_string_literal: trueを追記
$ sed -i '1s/^/# frozen_string_literal: true\n\n/' Gemfile

のようにすればファイル先頭に追記ができます。
db/*のように指定するとディレクトリ一括もできます(ちょっと危険)。

元も子もない話をすると、チュートリアルの範囲ならrubocopでこのチェック無効にしちゃってもいいです。
その場合は

.rubocop.yml
...

+ Style/FrozenStringLiteralComment:
+   Enabled: false

と追記してください。
ただし前述の通りRuby3からはデフォルトでイミュータブルになるので、今後新規アプリケーションを作り始めるのであれば入れておくべきでしょう。

あとは以下エラーを潰すだけですね。

C: Style/Documentation: Missing top-level class documentation comment.

app/mailers/application_mailer.rb
app/models/application_record.rb
config/application.rb
の3ファイルで発生しているはずですが、クラスのドキュメンテーションが無い時に出るエラーです。

config/application.rb
 module Bbs
+  #
+  # 設定ファイル
+  #
   class Application < Rails::Application

のようにclass定義の前にコメントを追加しましょう。

全て修正し、

Inspecting 26 files
..........................

26 files inspected, no offenses detected

のようにエラーがなくなればOKです。

続きはまた明日

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

【RSpec】FactoryBotを使ってみよう【Rails】

 Factory Botとは??

  • テストのためのデータを簡単に作成できるライブラリ
  • Active Record を使う

 Factory Botのあれこれ

factory_bot_railsをインストールすると、下記のようなコマンドでfactoryを作成するためのファイルを作ることができます。

bin/rails g factory_bot:model task

少し解説します。

bin/rails gまでは大丈夫でしょう。factory_bot:modelでモデルに明示します。taskのように対象を指定してあげてください。

このコマンドを実行すると、spec/factories/tasks.rbに以下のようなファイルが生成されると思います。

spec/factories/tasks.rb
FactoryBot.define do

  factory :task do

  end
end

雛形ができましたね。この中にデータを定義していきましょう。

spec/factories/tasks.rb
FactoryBot.define do

  factory :task do
    name "お買い物"
    content "夕飯を買いに行く"
  end
end

少ないかもしれませんが、まあ大丈夫でしょう。 テストファイル内でFactoryBot.create(:task)と実行することで、spec/factories/tasks.rbに記述した内容のデータを作成することができます。

 FactoryBotを少しだけ使いこなす。

 オーバーライドさせる

例えば、tasknamenilだった時にエラーを出力させる異常系のテストを書きたい場合は、下記のように書けます。

spec/models/task_spec.rb
# nameがなければ、無効。
it "is invalid without a name" do
  task = FactoryBot.build(:task, name: nil)
  task.valid?
  expect(task.errors[:name]).to include("can't be blank")
end

プロパティを上書きしてnilにすることで、期待通りの形にしています。

 ユニークな値を扱う。

例えばメアドは唯一無二であるはずだし、そのような仕様であり、そのようなテストを書くべきです。しかし、FactoryBotは常に同じ値を参照するので、そのような事象に対応できない場合があります。

この問題をは下記のようにシーケンスを使うことで解決できます。

spec/factories/users.rb
FactoryBot.define do

  factory :user do
    name "山田"
    sequence(:email) { |n| "test#{n}@example.com" }
  end
end

このように定義することによって、test1@example.comtest2@example.comのように設定されます。

とても便利。

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