PFメモ日記(4) sorceryの復習
deviseを使おうと思ったけど、せっかくなのでsorceryの復習をしたい。
※ 内容ごちゃごちゃしてかなり分かりづらいです。
目次
- 1. sorceryをインストール
- 生成されたマイグレーションファイルにその他欲しい属性と制約を追加
- crypted_passwordとsaltとは?
- 2. バリデーション(長い)
- 条件付きバリデーションとは?
- Procとlambda(ラムダ)とは?
- procとlambdaは何が違うのか
- 条件付きバリデーション、->(lambda記法)続き
- バリデーションの条件詳細
- 3. コントローラ作成
- form_withの話
- 認証機能の補足
- !!←こいつは何なのか
- 感想
- 参考にしたサイト
1. sorceryをインストール
gem 'sorcery'
$ bundle exec rails g sorcery:install create app/models/user.rb create db/migrate/XXXXXXXXX_sorcery_core.rb
- Userモデルとマイグレーションファイルが生成されている
生成されたマイグレーションファイルにその他欲しい属性と制約を追加
# db/migrate/20220615092132_sorcery_core.rb class SorceryCore < ActiveRecord::Migration[6.1] def change create_table :users do |t| t.string :email, null: false, index: { unique: true } # ユニークインデックスは生成時に最初から記載されていた t.string :crypted_password t.string :salt t.string :name, null: false # name属性とNOTNULL制約を追加 t.timestamps null: false end end end
【メモ】 emailのユニークインデックスをadd_indexの書き方に変更したところ、emailに対してno method errorが出てしまったのでemailの箇所は生成された状態からいじらずにdb:migrateした。なんでエラーになったのかよく分からない。
crypted_passwordとsaltとは?
↓簡単に書くと
- crypted_password(暗号化されたパスワードという意味)
- saltとはハッシュ化する前のパスワード前後につける文字列のこと
どういうこと?🤔
→パスワードをそのままDBに保存してしまうのはセキュリティ的な面で危険。このためハッシュ関数というものに通し、暗号化することでその安全性を高めている。ただし、このままだとハッシュ関数から元のパスワード文字列を割り出せてしまうかもしれないので、関数に通す前にsalt
という文字列を結合した上でハッシュ関数に通すようにしている。
↓詳しく
関数とは
- 何かを入れると(引数として渡すと)、何かを返してくれる(戻り値)プログラムの部品のこと
ハッシュ関数とは
- 入力されたデータを「特定のルールに沿って」ぐちゃぐちゃにした値を返してくれる
- 同じものを入れれば同じハッシュ値が帰ってくる
- 適当な値に見えるが特定のルールに従って出力された値であり、デタラメな訳ではない
- ハッシュ値から元のデータは特定するのはほぼ不可能
- パスワードなどそのままDBに保存したらセキュリティ的にアウトなものをハッシュ関数に渡して出てきたハッシュ値を保存している
入力されたパスワードが正しいかチェックするときは、入力されたパスワードをハッシュ関数に入れ、出てきたハッシュ値とデータベースに保存されている値を比較する
このままだとハッシュ関数から元の値を割り出せてしまうかもしれない
- 先人がさらに強固にするために、入力された値の前後に適当な文字列をくっつけてからハッシュ関数に入れる方法を考えた
→この文字列こそが「salt(ソルト)」
https://wa3.i-3-i.info/word16974.html
★ Sorceryでは、パスワードの末尾にランダムな文字列を結合し、その文字列をsaltフィールドに記憶することでパスワードハッシュの安全性を高めている
2. バリデーション(長い)
sorcery:installで追加されたUserモデルにバリデーションを追加する。
まずSorceryのログイン機能において、どういったバリデーションが追加されているのか確認する。
↓Sorceryのチュートリアルに記載されているバリデーションの例
# app/models/user.rb class User < ActiveRecord::Base authenticates_with_sorcery! validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] } validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] } validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] } validates :email, uniqueness: true end
→このif: -> { new_record? || changes[:crypted_password] }
は何なのか
条件付きバリデーションとは?
特定の条件を満たす場合にのみバリデーションを実行したい場合があります。:ifオプションや:unlessオプションを使うことでこのような条件を指定できます。引数にはシンボル、ProcまたはArrayを使えます。:ifオプションは、特定の条件でバリデーションを行なうべきである場合に使います。特定の条件でバリデーションを行なうべきでない場合は、:unlessオプションを使います。
Active Record バリデーション - Railsガイド
- 特定の条件の時のみ検証を行うことらしい
- sorceryのバリデーションにおけるこれ
if: -> { new_record? || changes[:crypted_password] }
は、バリデーションにつけられた条件 - この条件の時だけバリデーション(検証)にかけてくださいね的なものだと思う
->
(lambda記法)とは
呼び出したい
Procオブジェクト
を:if
や:unless
で使うこともできます。Procオブジェクト
を使うと、個別のメソッドを指定する代わりに、その場で条件を書けるようになります。ワンライナーに収まる条件を使いたい場合に最適です。
Active Record バリデーション - Railsガイド
どういうこと?🤔
この辺り、そもそもProcやlambdaというものが分からないと先を理解するのが難しいと思った。
Procとlambda(ラムダ)とは?
【Ruby】ブロック・Proc・lambda を理解する - Qiita
↑こちらをかなり参考にしています。
Proc
- do~endや{}で表現されるブロックを持ち運びに便利なオブジェクトにしたもの
- Procはクラスであり、Proc.newでオブジェクトを作ることができる
- 上記で生成されたProcオブジェクトはcallで呼び出しができる
# ブロックをProc.newでオブジェクト化している proc = Proc.new { |food| p food } proc.call("りんご") #=> "りんご" # callで生成したProcオブジェクトを呼び出している # 引数を渡すこともできる
ブロックを引数で明示的に受け取る方法
def method(&proc) # ブロックをprocオブジェクトに変換している proc.call end method { p 'とまと' } #=> "とまと"
- メソッド呼び出し時にブロックを引数として渡している
- 受け取り側のメソッドは&から始まるブロック引数を使用することで、ブロックを明示的に受け取ることができる
(&proc)
では渡されたブロックがProcオブジェクトに変換されている
(Proc難しい)
lambda(ラムダ)
Procオブジェクトを作る方法の一つ。
lamd = lambda { |n| p n }
みたいな形でprocオブジェクトを生成できる
ちなみにギリシャ文字の第11文字であるΛ・λ
のことを言うらしい。
余談ですが、ラムダ技術部というYouTuberさんの名前の由来がよく分かりました。
procとlambdaは何が違うのか
引数チェック
- lambdaの場合、渡す引数の数が違うとArgumentErrorになる
- procの場合は、先頭から必要な数だけとって後は無視し、少ないと足りない部分にnilを割り当てる
proc = proc { |n| p n } proc.call( 'proc', 'lambda' ) lamd = lambda { |n| p n } lamd.call('lambda') #=> "lambda" lamd.call('lambda', 'proc') #=> wrong number of arguments (given 2, expected 1)
returnの挙動
- lambdaの場合、returnした後にメソッドに戻り、メソッドを最後まで実行する
- Proc.newの場合はreturn後にメソッド自体を抜けてしまう
def lambda_method proc = Proc.new { return p "りんご" } proc.call p "ごりら" end # lambdaの場合、return後はメソッドに戻り最後まで実行するので"ごりら"も出力されている method #=> "りんご" #=> "ごりら" def proc_method proc = Proc.new { return p "わかめ"} proc.call p "たまねぎ" end # procはreturn後にメソッド自体を抜けてしまうので、出力されるのは"わかめ"のみ proc_method #=> "わかめ"
まとめ
- いずれもブロックを持ち運びに便利なprocオブジェクトにしたもの
Proc.new {ブロック}
かlambda {ブロック}
でブロックをオブジェクト化できる(他にもやり方があるが省略)- 渡す引数が多い場合、Proc.newでは先頭から必要な分だけ取り後は無視され、足りない部分にはnilが当て嵌められる。lambdaでは引数の数が異なるとArgumentErrorになる
- return後の挙動として、Procではreturn後にメソッドを抜け、lambdaではreturn後にメソッドに戻り処理を最後まで実行する
条件付きバリデーション、->
(lambda記法)続き
validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
- 条件付きバリデーションはある条件下の時のみ検証を行う
->
(lambda記法)はProcの一種。Procオブジェクトの生成を短く書くことができる
unless: Proc.new { |a| a.password.blank? } ↓ unless: -> { |a| a.password.blank? }
★ lambda記法(->)を用いて { new_record? || changes[:crypted_password] }のブロックをProcオブジェクト化している。それをif:オプションに渡している。
- 条件付きバリデーションの引数にはシンボル、Array、Procが使えるのでいずれか使用
- Procオブジェクトを使うメリットとしてはその場で条件が書けること。シンボルの場合は個別のメソッドを指定するという形になる
- ワンライナーで収まる条件を場合はProcオブジェクトを使うといい
class Order < ApplicationRecord # シンボル使用の例 validates :card_number, presence: true, if: :paid_with_card? def paid_with_card? payment_type == "card" end end
バリデーションの条件詳細
if: -> { new_recrod? || changes[:crypted_password] }
↑これはどういう意味なのか
new_record? インスタンスメソッド
Active Recordの
new_record?
インスタンスメソッドを使うと、オブジェトが既にデータベース上にあるかどうかを確認できます。
Active Record バリデーション - Railsガイド
newメソッドで新しくオブジェクトを作成しただけでは、オブジェクトはDBに属していない。saveメソッドを呼ぶことで、オブジェクトは適切なDBのテーブルに保存される。
以下のコンソール結果で納得
irb> p = Person.new(name: "John Doe") => #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil> irb> p.new_record? => true irb> p.save => true irb> p.new_record? => false
保存する前のデータである場合にnew_record?の結果がtrueになる。ニューレコードである。つまりオブジェクトが新規作成されたものであれば、検証を通す。
changes[:crypted_password]
if: -> { new_record? || changes[:crypted_password] }
ユーザーがパスワード以外のプロフィール項目を更新したい場合に、パスワードの入力を省略できるようになる。
irb(main):010:0> user.first_name = "太郎" => "太郎" irb(main):011:0> user.changes => {"first_name"=>["たろう", "太郎"]} irb(main):012:0> user.changes[:first_name] => ["たろう", "太郎"] irb(main):013:0> user.changes[:crypted_password] => nil
色々調べてみたけど、どういう理屈でパスワードの入力が省略されているのか分からなかった。。
→パスワードの入力が省略されているというより、if: -> { new_record? || changes[:crypted_password] }
によって、そのuserオブジェクトが新規作成されたものか、変更を加えようとしているのがuserオブジェクトのパスワードなのかを判定し、そうならば前述の検証にかけるという仕組み
なのかな?と思った。
★ とりあえず、パスワード意外を編集したい場合にパスワード入力を省略できる記載なんだなと納得しておく。
validates :password, length: { minimum: 8 }, if: -> { new_record? || changes[:crypted_password] }
- パスワードの入力は8文字以上
validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] } validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }
confirmation:
バリデーションヘルパーの一つ- 二つのフィールドで受け取る内容が完全に一致する必要がある場合に使う
- このヘルパーによって、確認したい属性(この場合password)に「_confirmation」を追加した仮想の属性を作成される
ビューでは下記のようなフィールドを用意
<%= f.text_field :user, :email %> <%= f.text_field :user, :email_confirmation %>
- このバリデーションチェックは、password_confirmationがnilでない場合のみ行われる
- 確認を必須にするために、password_confirmation属性に
presence: true
を追加する - presence: trueは指定された属性が空でないこと(値がnilや空でもホワイトスペースでもないこと)を確認するため、内部でblank?メソッドを使っている
validates :email, uniqueness: true
- これはemailが一意であり、重複していないことを検証する
3. コントローラ作成
ユーザー新規作成コントローラ
$ bundle exec rails g controller users
ルーティング
Rails.application.routes.draw do # root_pathで飛ぶページは適当に作っておく root 'home#top' # ログイン用のuser_sessionsコントローラ get 'login', to: 'user_sessions#new' post 'login', to: 'user_sessions#create' delete 'logout', to: 'user_sessions#destroy' # user新規作成機能のresources resources :users, only: %i[new create] end
class UserController < ApplicationController def new @user = User.new end def create @user = User.new(user_params) if @user.save redirect_to login_path # 新規作成成功後はログイン画面に else render :new end end private def user_params params.require(:user).permit(:email, :password, :password_confirmation, :name) end end
新規作成フォーム
<!-- 新規作成はユーザーをテーブルに登録する関係でモデルと紐づいているのでmodel:に@userを渡す--> <%= form_with model: @user, local: true do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.text_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation %> <%= f.submit '登録' %> <% end %>
ログイン用コントローラ
$ bundle exec rails g controller user_sessions
class UserSessionsController < ApplicationController def new; end def create @user = login(params[:email], params[:password]) if @user redirect_back_or_to root_path, success: "ログインしました" else flash.new[:danger] = "ログインに失敗しました" render :new end def destroy logout redirect_to root_path, success: "ログアウトしました" end end
ログインフォーム
<h1>ログイン</h1> <%= form_with url: login_path, local: true do |f| %> <%= f.label :email %> <%= f.text_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.submit 'ログイン', class: 'btn btn-primary' %> <% end %> <%= link_to '登録ページへ', new_user_path %> <a href="#">パスワードをお忘れの方はこちら</a>
- ログインは新たに情報を登録するわけではないので、モデル(テーブル)と紐づいていない
- このためurl:オプションでパスを指定するだけでいい
- フォーム内容はHTTP POSTメソッドとして送信されるため、
post 'login', to: 'user_sessions#create'
のルートでcreateメソッドが実行されログインできる
form_withの話
modelオプション
- 基本的にform_withのオプションは
model:
を使用する - 引数にはモデルクラスのインスタンス(保存したいテーブルのクラスのインスタンス)を指定する
usersテーブルに新たにレコードを作成したい場合、下記のように記述
def new @user = User.new end # 新規作成したレコードなのでcreateアクションが動く def edit @user = User.find(params[:id]) end # 既存のレコードを取得しているためupdateアクションが動く
↑ここで作成したインスタンスをフォームに渡す
<%= form_with model: @user %>
- ここでモデル.newで新たに作成され、何も情報を持っていなければ自動的にcreateアクションへ
- findメソッドなどで作成され、既に情報を持っている場合はupdateアクションへ振り分けられる
url:オプションを使う場合
- モデルとフォームが紐づかない場合(例: ログインフォーム: user_sessions)
- createやupdateを行うコントローラとモデルが紐付かない場合
(model: @userと設定したが、別のコントローラに処理が遷移して欲しい場合) - @userのようなインスタンス変数を渡さずにurlオプションのみを設定した場合、入力ミスなどで編集処理が失敗した時はフォームへの入力値が一々消えてしまう。失敗しても元入力した値を残しておきたいのならmodelオプションにインスタンスを渡す
認証機能の補足
以下メソッドについて
- requre_login
- not_authenticated
- redirect_back_or_to
not_authencticated
- authenticatedは認証済みという意味。ということは認証済みでないユーザーに対して行われる処理
公式リファレンス
# The default action for denying non-authenticated users. # You can override this method in your controllers, # or provide a different method in the configuration. def not_authenticated redirect_to root_path end
- 認証されていないユーザーを拒否する
- コントローラーでこのメソッドを上書きできる
- または構成で別の方法を提供している
→ 認証されていないユーザーをroot_pathに飛ばす?
redirect_back_or_toメソッド
# used when a user tries to access a page while logged out, is asked to login, # and we want to return him back to the page he originally wanted. def redirect_back_or_to(url, flash_hash = {}) redirect_to(session[:return_to_url] || url, flash: flash_hash) session[:return_to_url] = nil end
- ユーザーがログインしていない状態であるページにアクセスした場合、まずログインするように促し、ログイン後はユーザーがアクセスしていたページに戻してあげるメソッド
require_login
# To be used as before_action. # Will trigger auto-login attempts via the call to logged_in? # If all attempts to auto-login fail, the failure callback will be called. def require_login return if logged_in? # ログインユーザーがいるならメソッドを終了する # ここは解釈が合っているのか分からない # たぶんredirect_back_or_toのリクエストを保存する過程なのかなと・・・ if Config.save_return_to_url && request.get? && !request.xhr? && !request.format.json? session[:return_to_url] = request.url end # 未ログインの場合以下の処理が実行され、結果not_authenticatedメソッドでリダイレクトが行われる send(Config.not_authenticated_action) end
- before_actionとして使用される
- logged_inの呼び出しを介して自動ログイン
def logged_in? !!current_user end
- current_userが存在するのか存在しないのかtrue/falseを返す
!!
←こいつは何なのか
結論:戻り値をtrueかfalseに揃える
「
!!
」の動作: すべてをtrue
かfalse
にする
ご存じのとおり、Rubyではnilとfalse以外のオブジェクトはすべてtrueとして扱われます。そして否定の論理演算子「!」はtrueとfalseを反転します。
- !nil #=> true
- !!nil #=> false
- !false #=> true
- !!false #=> false
- !!その他何でも #=> true 反転の反転は論理上何も変化をもたらしませんが、表現がtrueかfalseに揃えられるところがポイントです。
Rubyの否定演算子2つ重ね「!!」(double-bang)でtrue/falseを返す|TechRacho by BPS株式会社
適当に試してみた
irb(main):008:0> !nil => true irb(main):009:0> !!nil => false irb(main):010:0> !!true => true irb(main):011:0> !true => false irb(main):012:0> !!false => false irb(main):013:0> !false => true irb(main):014:0> !!"" => true irb(main):015:0> !!"あいう" => true irb(main):016:0> !"" => false
感想
サクッと復習するつもりが意外と分かっていないことが分かってモリモリになってしまった。。最初期の頃よりかは公式文書への抵抗感が少なくなって良かった。
参考にしたサイト
Simple Password Authentication · Sorcery/sorcery Wiki · GitHub
PFメモ日記(3) railsコマンドが実行できないエラー
deviseを使おうとgemをインストールし、bundle exec rails g devise:install
した際に下記のエラーが発生。
Calling
DidYouMean::SPELL_CHECKERS.merge!(error_name => spell_checker)' has been deprecated. Please call
DidYouMean.correct_error(error_name, spell_checker)' instead. You don't have net-smtp installed in your application. Please add it to your Gemfile and run bundle install /Users/ユーザー名/.rbenv/versions/3.1.2/lib/ruby/3.1.0/psych/visitors/to_ruby.rb:430:in `visit_Psych_Nodes_Alias': Unknown alias: default (Psych::BadAlias)
- deviseに起因するエラーではない
- 序盤に書いてある
DidYouMean::SPELL_CHECKRS
はruby3.1環境で出てくる警告文らしい - Rubyの3.3バージョンでは
DidYouMean::SPELL_CHECKRS
の使用が非推奨になり、削除されることの注意書きであり、Ruby3.3以前を使用している分には特に支障はない
↑thor
というタスク実行のためのコマンドを作成するgemに起因する注意書きらしく、updateすればなくなる旨が記載されていた。
後半のエラーについて
You don't have net-smtp installed in your application. Please add it to your Gemfile and run bundle install /Users/ユーザー名/.rbenv/versions/3.1.2/lib/ruby/3.1.0/psych/visitors/to_ruby.rb:430:in `visit_Psych_Nodes_Alias': Unknown alias: default (Psych::BadAlias)
net-smtp
がインストールされていないのでインストールする旨書かれている
→これはメール機能に関するgemらしく、今は使用する予定がないので飛ばした。必要になったらインストールしてみる。色々調べてみるとrailsコマンドが使えないのは、
visit_Psych_Nodes_Alias': Unknown alias: default (Psych::BadAlias)
の部分に起因する何からしい- psychというYAML解釈用のgemがあり、4系と3系で解釈法が変わったことによるエラーらしい
→ 3系でないとdatabase.ymlの解釈に失敗している
gemfile.lockを確認すると4系がインストールされていたのでgemfileにgem 'psych', ''~> 3.1
を記載してbundle installし直すと解決した
他に試したこと
rails6.1.4以降で解決されているとのことで、gemfileのrailsバージョンを書き換え、bundle updateしてみたが変化なし
Psychとは
Ruby本体のdefault gemでYAMLライブラリのバックエンド実装になります。YAML自体がPsychに依存する形で実装されています。 Ruby の YAML.load が非互換になる(かもしれない) - Secret Garden(Instrumental)
参考にしたサイト
thor
【Ruby】thorの仕組みを調べてみた DSL編 - サーバーワークスエンジニアブログ
psychのエラーに関して
エラーUnknown alias: defaultが出て、railsサーバを起動できない
psych::badalias: unknown alias: defaultで Railsコマンドが使えないを解決 - Qiita
PFメモ日記(2) rails new
githubでリポジトリを作り、README作成後、特に何も考えずにrails new
してgit branchが訳分からないことになってしまったのでお作法を元にやり直すことに。。
- Rubyは最新版を使用
- bundle initから始める(Gemfileが作成される)
- Railsも原則最新の安定版使用とのことだが、操作に不安があるため6系を使用
- Minitest、JSライブラリ使用の有無、デプロイ環境のことを考え、オプションを付与して
rails new
する
※ hotwireとやらを気にすると進まなくなりそうだったので深入りしないことにした
クライアント側のJavaScriptを最小限にするHotwire - ログミーTech
Rubyバージョン設定
① Ruby最新バージョン確認
https://www.ruby-lang.org/ja/downloads/
↑2022年6月現在の時点では3.1.2が最新の安定版
② インストール可能バージョン一覧を確認
$ rbenv install --list
※ 一覧に最新版が表示されなかった場合、下記コマンドでrbenvとruby-buildを更新する
$ brew upgrade rbenv ruby-build
③ 最新版をインストール
$ rbenv install 3.1.2
④ インストールされているか確認
$ rbenv versions
⑤ 最新版がインストールされていたので適用
$ rbenv local 3.1.2
⑥ ruby -v
でバージョンが適用されているか確認
rbenvを利用してRubyのバージョンを最新安定版にする - Qiita
「bundle init」してGemfileを作成する
$ bundle init
tree
コマンドで生成されているか確認
生成されたGemfileのRailsに関する記載を確認する
# gemfile # 下記の記載があるので任意のバージョンを指定する # 特に記載しない場合は自動的に最新の安定版がインストールされる gem "rails", "6.1.6"
7系の仕様に自信がないので6系にする
Gemfileを編集したらbundle install
rails newにオプションを設定する
【完全網羅】rails newの基礎からオプションまで(db, rspec) | 侍エンジニアブログ
↑オプション一覧の記載あり
- RSpecを使う前提でminitestが生成されないようにする
- Herokuでデプロイする前提でPostgreSQLを採用する
※ Herokuでデプロイする場合はMySQLよりも作業に楽になるらしい - JSライブラリを使用する前提でhotwireまたはturbolinksのインストールをスキップする
$ bundle exec rails new . -d postgresql --skip-test --skip-turbolinks
競合するファイルを上書きするかどうかで処理が止まるので、最初から-s
オプションで既にするファイルをスキップすればよかった。
エラーメモ
You don't have net-smtp installed in your application. Please add it to your Gemfile and run bundle install rails aborted! LoadError: cannot load such file -- net/smtp
net/smtp
とやらのエラーが出た
https://github.com/ruby/net-smtp
メール送信関連で必要なファイルらしい🤔
とりあえず今は必要ないから良いのかな??何かあれば、gemをイントールすることに。。
おまけ:アプリ共通で入れることが多いGem
Lint, コード解析
- rubocop
- rubocop-rails
- bullet
テスティングフレームワーク
デバッグ
- better_errors
- binding_of_caller
※ ruby3系から標準でdebug.gemが同梱されるため、あえてpry-railsなどを入れる必要がないとのこと
コード補完
- solargraph
その他
- annotate
PFメモ日記(1) DBからランダムに値を取ってくる
複数ある中からある一つをランダムで出力する機能を作りたい。
課題で作った掲示板アプリの機能を使って以下のメソッドを試してみる。
機能:掲示板に投稿されたコメントの中から一つランダムで出力する
- sampleメソッド
- whereメソッド
- offsetメソッド
sampleメソッド
配列の要素を1個(引数を指定した場合は自身の要素数を超えない範囲でn個)ランダムに選んで返します。クエリ:sample | るりまサーチ
board.comments.all.sample Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."board_id" = ? [["board_id", 39]] => #<Comment id: 48, body: "卵", user_id: 1, board_id: 39, created_at: "2022-06-10 08:28:18", updated_at: "2022-06-10 08:28:18">
デメリット
処理に時間がかかりすぎるらしい
offsetメソッド
テーブルからデータを取得する際、どのデータから取得するか(取得開始位置)を指定したい場合、offsetメソッドを使います。 【Rails】offsetメソッドで取得するデータの範囲を指定する方法を解説 | CODE MARINE
IT用語辞典
オフセット【offset】
ITの分野では、何かの位置を指し示す際に、基準となる位置からの差(距離、ズレ、相対位置)を表す値のことをオフセットということが多い。オフセットとは - 意味をわかりやすく - IT用語辞典 e-Words
モデル名.offset(2)
↑先頭は0から始まるため(インデックス番号)、引数にはそれに応じた番号を渡している
randメソッド
指定した範囲の中からランダムな値を返す。
board.comments.offset(rand(board.comments.count)).first (0.1ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."board_id" = ? [["board_id", 39]] Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."board_id" = ? ORDER BY "comments"."id" ASC LIMIT ? OFFSET ? [["board_id", 39], ["LIMIT", 1], ["OFFSET", 1]] => #<Comment id: 49, body: "いちご", user_id: 1, board_id: 39, created_at: "2022-06-10 08:28:23", updated_at: "2022-06-10 08:28:23">
whereメソッド
board.comments.where('id >= ?', rand(board.comments.first.id..board.comments.last.id)).first Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."board_id" = ? ORDER BY "comments"."id" ASC LIMIT ? [["board_id", 39], ["LIMIT", 1]] Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."board_id" = ? ORDER BY "comments"."id" DESC LIMIT ? [["board_id", 39], ["LIMIT", 1]] Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."board_id" = ? AND (id >= 50) ORDER BY "comments"."id" ASC LIMIT ? [["board_id", 39], ["LIMIT", 1]] => #<Comment id: 50, body: "納豆", user_id: 1, board_id: 39, created_at: "2022-06-10 08:28:28", updated_at: "2022-06-10 08:28:28">
id >= ?
の?
はプレースホルダーと呼ばれており、第二引数で指定した値に置き換えられる- 第二引数の
rand(board.comments.first.id..board.comments.last.id)
で取得した値が?に入る
デメリット
IDに抜けがあるとランダムの正確性に欠ける
まとめ
- 自分が試した中ではそもそものコメント総数が多くなかったので、大きな速度差がなかった
- 実際はwhere>offset>>sampleの順に速さが変わるらしい
- 正確性の面ではoffset>where
参考にしたサイト
https://izanagi-portfolio-site.com/blog/articles/3yg4y3i-ih_e/
Ruby学習まとめ
自販機をシミュレートしたコードを問題点を完全しながら、オブジェクト指向についての考え方を学ぶ。
名前は省略しない
引数にはiとkind_of_drinkが記載されており、このままだとiが何を指すのか分からない。
def buy(i, kind_of_drink) if i != 100 && i != 500 @change += i return nil end
名前は省略せずに、誰が見ても分かるようにする
def buy(payment, kind_of_drink) if payment != 100 && payment != 500 @change += payment return nil end
ValueObject
intやstringと言ったプリミティブ型(基本的な型)をラップする。
意味のある値に対して型を定義すること。
改善前のコードでは自販機クラス
のbuyメソッド
に、支払額として整数の数値をそのままと、Drinkクラスに直接記載されたドリンクの種類を渡していた(引数)。これを、それぞれCoinクラスとDrinkTypeクラスに分けて、enum型として定義しなおす。
一見面倒にしか思えなけれど、下記のメリットがある。
- コードの可読性を高める
- 変更に強い
- 拡張しやすい
https://codezine.jp/article/detail/10184
ファーストクラスコレクション
配列をラップしたクラスのことで、対象の配列に対する処理を全て集約したクラスになります。別名コレクションオブジェクトとも呼ばれるそうです。
メリット
- 配列の複雑さを専用の小さなクラスに集約できる
- これにより対象の配列に関する処理がプログラムのあちこちに散らばることなく管理ができる
- メンテナンス性の向上やプログラム自体の分かりやすさにつながる
GetterとSetter、プロパティを使用しない
GetterとSetter
【Ruby】「ゲッター」と「セッター」を理解する - Qiita
インスタンス変数そのものはクラス内からしか取得できない。これを外部でも取得できるように定義したのがゲッター。セッターも同様、更新はクラス内からしかできないため、外部から更新できるようにするためクラス内部に更新用のメソッドを定義する必要がある。
アクセスメソッド
ゲッター、セッターを定義しなくてもインスタンス変数の参照・更新を可能にする方法。
- attr_reader: 変数名…参照を可能にする。ゲッター。
- attr_writer: 変数名…更新を可能にする。セッター。
attr_accessor: 変数名…参照・更新の両方を可能にする。セッターとゲッターを一気に使える。
オブジェクト内部状態を使用した処理を実現したい場合は、オブジェクト内で処理し、その結果だけを返すようにする。
まとめ
- これらはオブジェクト指向のカプセル化を壊す不適切なメソッドになりやすい
- オブジェクト内部状態を使用した処理を実現したい場合は、オブジェクト内で処理し、その結果だけを返すようにする。(処理結果を返すメソッドをクラス内に定義すると言うこと)
- これにより、オブジェクトが管理する内容が変わっても、他のクラスやメソッドに影響することなく、クラスを変更することができる!
一つのクラスにつき、インスタンス変数は二つまで
※ あくまでもオブジェクト指向エクササイズの規約内容ではインスタンス変数を二つに絞るように書かれているが、同じ役割を一つのクラスにまとめることを考えること。
反対に、一つのクラスが複数の役割を担っている場合は、枠割ごとにクラスを分割することも検討する。
else句を使用しない
現状、自販機クラスのbuyメソッド内には「ジュース一本100円の時、投入された硬貨が100円ならば、お釣り管理オブジェクトに支払額を入れる。投入額が500円ならばお釣り管理から400円分のお釣りを取り出す処理をして、お釣り管理の在庫内を更新する。」みたいな処理が一緒くたに記載されている。
非効率で分かりづらいコード。
お釣り管理(coinmech)に対して、お支払額(payment)を渡すことで、後の内部処理はお釣り管理でするして、その結果のみ返すようにすれば、もっと見通しよく簡潔なコードが書ける。
「else句を使用しない」と言うのはそう言うこと。
関連が強いもの同士を同じパッケージにまとめる
お金関連ならmoneyディレクトリに、在庫関連なら在庫ディレクトリにまとめてファイルを見通し良く使おう。
アルゴリズムメモ
組み込み変数ARGV(argument vector)
【Rubyの基礎】コマンドラインからの実行で利用するARGVの基本 | ポテパンスタイル
Rubyプログラムを実行する際に、引数として指定したオプションをプログラムの中で利用するための仕組み。argumentは引数のことで、ARGVはプログラムを実行した際に指定された引数を格納する配列。
# sample.rb arg = ARGV[0].to_i
$ sample.rb 9
- sample.rbファイルをターミナルで実行。その際、引数として適当な数字を渡す
- 渡される引数はstring型になっているので、もし数値そして使いたいのであればto_iメソッドで変換する
times, upto, downtoメソッド
timesメソッド、uptoメソッド、downtoメソッド - 繰り返し - Ruby入門
いずれもIntegerクラスで用意されている
upto
# sample2.rb arg = ARGV[0].to_i 1.upto(arg) do |num| p num end
$ ruby sample2.rb 4 #=> 1 2 3 4
- uptoメソッドは
x.upto(y)
のように使用した場合に、xからyまでの数値を順にブロック内に代入していることがわかる
MySQLメモ
M1 Mac MySQLのインストール
課題に際してbundle installしようとするとエラーが出て上手くいかなかったため、調べた自分用メモです。間違った記載が多くあると思われるので、あまり参考にしない方が良いかも知れないです。
※ 記事を参考にコマンド実行する前に記事の鮮度・信頼性、本当に自分と同じ環境なのか考えてから実行するようにすること。
bundle install時に下記エラーが出ました。
34 warnings generated. 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/ユーザー名.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/mysql2-0.5.3 for inspection. Results logged to /Users/ユーザー名/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/extensions/-darwin-20/2.6.0/mysql2-0.5.3/gem_make.out An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling.
An error occurred while installing mysql2 (0.5.3), M1 Mac
↑こちらのブログを参考にインストールすることができました。
どうやら、デフォルトで使われているSSLに互換性がないためgem installで探し出すことができないとかなんとか・・
試したこと
- 互換性のあるopensslというものに変更
- シェルにopensslのパスを通して、反映させる
- bundle configのパスを変更
ライブラリのパスを変更
使用されているsslを確認
$ openssl version LibreSSL 2.8.3
- opensslのパス確認
$ brew --prefix openssl
bundle installがmysql2でコケるときに試すやつ
$ brew install openssl $ echo 'export PATH="/opt/homebrew/opt/openssl@3/bin:$PATH"' >> ~/.zshrc $ source ~/.zshrc $ bundle config --local build.mysql2 "--with-ldflags=-L/opt/homebrew/opt/openssl@3/lib" $ export LIBRARY_PATH=$LIBRARY_PATH:/opt/homebrew/Cellar/zstd/1.5.2/lib:/opt/homebrew/Cellar/openssl@3/1.1.1m/lib/
これでbundle installできるようにはなったけど、正直良く分かってない。。🥲
OpenSSLとは
インターネット上で標準的に利用される暗号通信プロトコルであるSSLおよびTLSの機能を実装したプログラムの一つ。他のソフトウェアに組み込んで使用するライブラリ(プログラム部品)となっており、オープンソースソフトウェアとして公開されている。OpenSSLとは - IT用語辞典 e-Words
まずSSL(Secure Sockets Layer...『安全な受け口』層)とは
「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典より
通信プロトコル(通信する際に双方で足並みを揃えるための約束事)の一つ。
インターネット上でやり取りする情報を暗号化して送受信するための仕組み。
SSLは、公開鍵暗号方式で共通鍵の受け渡しを行い、あとは受け渡した共通鍵を使って共通鍵暗号方式(秘密鍵暗号方式)でやり取りすることで、通信の安全性を高める仕組みです。
公開鍵暗号方式ってどんなもの?
クライアントがサーバに挨拶する。
🙋🏻♂️ < Hi! 🤖サーバはクライアントに
サーバ証明書
(自己紹介)を送る。このサーバ証明書の中にはサーバの公開鍵
(「これを使って暗号化して下さい」の鍵)が入っている。
💁🏻♂️ サーバ証明書📝🗝送る >🤖クライアントは
サーバ証明書
を送ってきたサーバが本物か確かめるために、認証局に問い合わせる。🏢< 本物です。 アレは本物ですか? >🤷🏻♂️ 🤖サーバが本物だった場合、サーバと秘密の会話をするためのルールである
共通鍵
を作成し、先ほど受け取ったサーバの公開鍵
に従って共通鍵
を暗号化する。
📄+🗝🙆🏻♂️(暗号化) 🔐✨暗号化された共通鍵(暗号化された共通ルール)をサーバに送る。
💁🏻♂️< 共通鍵渡すね 🔐🤖どうもサーバは、暗号化を解除する鍵であるサーバの秘密鍵を使って、暗号化された共通鍵を復号する。 🤖🔑 🔓✨(復号)
これにより、クライアントもサーバもやり取りするための共通鍵(ルール)を手に入れた。あとはこのルール(共通鍵)を使ってやり取りすることで第三者にやり取りを盗み見られたとしても、暗号化されているので何が書いてあるのか分からない。これが共通暗号方式(秘密鍵暗号方式)でやり取りをするということ。
🙆🏻♂️🔐✨⇄🤖🔐✨ ❌👿(くそぉ...)
TLSとは
SSLが1.0→2.0→3.0までバージョンアップし、次のバージョンアップでSSL4.0になると思いきや「TLS1.0」になったらしい。SSLの後継。(厳密には違うらしい)
このあとrails db:createしようとするも上手くいかない
これに関してはusernameとpasswordが記載されていないことが原因でした。
各自の環境に合わせてusernameとpasswordを修正するように指示があったので、初期設定のrootログインをしてみることに。
$ mysql -u root
パスワードを設定する
mysql> set password for root@localhost='新しいパスワード'; Query OK, 0 rows affected (0.00 sec)
MySQLにログインする
$ mysql -u root -p