20201120のRubyに関する記事は22件です。

文字列を配列に入れる方法

splitメソッド

文字列に使うことのできるメソッド。
文字列を分割して、配列に入れる事ができる。

str = "Ruby PHP"
ary = str.split('') #何で区切るか
#=>ary = ["Ruby","PHP"]

('')空白で区切ったり、(,)カンマで区切ったりを引数で指定できる。

配列にすることで、使う事ができるメソッドが変わる。
getsで入力されたものを配列に入れるときにも使える。

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

Railsを使ったToDoリストの作成(5.CRUDのCreate機能)

概要

本記事は、初学者がRailsを使ってToDoリストを作成する過程を記したものです。
私と同じく初学者の方で、Railsのアウトプット段階でつまづいている方に向けて基礎の基礎を押さえた解説をしております。
抜け漏れや説明不足など多々あるとは思いますが、読んでくださった方にとって少しでも役に立つ記事であれば幸いです。

環境

  • Homebrew: 2.5.10 -> MacOSのパッケージ管理ツール
  • ruby: 2.6.5p114 -> Ruby
  • Rails: 6.0.3.4 -> Rails
  • node: 14.3.0 -> Node.js
  • yarn: 1.22.10 -> JSのパッケージ管理ツール
  • Bundler: 2.1.4 -> gemのバージョン管理ツール
iTerm
$ brew -v => Homebrew 2.5.10
$ ruby -v => ruby 2.6.5p114
$ rails -v => Rails 6.0.3.4
$ npm version => node: '14.3.0'
$ yarn -v => 1.22.10
$ Bundler -v => Bundler version 2.1.4

第5章 CRUDのCreate(new/create)

第5章では、以下の機能を実装していきます。

  • 新規登録画面(newアクション)
  • 新規登録画面で入力された情報をデータベースに保存する機能(createアクション)

では、詳しく見ていきましょう。

1 newアクション

まずは、newアクションを使って新規登録画面を実装していきます。

1 コントローラ

まず、コントローラで新規登録された情報を保存するための空のインスタンスを作ります。ActiveRecordのnewメソッドを使っていきます。

app/controllers/boards_controller.rb
def new
    @board = Board.new
end

(name: ' ', description: ' ')のような空の箱がインスタンス変数に代入されているイメージです。

2 ビュー

次に、投稿フォームを作成していきます。

new.html.haml
.container
  %h2.form_title New Board
  = form_with(model: @board, local: 'true') do |f|
    %div
      = f.label :title, 'Name'
    %div
      = f.text_field :name, class: 'text'
    %div
      = f.label :description, 'Description'
    %div
      = f.text_area :description, class: 'text'
    %div
      = f.submit :Submit, class: 'btn-primary'

フォーム作成のポイントは2つあります。

  • form_withメソッドを使ってフォームを作成しています。form_withメソッドの引数はmodel: [モデルクラスのインスタンス], local: 'true'を指定します。local: 'true'を指定しない場合はAjaxによる送信になります。
  • フォームヘルパーを使って、ラベル・1行入力欄、複数行入力欄を作成しています。フォームヘルパーは以下のように指定します。
=f.[メソッド名] :[column], class: 'class名'

フォームヘルパーの種類について知りたい場合は以下の記事が参考になります。
* フォームヘルパーの種類と例
* Action View フォームヘルパー

3 一覧表示画面にリンクを貼る

最後に、一覧表示ページから新規登録ページに遷移できるようにリンクを貼っていきましょう。pathはrails infoの'boards#new'を参照してください。

app/views/boards/index.html.haml
= link_to new_board_path do
  .add_card
    %p + Add new board

以上で、新規登録画面の実装は完了です。

2 createアクション

では、次にcreateアクションを使って新規登録画面で入力された情報をデータベースに保存し、保存が成功したら詳細表示ページに遷移する機能を実装していきます。

1 コントローラ

コントローラで以下のように記述します。詳しい説明は後述します。

app/controllers/boards_controller.rb
def create
    @board = Board.new(board_params)
    if @board.save
        redirect_to board_path(@board), notice: 'Save successful'
    else
        flash.now[:error] = 'Could not save'
        render :new
    end
end

private
def board_params
    params.require(:board).permit(:name, :description)
end

Board.newでインスタンスを作成し、インスタンス変数に代入します。引数にはStrong Parameter(後述)を渡します。
ActiveRecordのsaveメソッドを実行し、作成したインスタンスをデータベースに保存します。

Q. newアクションで新しいインスタンスを作成していたのに、なぜcretaeアクションで再度作成しなければいけないのか?

A. Webアプリケーションでは、ブラウザからのリクエストのたびに新しくプロジェクトが実行されるため、newアクション(GETリクエスト)で作成した変数は利用することができません。そのため、今回はパラメータを渡して再度インスタンスを作成することが必要となってきます。

'render'と'redirect to'

renderはアクションに続けてビューを表示させる場合に使用します。
redirect_toはアクションを実行した直後にビューを表示せず、別のURLに遷移したい場合に使用します。
今回のケースではif文を使用して、インスタンスが保存された場合は詳細表示ページ(boards#show)に遷移するようにして、保存が失敗した場合は、現在の画面のビュー(boards#new)を表示するようにしています。
redirect_toの引数に@boardを指定しているのは、idを渡すためです。id: @board.idだと思ってください。

Strong Parameter

Strong ParameterはRailsのセキュリティに関する概念の一つです。board_paramsメソッドに記載している内容が該当します。
[更新する対象のモデル名_params]と命名する必要があります。
また、Strong ParameterはBoardsControllerクラスの外で使用することはないのでprivateメソッドを使用します。

表記 意味
params リクエストパラメータとして送られてきた情報が格納されているオブジェクト
.require(:board) オブジェクトの中にboardというキーが入っている必要があります
.permit(:name, :description) boardの中で、nameとdescriptionを保存する対象として許可します

ちなみに今回のparamsの状況は以下のようになっています。
スクリーンショット 2020-11-20 16.30.38.png
これを見ると、paramsはハッシュのようになっていてboardというキーとnameやdescriptionなどのデータが入ったバリューを持っていることがわかります。

Flashメッセージ

Flashは、リダイレクトやレンダーする際に、次のリクエストに対してちょっとしたデータを伝えるために用意された仕組みです。
Flashにはハッシュでデータを格納することができます。

redirect_toの場合は、第2引数にnotice: '[表示したいメッセージ]'を指定し、renderの場合はflash.now[:error] = '[表示したいメッセージ]'を指定することで設定することができます。
今回の場合は以下のようなハッシュが格納されています。

flash = { notice: 'Save successful', error: 'Could not save' }

ビューで以下のように設定することで画面に表示することができます。

- if flash.present?
  .flash
    - flash.each do |key, value|
      %div.key= value

これは何をやっているかというと、まず、present?メソッドを使ってflashにデータが入っているかどうかを確認します。if文を使ってtrueの場合は3行目と4行目を実行します。

2 モデル

モデルではvalidationの設定を行います。
validationとは、データの内容が正しいかどうかをチェックする仕組みのことを指します。

モデルのvalidationは、モデルに対応するオブジェクトをデータベースに登録・更新する前に検証を行い、エラーがあれば登録・更新処理をせずに差し戻しを行うという仕組みになっています。
今回のケースではsaveメソッドを実行しデータベースの登録を行う前に自動的に検証を行い、エラーがあればfalseが返ってきます。検証エラーの詳細はerrorsメソッドを使うことで確認することができます。

app/models/board.rb
class Board < ApplicationRecord
    validates :name, presence: true
    validates :description, presence: true
end

今回は、Railsがデフォルトで用意してくれている検証用のヘルパーを利用し必須のデータが入っていなければエラーが出るようにしています。
記述方法は以下の通りです。

validates :[column], presence: true

3 ビュー

ビューではvalidationの検証エラーのエラー文を取得して表示できるように設定していきたいと思います。
具体的には、まずerrorsメソッドを使って検証エラーの詳細が入ったオブジェクトを取得します。このオブジェクトにfull_messagesメソッドを使ってエラー文の全てを配列で取得します。さらにこれをeach文で表現することにより、各エラーがリストで表示されるようにします。

app/views/new.html/haml
%ul
  - @board.errors.full_messages.each do |message|
    %li= message

コンソールで確認すると以下のようになっています。
スクリーンショット 2020-11-20 19.58.44.png
errorsメソッドで検証エラーの詳細がわかります。
また、full_messagesメソッドでエラー文の全てが配列で取得できていることがわかります。
これをeach文を使ってリスト化しているため、検証に引っかかった文章を表示することができます。

以上で、新規登録機能の実装は完了です。

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

Python学習 基礎編 ~文字列を出力(表示)するには?~

こちらではPython学習の備忘録と、Ruby、JavaScriptとの比較も含め記載していきたいと思います。
プログラミング初心者や他の言語にも興味、関心をお持ちの方の参考になれば幸いです。

文字を出力(表示)するには?

共通部分
文字列は、必ず半角の「''」シングルクォーテーション、または「""」ダブルクォーテーションのどちらかで囲む必要がある。
記述がない場合はエラーになるので注意!

Python
script.py
print('Hello World')
Ruby
index.rb
puts 'Hello World'
JavaScript
script.js
console.log('Hello World');

文の最後は「;」セミコロンで終わりにする。


Pythonにてクォーテーションの記述がなかった場合
SyntaxError: invalid syntax

「構文エラー:無効な構文」というエラーが発生します。

そして、 今後、記述が多くなると上記エラーが発生する確率も高くなるのでその際は下記をチェックしてみましょう。

  • Pythonのコマンドや関数の名前を打ち間違えていないか?
  • カッコを閉じ忘れていないか?
  • 半角スペースのところが全角になっていないか?
  • for文, if文, 関数などの「:」を書き忘れていないか?
  • 「''」シングルクォーテーション、または「""」ダブルクォーテーションを忘れていないか?

おわりに

私自身「Python」の学習を始めたばかりなので、これからよろしくお願い致します!
また間違いがあれば遠慮なくご指摘願います!!


参考記事
SyntaxError: invalid syntaxとは何ですか?

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

AtCoder 茶色(400)になるまでにやったこと

こんにちは、yukad2です。
この記事では私がAtCoderでRating:400になるまでに勉強したことをまとめています。

0.初めてのコンテスト前

中学生の頃にC++ゲームプログラミングに挑戦しましたが、if, forが理解できる程度の知識でした。
大学1年で学習したRubyが(Cと比べて)非常に書きやすかったので、プログラミングの練習も兼ねて競技プログラミングを始めました。
1. chokudaiさんの記事を読んで、就活まで(2年以内)に緑~水色を目標にしました。
2. コードテストで入出力の確認をしました。
3. 過去問を何度か解きました。

1. コンテスト

1回目
3完 Perf:681
2回目
2完 Perf:244
3回目
2完 Perf:196

全然できない、と焦りを感じて過去問でアルゴリズムとデータ構造の基礎を勉強しました。

2. 勉強したこと

for 2重 3重

h = 3; w = 3;
arr=[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
h.times{|i|
    w.times{|j|
        puts arr[i][j]
    }
}

勉強したわけはないですが、必須なので。RubyなのでArray#eachの方をよく使っていました。

bit全探索

RubyではArray#combinationを使って実装しました。ベンチマークは取っていませんがABCの300点相当だとそこまで時間がキツイ問題は無いと思うので...

n = 5
arr=[1, 2, 3, 4, 5]
(n+1).times{|i|
     arr.combination(i){|item|
        p item
    }
}

最大公約数(GCD)

a = 3
b = 6
p 3.gcd(6)

最大公約数を求めるメソッドが標準にあります。(2020/11/21 追記)

約数列挙

n = 30
ans = []
(1..Math.sqrt(n).to_i).each{|i|
    if n % i == 0
        ans << i
        ans << n / i
    end
}
p ans.uniq.sort

$O(log(n))$
上記のコードは$\sqrt n$が整数の場合は$\sqrt n$が重複するので注意が必要です。(uniqで重複を消して出力)

Hash(連想配列)

all = Hash.new
all["hoge"] = 1
all["huga"] = 3
p all

知らなかったので全く戦えなかった問題がありました。

その他過去問

6問になってからのC問題を確実に解けるように練習しました。
過去問を解く際に、AtCoderProblemsを参考に上から順番に解いてました。
AtCoder Problems

3. 結果

9回目のコンテストで茶色になりました。間のブランクは大学とバイトが忙しすぎてほとんど問題は解いていなかったです。
次は緑目指してガンバリマス!
image.png

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

【Rails5】check_boxで任意の文字列を渡す方法(ガチ初学者向け)

初投稿です。
初学者のため、参考程度に見ていただければ幸いです。

至らない点、わかりにくい点などあればコメントで教えていただければ幸いです。

version

Ruby 2.5.7
Rails 5.2.4.4

How to

まずチェックボックスフォーム機能を実装する際に、デファルトだとか機能ようなコードになると思います。(form_withを使う場合)

<%= form_with model: 変数, url:遷移先のパス do |f| %>
   <%= f.check_box :カラム名,{オプション(クラス名など} %> ←チェックボックスを表示
   <%= f.submit %>
<% end %> 

このようにするとデフォルトで"1"か"0"が送られます。

<%= form_with model: 変数, url:遷移先のパス do |f| %>
   <%= f.check_box :カラム名, {オプション(クラス名など}, "true","fales" %> ←チェックボックスを表示
   <%= f.submit %>
<% end %> 

このように記述すればtrue,falesを渡すことができます。

<%= form_with model: 変数, url:遷移先のパス do |f| %>
   <%= f.check_box :カラム名, {オプション(クラス名など}, "attende","absence" %> ←チェックボックスを表示
   <%= f.submit %>
<% end %> 

同様にtrue,falesの部分を書き換えれば、任意の値を渡すことができます。

ポイントは、オプションの後ろに記述することです。
渡したい文字列の後ろにクラス名などのオプションを指定するとエラーが出ます。

簡単なようで意外と車学者がつまずくポイントかなと思い記事にさせていただきました。

誰かの参考になれば・・・

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

Kinx 実現技術 - JIT: sljit の使い方

sljit の使い方

はじめに

こんにちは。皆さん、お元気でしょうか。

今日は JIT Compiler の話です。Just In Time Compiler 技術です。技術の紹介をして皆の役に立とうという気持ちもありますが、半分は 自分のための備忘録 の意味もあります。なにせ、情報が少なくて元のソースコードを解析しながら使っているので。

Kinx では native 関数を定義できる機能があり、いわゆる JIT 実行させています。ここで Kinx って何?な方は下記をご参照ください。「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」で頑張っているアレです。

さて JIT です。JIT したいですよね。

そう、JIT はみんなやりたがるのですが、ここで 1 つ大きな問題があります。それは アセンブラは CPU ごとに違う ということです。

それを解決するのが sljit です。ここではその sljit が便利だったのでご紹介します。

...という趣旨で下書きしていたのですが、実は現在 MIR というものに夢中です。これに関してはまた別途記事にしていこうと思います。今回ひとまず、備忘録として sljit に関する本記事を残しておきます。

sljit とは

公式? 説明文書

私が知る限り、参考になる文書は以下程度しか見つかりませんでした。

参考にはなります。

GitHub のリポジトリは以下です。

サポート状況

上記によれば、以下のプラットフォームをサポートしているようです。

  • Intel-x86 32
  • AMD-x86 64
  • ARM 32 (ARM-v5, ARM-v7 and Thumb2 instruction sets)
  • ARM 64
  • PowerPC 32
  • PowerPC 64
  • MIPS 32 (III, R1)
  • MIPS 64 (III, R1)
  • SPARC 32

アセンブラを抽象化

sljit とは一言でいえば 抽象化アセンブラ であり、sljit 形式で書くことによってバックエンドで各 CPU 命令にトランスレートしてくれるのです。いやぁありがたい。

つまり、x86 だけ、とか x64 だけなら頑張ってできることも、ARM でも使いたいとか SPARC でも使いたいとか、うちは PowerPC メインなんで、とか言われたりすると、全部違う実装しなくてはなりません。ただでさえ面倒な JIT の実装なのに、やってられませんよね。それを吸収してくれるのが sljit です。

ということで、Kinx では sljit を使ってアーキテクチャの違いを吸収しようと試みているのでした。

ただし、x64 でしか試していません。

sljit の使い方(基本編)

ソースコード

ソースコードはここから入手できます。

$ git clone https://github.com/zherczeg/sljit.git

必要なのは、このリポジトリの sljit_src です。このフォルダ内のファイルだけマルっとコピーすれば OK です。

ビルド方法

ビルドも上記のファイルの中の sljitLir.h をインクルードして、sljitLir.c を一緒にコンパイルすれば OK です。その時、SLJIT_CONFIG_AUTO マクロを 1 に指定してください。アーキテクチャを自動的に選択してくれます。

つまり、sljit フォルダに先ほどのファイル一式を展開(コピー)した場合、以下のようになります。

$ cl /DSLJIT_CONFIG_AUTO=1 /I sljit ソースファイル.c sljit\sljitLir.c

gcc の場合は以下の通り。

$ gcc -DSLJIT_CONFIG_AUTO=1 -I sljit ソースファイル.c sljit/sljitLir.c

レジスタの制約

sljit レジスタの種類

レジスタには汎用レジスタと浮動小数演算レジスタの 2 種類があり、それぞれに Scratch レジスタSaved レジスタ というものがあります。

下記のソースコードの説明に従うと、Scratch レジスタとは一時的に使うレジスタで関数呼び出し後に保存されていない可能性があるもの、Saved レジスタとは関数呼び出し後であっても値が保持されているもの、となります。

/*
   Scratch (R) registers: registers whose may not preserve their values
   across function calls.

   Saved (S) registers: registers whose preserve their values across
   function calls.
*/

それぞれ、scratchsavedfscratchfsaved と区別されて名前付けされています。表にすると以下の通り。

- Scratch Saved
汎用レジスタ scratch
SLJIT_R0, SLJIT_R1, ...
saved
SLJIT_S0, SLJIT_S1, ...
浮動小数演算レジスタ fscratch
SLJIT_FR0, SLJIT_FR1, ...
fsaved
SLJIT_FS0, SLJIT_FS1, ...

ちなみに引数は SLJIT_S0SLJIT_S1SLJIT_S2 で渡されてくる決まりですが、後述する様々なアーキテクチャに対応するため、そしてそれぞれの呼び出し規約に対応するため、引数は 3 つまでしか対応していない とのことです。引数は 3 つまで。

Kinx では、ローカル変数領域に引数領域を取っておき、そこへのポインタを引数をとして渡すようにしています。これで複数の引数を疑似的に渡せます(ただし現状最大 32 個まで)。

様々なアーキへの対応

様々なアーキに対応させるには、最大公約数的な使い方をしなくてはなりません。つまり、使うレジスタの数は全てのプラットフォームで許されてる数までに制限して使います。

幸い、ソースコード上に以下のコメントがあるのでどこまで使えるかが分かります。

/*
   ...

   Note: On all supported architectures SLJIT_NUMBER_OF_REGISTERS >= 12
         and SLJIT_NUMBER_OF_SAVED_REGISTERS >= 6.
   ...
*/
/*
   ...
   Note: the following conditions must met:
         0 <= scratches <= SLJIT_NUMBER_OF_REGISTERS
         0 <= saveds <= SLJIT_NUMBER_OF_REGISTERS
         scratches + saveds <= SLJIT_NUMBER_OF_REGISTERS
         0 <= fscratches <= SLJIT_NUMBER_OF_FLOAT_REGISTERS
         0 <= fsaveds <= SLJIT_NUMBER_OF_FLOAT_REGISTERS
         fscratches + fsaveds <= SLJIT_NUMBER_OF_FLOAT_REGISTERS
   ...
*/

SLJIT_NUMBER_OF_SCRATCH_REGISTERS == SLJIT_NUMBER_OF_REGISTERS - SLJIT_NUMBER_OF_SAVED_REGISTERS なので、Scratch レジスタ x 6 個、Saved レジスタ x 6 個の 12 個までなら全てのプラットフォームで使える、ということです。

浮動小数演算レジスタのほう(float/double)のレジスタは、色々調べたところ、SLJIT_NUMBER_OF_FLOAT_REGISTERS が 6 で Saved が 0 or 1 だった(というか x64)ので、fscratch x 5、fsaved x 0 で考えておくのが良さそうです。

sljit を使うソースコードの構造

sljit でのコードジェネレーションでは、基本的に関数ポインタとして復帰してきます。つまり、関数のエントリポイントとリターンのコードを最初と最後につけます。テンプレートとしては、以下の通り。

sljit_create_compiler でコンパイラ・オブジェクトを作成し、sljit_emit_enter で関数の入り口を作成します。ここでは引数が 1 つ(SW は Signed Word)、ローカル変数領域は 0 バイトとしています(使わない例)。sljit_generate_code で実際のコードを作成して、先頭アドレスへのポインタを返します。

#include "sljitLir.h"

int main(void)
{
    /* コンパイラ・オブジェクトの作成 */
    struct sljit_compiler *C = sljit_create_compiler(NULL);

    /* 関数のエントリポイント */
    sljit_emit_enter(C, 0,
        SLJIT_ARG1(SW),         /* argument type */
        6,  /* scratch  : temporary R0-R5   */
        6,  /* saved    : safety    S0-S5   */
        5,  /* fscratch : temporary FR0-FR4 */
        0,  /* fsaved   : safety    -       */
        0   /* local    :                   */
    );

    /* コード作成部分 */
    /*
        ここに sljit でのアセンブラコードを書いていく
        尚、関数の引数は、SLJIT_S0, SLJIT_S1, ... と saved レジスタが使われる。
    */

    /* 復帰コード */
    sljit_emit_return(C, SLJIT_MOV, SLJIT_IMM, 0);

    /* コード生成 */
    void *code = sljit_generate_code(C);
    int (*func)(sljit_sw) = (int (*)(sljit_sw))code;

    /* 実際の関数呼び出し */
    int r = func(100);

    /* 後始末 */
    sljit_free_compiler(C);
    sljit_free_code(code);
    return 0;
}

簡単なサンプル

ここまでの前提知識と、いくつかの sljit のインターフェースを使って簡単なサンプルを作ってみましょう。さっきのテンプレートにコード作成部分を追加します。

サンプル(足し算プログラム)

ソースコード

3 つの引数を受け取って、全部足し合わせて結果を返します。やっていることは以下の通り。

  1. SLJIT_R0 レジスタに SLJIT_S0 レジスタと SLJIT_S1 レジスタを加えた値を代入します。
  2. SLJIT_R0 レジスタに、さらに SLJIT_S2 レジスタを加えた値を代入します。
  3. sljit_emit_returnSLJIT_R0 レジスタの値を返します。

また、以下の変更をしています。

  • sljit_emit_enter の指定で、関数内で使うレジスタ数を最小に(Scratch x 1、Saved x 3)。
  • sljit_emit_enter の指定で、引数を 3 つ受け取る(SLJIT_ARG1(SW) | SLJIT_ARG2(SW) | SLJIT_ARG3(SW))ように変更。
  • 関数プロトタイプも引数を 3 つ受け取る(int (*)(sljit_sw,sljit_sw,sljit_sw))ように変更。
#include "sljitLir.h"

int main(int ac, char **av)
{
    if (ac != 4) {
        return 1;
    }

    /* 計算に使う数値の入力 */
    int a = atoi(av[1]), b = atoi(av[2]), c = atoi(av[3]);

    /* コンパイラ・オブジェクトの作成 */
    struct sljit_compiler *C = sljit_create_compiler(NULL);

    /* 関数のエントリポイント */
    sljit_emit_enter(C, 0,
        SLJIT_ARG1(SW) | SLJIT_ARG2(SW) | SLJIT_ARG3(SW), /* argument type */
        1,  /* scratch  : temporary R0      */
        3,  /* saved    : safety    S0-S2   */
        0,  /* fscratch : temporary -       */
        0,  /* fsaved   : safety    -       */
        0   /* local    :                   */
    );

    /* コード作成部分 */
    sljit_emit_op2(C, SLJIT_ADD, SLJIT_R0, 0, SLJIT_S0, 0, SLJIT_S1, 0);
    sljit_emit_op2(C, SLJIT_ADD, SLJIT_R0, 0, SLJIT_R0, 0, SLJIT_S2, 0);

    /* 復帰コード */
    sljit_emit_return(C, SLJIT_MOV, SLJIT_R0, 0);

    /* コード生成 */
    void *code = sljit_generate_code(C);
    int (*func)(sljit_sw,sljit_sw,sljit_sw) = (int (*)(sljit_sw,sljit_sw,sljit_sw))code;

    /* 実際の関数呼び出し */
    int r = func(a, b, c);
    printf("r = %d\n", r);

    /* 後始末 */
    sljit_free_compiler(C);
    sljit_free_code(code);
    return 0;
}

sljit_add3.c というファイル名で保存し、コンパイルして実行してみましょう。Visual Studio (cl.exe) でのコンパイルの仕方は以下の通り。

$ cl /DSLJIT_CONFIG_AUTO=1 /I sljit sljit_add3.c sljit\sljitLir.c

$ sljit_add3.exe 100 101 102
r = 303

出力コード

ディスアセンブラが無いので、出力コードが良くわかりませんね。一部細工して生成されたコードを逆アセンブルできるようにした上で出力してみると以下のようになりました。

       0:   53                                          push rbx
       1:   56                                          push rsi
       2:   57                                          push rdi
       3:   48 8b d9                                    mov rbx, rcx
       6:   48 8b f2                                    mov rsi, rdx
       9:   49 8b f8                                    mov rdi, r8
       c:   4c 8b 4c 24 f0                              mov r9, [rsp-0x10]
      11:   48 83 ec 10                                 sub rsp, 0x10
      15:   48 8d 04 33                                 lea rax, [rbx+rsi]
      19:   48 03 c7                                    add rax, rdi
      1c:   48 83 c4 10                                 add rsp, 0x10
      20:   5f                                          pop rdi
      21:   5e                                          pop rsi
      22:   5b                                          pop rbx
      23:   c3                                          ret

関数のプロローグとエピローグでいくつかの push/pop がありますが、先の Saved レジスタを実現するためでしょう。レジスタ数を最大まで使うようにすると、もっと増えます。

もう少し JIT っぽいアプローチ

ソースコード

JIT コンパイルを使う意義というのは色々ありますが、即値 というのも重要な要素でしょう。コンパイルコードに直接値を埋め込んでコンパイルしてしまいます。

尚、ここでは sljit_emit_enter による関数定義とは別の方法を、紹介がてら使ってみます。

sljit には引数が無い場合、もっと簡単に関数定義するための sljit_emit_fast_enterSLJIT_FAST_RETURN を使う手段も提供されています。これは、先ほどのプロローグやエピローグを一切出力しません。

sljit_emit_fast_enter で指定したレジスタにリターン・アドレスを保存しておき、それを使ってリターンするようにするだけです。ただし、sljit_emit_enter の代わりに sljit_set_context する必要があります。使用するレジスタを明示するだけのようです。Saved レジスタの対処やローカル変数領域などは指定しても領域確保はしてくれませんが、sljit_set_context で指定した数以上のレジスタを使おうとすると assertion が入ります。

主に、JIT <=> JIT 間での簡易関数コールなどに使えます。ここには呼出規約も何もないので、好きなレジスタに値を設定して、コールすれば良いだけです。最小限のコードを出力してくれます。

ただ、ここでやってる使い方は結構危険かもしれないですね。R1/R0 レジスタが呼び出し元レジスタとして割り付けられてた場合、最悪クラッシュします。今回のケースでは多分大丈夫(ちゃんと見てないけど直観)。通常は JIT <=> JIT 間で使うのが良いですね。

こんな風に書けます。

int main(int ac, char **av)
{
    if (ac != 4) {
        return 1;
    }
    int a = atoi(av[1]), b = atoi(av[2]), c = atoi(av[3]);

    /* コンパイラ・オブジェクトの作成 */
    struct sljit_compiler *C = sljit_create_compiler(NULL);

    /* 関数のエントリポイント */
    sljit_set_context(C, 0, 0, 2, 0, 0, 0, 0); // SLJIT_R0 と SLJIT_R1 しか使わない.
    sljit_emit_fast_enter(C, SLJIT_R1, 0);     // SLJIT_R1 にリターン・アドレスを保存.

    /* コード作成部分 */
    sljit_emit_op2(C, SLJIT_ADD, SLJIT_R0, 0, SLJIT_IMM, a, SLJIT_IMM, b);
    sljit_emit_op2(C, SLJIT_ADD, SLJIT_R0, 0, SLJIT_R0,  0, SLJIT_IMM, c);
    /* 戻り値は SLJIT_R0 */

    /* 復帰コード */
    sljit_emit_op_src(C, SLJIT_FAST_RETURN, SLJIT_R1, 0); // SLJIT_R1 のアドレスにリターン

    /* コード生成 */
    void *code = sljit_generate_code(C);
    int (*func)() = (int (*)())code;

    /* 実際の関数呼び出し */
    int r = func();
    printf("r = %d\n", r);

    /* 後始末 */
    sljit_free_compiler(C);
    sljit_free_code(code);
    return 0;
}

動作は同じです。

$ cl /DSLJIT_CONFIG_AUTO=1 /I sljit sljit_add3.c sljit\sljitLir.c

$ sljit_add3.exe 100 101 102
r = 303
$ sljit_add3.exe 1000 1001 1002
r = 3003

なぜ 2 回やったかというと、動的コンパイルの良さがここに表れるからですね。次の逆アセンブルリストを見てみましょう。

出力コード

最初のコマンドラインではこうです。

       0:   5a                                          pop rdx
       1:   48 c7 c0 64 00 00 00                        mov rax, 0x64
       8:   48 83 c0 65                                 add rax, 0x65
       c:   48 83 c0 66                                 add rax, 0x66
      10:   52                                          push rdx
      11:   c3                                          ret

関数のプロローグやエピローグが無いのはもちろんですが、0x64, 0x65, 0x66 という値が直接埋め込まれています。コール直後はスタックにリターンアドレスがあるので、rdx レジスタに保存しておいて、最後にスタックにリターンアドレスをプッシュして ret します。ret 命令はスタックトップの値をアドレスと見立ててそこにジャンプする命令です。

では 2 回目の実行ではどうなるでしょう。

       0:   5a                                          pop rdx
       1:   48 c7 c0 e8 03 00 00                        mov rax, 0x3e8
       8:   48 81 c0 e9 03 00 00                        add rax, 0x3e9
       f:   48 05 ea 03 00 00                           add rax, 0x3ea
      15:   52                                          push rdx
      16:   c3                                          ret

コード自体が変わりました。こういうやり方ができるので、JIT は事前コンパイル(AOT)より速い場合があるのです。

おわりに

今回は sljit の関数の作り方でした。一部命令(ADD)も使いましたが、他にも命令は色々あります。また、プログラムを作るうえでは条件分岐は欠かせません。そういうのも含めて、使い方の記録を残しておこうと思います。

では、また続き(あるのかなー)をお楽しみに。

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

ruby 教訓

教訓まとめ

シンボルクラスとストリングクラスは同義と考える
hash = {"key" => "value"}
hash = {key: => "value"}

クラスを配列に入れ込むときは配列の要素はクラスです。

上手く説明できない。

class Task
  # コードを追加
  attr_accessor :id, :name, :content
  @@count = 0

  def initialize(**params)
    @name = params[:name]
    @content = params[:content]
    # @@count = @@count + 1
    # @id = @@count
    @id = @@count += 1
  end

  def info
    "タスクNo.#{@id} : #{@name} #{@content}"
  end
end

class Todo
  # コードを追加
  # initializeはインスタンス作成時に動くから、作成時にインスタンス変数等に引数を代入したい時に引数を定義する
  # 今回の場合は空配列を初期化するために実装しただけなので、引数を定義していない
  def initialize
    @tasks = []
  end

  # 作成後のインスタンスごとに引数を渡しながらメソッドを動作させるときはあらかじめ引数を用意しておく
  def add(task)
    @tasks << task
  end

  def info
    @tasks.each do |todoList|
      puts todoList.info 
    end
  end

  def delete(id:)
    # p @id
    # delete_task = @tasks.find{|task| task.instance_variable_get(:@id)==id}
    delete_task = @tasks.find { |task| task.id == id }
    @tasks.delete(delete_task)

    # p @tasks[0].instance_variable_get(:@id)
  end
end



task1 = Task.new(name: "洗濯", content: "8時までに干し終える")
task2 = Task.new(name: "仕事", content: "9時〜18時")
task3 = Task.new(name: "買物", content: "卵,ネギ")

todoList = Todo.new
todoList.add(task1)
todoList.add(task2)
todoList.add(task3)
todoList.info
puts "----"
todoList.delete(id:1)
todoList.info

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

rubyクラスを利用したアプリ

シンボルクラスとストリングクラスは同義と考える
hash = {"key" => "value"}
hash = {key: => "value"}

教訓

1.attr_accessor :id, :name, :content クラス外から参照、編集するために必要な処理
2. @@xxxはクラス変数。クラスを使うたびに定義される
3. @xxxはインスタンス変数。インスタンス毎に定義される
4. @tasks << task(TASKクラスを利用したインスタンス)ではインスタンス変数に、task(TASKクラスを利用したインスタンス)を配列の要素として代入している。※ハッシュを代入していると思っており、ハッシュのメソッドを探していた。
5. initializeはインスタンス作成時に動作する(主に変数の初期値設定)
6. クラス毎に用途を分けた方が良い。今回なら、TASKクラスはタスクを保存するため用途。TODOクラスはtaskインスタンスを扱う用途。

class Task
  # コードを追加
  attr_accessor :id, :name, :content
  @@count = 0

  def initialize(**params)
    @name = params[:name]
    @content = params[:content]
    @id = @@count += 1
  end

  def info
    "タスクNo.#{@id} : #{@name} #{@content}"
  end
end

class Todo
  # コードを追加
  # initializeはインスタンス作成時に動くから、作成時にインスタンス変数等に引数を代入したい時に引数を定義する
  # 今回の場合は空配列を初期化するために実装しただけなので、引数を定義していない
  def initialize
    @tasks = []
  end

  # 作成後のインスタンスごとに引数を渡しながらメソッドを動作させるときはあらかじめ引数を用意しておく
  def add(task)
    @tasks << task
  end

  def info
    @tasks.each do |todoList|
      puts todoList.info 
    end
  end
-----------ここをid:にどうやってtodoList.delete(id:1)の値を入れ込むかわからなくて苦労した。----
  def delete(id:)
    delete_task = @tasks.find { |task| task.id == id }
    @tasks.delete(delete_task)
  end
end
---------------------------------------------------------------------------------
task1 = Task.new(name: "洗濯", content: "8時までに干し終える")
task2 = Task.new(name: "仕事", content: "9時〜18時")
task3 = Task.new(name: "買物", content: "卵,ネギ")

todoList = Todo.new
todoList.add(task1)
todoList.add(task2)
todoList.add(task3)
todoList.info
puts "----"
todoList.delete(id:1)
todoList.info

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

[Rails]selfについての理解を深めたい[初心者]

はじめに

プログラミングの超初心者です。
selfへの理解が全然できていないと感じたので、アウトプットも兼ねて、学んだことをまとめます!
それではいきましょう!!

selfって何のこと?

オブジェクトそのものを指す特殊な変数です。

と、説明だけでは非常に分かりにくいですよね。
一緒に、どのように使用されているのか、確認していきましょう。

使用例

book.rb
class Book < ActiveRecord::Base
  def hoge                 #インスタントメソッド
  end

  def self.hoge            #クラスメソッド
  end

  def author               #インスタントメソッド
    self.hoge              #インスタンスメソッドのhogeが呼ばれる
    hoge                   #インスタンスメソッドのhogeが呼ばれる
    self.class.hoge
  end

  def self.author          #クラスメソッド
    self.hoge              #クラスメソッドのhogeが呼ばれる
    hoge                   #クラスメソッドのhogeが呼ばれる
  end
end

クラスメソッド(特異メソッド)とは?

さて、クラスメソッドとは一体、何を指しているのでしょうか?
ざっくりの認識では、クラス全体の中から何かを取り出す際に使用するものです。
一つ一つのインスタンスには紐づいておらず、クラスに対して働きかけるメソッド、とも言い換えられるでしょう。
後述する特異クラスと分ける形で、特異メソッドと呼ばれます。

メソッドの呼び出しには、必ずレシーバが必要になります。
クラスメソッドには、あらかじめレシーバとしてself(自分自身のオブジェクト)が指定されているので、クラスから直接呼び出せる仕掛けになっています。

インスタントメソッドとは?

では、インスタントメソッドとは何ぞや、となるでしょう。
簡単に説明すると、特定のインスタンスから、何かを取り出す際に使用されるもの、という認識でOKです。
逆に言うと、メソッド名にselfを付けないと、指定したオブジェクト(レシーバ)に対してしか、そのメソッドを使用することが出来ないということです。

特異クラスとは?

クラスメソッドの中で、別名特異メソッドと紹介していたのを覚えていますか。
実際は、クラスメソッドで毎回selfと記載すれば大丈夫なのですが、クラスメソッドが複数に渡る場合は、いちいちselfと打っていくのが面倒ですよね。
そこで使えるのが、特異クラスです。
使い方は、以下の通りです。

book.rb
class Book < ActiveRecord::Base
  class << Book
    def say_thanx
      puts "#{self}, thanx"
    end
  end
end

↑に、book = 'book'という条件を与えた上で、book.say_thanxを打ち込むと、book, thanxと出力されます。
全体の流れは、クラスメソッドと似通っていますが、<< Bookにより、bookオブジェクトの特異クラスを呼び出しているため、あくまでもオブジェクトに対しての特異メソッド、という定義です。

おわりに

自分でこの記事を書いていて、なんて初心者にはとっつきにくい概念なんだろうと思っています。
記事の中では、あくまで概念にしか触れていませんので、実際にアプリ制作で使用する際は、もっと実地に即した形での運用になると思います。
「なぜこの記載をしているのか」を大切にして、ただの丸暗記に終始することの無いよう、自戒を込めて執筆しました。

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

enumを使用して投稿の公開設定をする(二択式)

はじめに

やりたいこと

  • 投稿機能に公開ステータスを持たせる
  • デフォルトは非公開にする
  • 公開設定をセレクトボタンで選択させる

 やってみて

enum自体は非常に奥が深いモジュールだと感じましたが、私がやりたいことは簡単に実装できました。
ですので、この投稿は初心者でもすぐに実践できる内容になっていると思います。

参考

Rails公式リファレンス
Pikawaka <【Rails】enumチュートリアル

手順

DB設計

db/migrate/create_item.rb
class CreateItems < ActiveRecord::Migration[6.0]
  def change
    create_table :items do |t|
      t.string :title,        null: false
      t.string :tagbody
      t.string :url
      t.text :body,           null: false
      # statusカラム、integer型、deroult: 0 を指定
      t.integer :status,      null: false, default: 0
      t.integer :category_id, null: false
      t.references :user,     foreign_key: true

      t.timestamps
    end
  end
end

boolean型で作成することもできますが、2個以上の定数と紐付けたい場合はinteger型を選択。

Model

models/item.rb
enum status: { closed: 0, open: 1 }

デフォルトを非公開にするので「closed」という定数を「0」に設定しました。

Controller

通常通りストロングパラメーターとcreateアクションを定義します。

controllers/items_controller.rb
class ItemsController < ApplicationController

  def create
    @item = Item.new(item_params)
    if @item.save
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def item_params
    params.require(:item).permit(:title, :tagbody, :body, :url, :status, :category_id).merge(user_id: current_user.id)
  end

end

View

formの書き方

views/items/new.html.erb
<%= form_with model:@item, url: '/items', local: true do |f| %>

  <%= f.select :status, Item.statuses.keys.to_a, {}, { class:'item-open' } %>

<% end %>

解説

f.selectの使い方

<%= f.select 属性, 選択肢の集合, {オプション}, {HTMLオプション} %>

Item.statuses.keys.to_a について

enumで指定した定数はハッシュの形で配列になっています。
したがってstatuses(複数形).keys.to_a(配列を返すメソッド)の形で呼び出します。

実装例

投稿画面

画面収録 2020-11-20 16.42.42.mov.gif

DB

スクリーンショット 2020-11-20 16.44.41.png

数字で保存される

呼び出し方

公開記事だけをインスタンス変数に含める

controllers/item_controller.rb
class ItemsController < ApplicationController

  def index
    @items = Item.open.order('created_at DESC')
  end

end

おわりに

今回はenumを使用して二択のセレクトボックスからDBに保存する形を実践しました。
今後はより多くの選択肢を与えたり、enumを使用して検索・確認・更新などの機能を実装してみたいなと思います。

✔︎

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

【RSpec】letを使用するメリット

 最近、RSpecによるテストにおいて、インスタンス変数の代わりに「let」という機能を用いることができると知りました。

 そこで、自分もletを使ってみようと考えたのですが、
「letを使うとどんなメリットがあるの?」
という疑問が湧いたので、備忘録としてまとめることにしました。

 今回、参考にさせていただいた記事を一番下にまとめていますので、気になる方はそちらをご覧ください。

 同じような疑問を持っている方の参考になると幸いです。

letの書き方

 先ほども書きましたが、テストにおいて、インスタンス変数を「let」に置き換えることができます。

今回はFactoryBotを使用している場合の書き方を紹介します。

#インスタンス変数を使用した場合
before do
  @インスタンス変数名 = FactoryBot.create(:モデル名)
end

#↓以下のように書ける!

#letを使用した場合
let(:メソッド名) { FactoryBot.create(:モデル名) }

letを使用するメリットとは?

(以下の内容は、@junchitoさんの記事を参考にさせていただいています。書籍も含めて、本当にいつもお世話になっています)
参考:RSpecのletを使うのはどんなときか?(翻訳)

まず、letを使用するメリットについてですが、

(1) typoにすぐ気づくことができる。

* typoとは、「誤植」を意味するtypographical errorの略。簡単に言えばタイプミスのこと。

 「ユーザー登録に関するテスト」を例にして説明したいと思います。

以下は、Userモデルのファイルです。

app/models/user.rb
class User < ApplicationRecord
  # ユーザーのニックネームに関するバリデーション
  validates :nickname, presence: true
end

以下はUserモデルのテストデータを生成するFactoryBotのファイルです。

spec/factories/users.rb
#ユーザー登録のテストデータを生成
FactoryBot.define do
  factory :user do
    nickname { "Taro Tanaka" }
  end
end

以下がUserモデルの単体テストコードです。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'ユーザーの新規登録' do
    before do
      @user = FactoryBot.build(:user)
    end
    context '新規登録できない場合' do
      it 'nicknameが空では登録できないこと' do
        @user.nickname = nil
        @user.valid?
        expect(@user.errors.full_messages).to include("Nickname can't be blank")
      end
    end
  end
end

さて、ここでUserモデルの単体テストコードに、次のようなtypoがあったとします。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'ユーザーの新規登録' do
    before do
      @user = FactoryBot.build(:user)
    end
    context '新規登録できない場合' do
      it 'nicknameが空では登録できないこと' do

        # 「@user」のはずが、rが抜けて「@use」に間違えている
        @use.nickname = nil

        @user.valid?
        expect(@user.errors.full_messages).to include("Nickname can't be blank")
      end
    end
  end
end

このままターミナルで「bundle exec rspec」を実行すると、以下のようなエラーが出ます。

ターミナル
Failures:

  1) User ユーザーの新規登録 新規登録できない場合 nicknameが空では登録できないこと
     Failure/Error: @use.nickname = nil

     NoMethodError:
       undefined method `nickname=' for nil:NilClass
     # ./spec/models/user_spec.rb:15:in `block (4 levels) in <main>'

「Failure/Error: @use.nickname = nil」の行を注意して見ればtypoに気づくことができるかもしれませんが、そのまま見逃して
「あれ!? なんでNoMethodErrorが出たんだろう!?」
となりそうですよね。

そこで、インスタンス変数の代わりにletを用いてみましょう。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'ユーザーの新規登録' do

   # インスタンス変数の代わりにletを使用
   let(:user) { FactoryBot.build(:user) }

    context '新規登録できない場合' do
      it 'nicknameが空では登録できないこと' do

        # 「user」のはずが、rが抜けて「use」に間違えている
        use.nickname = nil

        user.valid?
        expect(user.errors.full_messages).to include("Nickname can't be blank")
      end
    end
  end
end

この状態でターミナルで「bundle exec rspec」を実行すると……

ターミナル
Failures:

  1) User ユーザーの新規登録 新規登録できない場合 nicknameが空では登録できないこと
     Failure/Error: use.nickname = nil

     NameError:
       undefined local variable or method `use' for #<RSpec::ExampleGroups::User::Nested::Nested_2:0x00007fbbec3ed7f8>
       Did you mean?  user
     # ./spec/models/user_spec.rb:15:in `block (4 levels) in <top (required)>'

 このように、
「'use'というメソッドは定義されていないですよ。正しくは'user'ではないですか?」
とNameErrorで教えてくれるわけですね。

これがletを使用する1つ目のメリット、「typoに気付きやすくなる」でした。

(2) 無駄な初期化の時間を無くすことができる

インスタンス変数を用いてテストを行う場合、beforeを用いてテストデータを作成します。冒頭でも紹介しましたが、以下のようにします。

#インスタンス変数を使用した場合
before do
  @インスタンス変数名 = FactoryBot.create(:モデル名)
end

このように、beforeを使用した場合、beforeは各example(各it '○○' do 〜 endのこと)の前に実行されます。

その際、あるexampleで使われないインスタンス変数があっても、before内に定義されていれば、その不必要な変数も一緒に生成されてしまいます。
つまり、余計な時間がかかってしまうわけですね。

先ほどのUserモデルで例を出します。以下のように、before内に「@image」というインスタンス変数を作ったとしましょう。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'ユーザーの新規登録' do

   before do
    @user = FactoryBot.build(:review)
    @image = fixture_file_upload('public/images/test.jpg')
  end

    context '新規登録できない場合' do
      #必要なインスタンス変数は@nicknameのみ
      it 'nicknameが空では登録できないこと' do
        @user.nickname = nil
        @user.valid?
        expect(@user.errors.full_messages).to include("Nickname can't be blank")
      end
    end

      it 'imageが空では登録できないこと' do
     #必要なインスタンス変数は@imageのみ
        @image = nil
        @image.valid?
        expect(@user.errors.full_messages).to include("Image can't be blank")
      end
  end
end

この時、
「'nicknameが空では登録できないこと'のテストに必要なインスタンス変数は@userのみ、@imageはいらない」
「'imageが空では登録できないこと'のテストに必要なインスタンス変数は@imageのみ、@userはいらない」
となっています。
しかし、それに関わらず、どのexampleでも、@user@imageの両方が生成されてしまうわけです。

今回のような例なら大した問題になりませんが、これが
「exampleが何百とある」
「インスタンス変数を何個も作っている」
というような大規模なテストだった場合、「使わないインスタンスを生成する」という行為を何百回と繰り返すわけですから、無駄な時間がかかってしまうわけですね。

そこで、インスタンス変数の代わりにletを使えば、

「'nicknameが空では登録できないこと'をテストするのに必要な'userメソッド'のみを生成する」

「'imageが空では登録できないこと'をテストするのに必要な'imageメソッド'のみを生成する」

といったことが働きをしてくれるので、「不要なインスタンス変数を作成する」という無駄な時間を削ることができます。このように、letで定義されたメソッドを使うと、そのメソッドを呼び出した時のみ初期化処理を実行してくれます。

以上、2つ目のメリット「無駄な初期化の時間を無くすことができる」でした。

(3) ローカル変数をそのままletに置き換えることができる

 ここでのローカル変数とは、example内(it '○○' do 〜 end)で定義した変数のことです。ローカル変数は、そのexample内でしか使うことができず、他のexampleでは使うことができません。

 このローカル変数のシンタックス(構文、書き方)を変更することなく、そのままletに置き換えることができます。

例えば、「重複したニックネームを保存することはできない(他のユーザーと同じニックネームは使えない)」というバリデーションを設定した上で、そのことをインスタンス変数を使ってテストします。

spec/models/user_spec.rb
before do
    @user = FactoryBot.build(:review)
end

it '重複したnicknameが存在する場合登録できないこと' do
    @user.save
    another_user = FactoryBot.build(:user, nickname: @user.nickname)
    another_user.valid?
    expect(another_user.errors.full_messages).to include('Nickname has already been taken')
end

この「another_user」がローカル変数に該当しますね。
それでは、インスタンス変数をletに置き換えてみます。

spec/models/user_spec.rb
let(:user) { FactoryBot.build(:user) }

#(他のexampleは省略)

it '重複したnicknameが存在する場合登録できないこと' do
    user.save
    another_user = FactoryBot.build(:user, nickname: user.nickname)
    another_user.valid?
    expect(another_user.errors.full_messages).to include('Nickname has already been taken')
end

このように、example内では、@userの@を削除するだけで済みました。

以上、3つ目のメリット、「ローカル変数をそのままletに置き換えることができる」でした。

(4) テストが読みやすくなる

 主観的な話になるみたいですが、「letを使った方が、テストが読みやすくなる」 と、伊藤淳一さんもおっしゃっています。
参考:RSpecのletを使うのはどんなときか?(翻訳)

参考にさせていただいた記事

使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」

RSpecのletを使うのはどんなときか?(翻訳)

 いかがでしたか?
 僕自身、RSpecを本格的に学んで間もないため、至らない説明があるかもしれません。
 ご指摘などありましたら、教えていただけますと幸いです。

ここまで読んでいただき、ありがとうございました!

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

rubyzipでディレクトリが圧縮できない事がある不具合を解決する

Ruby on Rails でディレクトリをzipで圧縮しようとしたときに遭遇したエラーのお話です。
rubyzipというgemを使って実装する際、ディレクトリをzip圧縮するサンプルスクリプトが含まれていたので、
その通り導入してみたのですが...

サンプルスクリプトでは圧縮できなかった

大きく以下の問題でエラーが発生しました。(箇条書きですみません...)

  1. 日本語のディレクトリ名を使った場合に文字化けする
  2. ディレクトリの合計容量が大きかった場合に、処理が途中でコケる

調整後のスクリプト

zip_file_generator.rb
require 'zip'

# This is a simple example which uses rubyzip to
# recursively generate a zip file from the contents of
# a specified directory. The directory itself is not
# included in the archive, rather just its contents.
#
# Usage:
#   directory_to_zip = "/tmp/input"
#   output_file = "/tmp/out.zip"
#   zf = ZipFileGenerator.new(directory_to_zip, output_file)
#   zf.write()
class ZipFileGenerator
    # Initialize with the directory to zip and the location of the output archive.
    def initialize(input_dir, output_file)
        @input_dir = input_dir
        @output_file = output_file
    end

    # Zip the input directory.
    def write
        entries = Dir.entries(@input_dir) - %w(. ..)

        Zip.unicode_names = true
        ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
            write_entries entries, '', zipfile
        end
    end

    private

    # A helper method to make the recursion work.
    def write_entries(entries, path, zipfile)
        entries.each do |e|
            zipfile_path = path == '' ? e : File.join(path, e)
            disk_file_path = File.join(@input_dir, zipfile_path)
            puts "Deflating #{disk_file_path}"

            if File.directory? disk_file_path
                recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
            else
                put_into_archive(disk_file_path, zipfile, zipfile_path)
            end
        end
    end

    def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
        zipfile.mkdir zipfile_path.encode('cp932')
        subdir = Dir.entries(disk_file_path) - %w(. ..)
        write_entries subdir, zipfile_path, zipfile
    end

    def put_into_archive(disk_file_path, zipfile, zipfile_path)
        zipfile.get_output_stream(zipfile_path.encode('cp932')) do |f|
            #f.write(File.open(disk_file_path, 'rb').read)
            IO.copy_stream(File.open(disk_file_path, 'rb'), f)
        end
    end
end

まとめ

文字化けのためcp932でエンコーディングを行い、
大きなファイルを全てメモリに蓄えてしまわないよう、ストリーミング処理に変更しました。
誰かの参考になれば。

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

【Rails】ジャンル&商品名の検索機能実装

前提

ジャンルの一覧表示の中で検索したいジャンルをクリック or 検索フォームに商品名を打ち込んで「検索」ボタンをクリックすることで、searchアクションを実行します。ジャンルor商品名の検索結果を遷移後のsearch.html.erbで表示するような機能を実装していきます。検索結果画面には、該当する商品のレコード内容(商品写真や商品名など)をeach文で表示する形になります。モデル同士のアソシエーションなどの部分は省いていきます。
通常の検索機能の実装がしたい方はこちらを参考にしてください(gemなし複数モデル対象の検索機能)

実装の条件、太文字は必須のカラムです(カラム名は違くても問題ありません、デフォルトでつくidカラムは一応記述しました)

・genresテーブル(PK:id、ジャンル名:name、ジャンル有効or無効:is_active(boolean))
・itemsテーブル(PK:id、ジャンルのid(FK):genre_id、商品名:name、その他、商品写真や商品説明など)

実装環境

ruby '2.6.3'
gem 'rails', '~> 5.2.4', '>= 5.2.4.3'
gem 'bootstrap-sass', '~> 3.3.6' (無くても可)
gem 'devise'(無くても可)
・bootstrapはtableタグや、class指定でレイアウトを整えています。
・deviseはヘルパーメソッドを時々使用していますが、記述しなくても検索機能への影響はありません。

実装後のイメージ

image.png
商品の一覧画面でジャンル検索枠内のジャンル名をクリックすると、写真の検索結果一覧画面に結果が表示されます。
商品の一覧画面で検索フォームに商品名(例:ケーキ)、検索方法を指定後「検索」を押すと写真の検索結果一覧画面に結果が表示されます。
検索結画面の写真にジャンル名を載せてないのでわかりずらいですが、「ケーキ」ジャンル(genre_id:1)で検索をした結果、itemsテーブルのgenre_idカラムが1の商品だけ表示されている状態です。
※筆者は検索結果画面(写真)にも検索フォームとジャンル検索のサイドバーを表示させています。

実装していきます!

1⃣コントローラー新規作成

$ rails g controller Search search

2⃣Viewに検索フォーム & searchアクションへのlink_toを記述

@genresはviewページのコントローラーで@genres = Genre.allで定義してください。
・検索に必要な情報であるvalue(検索ワード)とhow(検索方法)をコントローラーへ送信できるようにそれぞれ定義しています。
・form_with内では、フォームに打ち込まれた文字列をvalue、プルダウンメニューで選択された「検索方法」をhowとして送信できるように定義しています。
・ジャンル検索では、genre.nameをリンクにしてsearchアクションを実行するようにしてます。search_pathの第一、第二引数にvalueとhowを定義してコントローラーで必要な情報を受け取れるようにしています。valueにgenre.idを指定してる理由ですが、itemsテーブル内にgenre_idカラムが存在するからです。genre_idカラムが一致する商品だけ探し出す処理をコントローラーで定義します。

items/index.html.erb
<div class="name-search">
  <% if customer_signed_in? %>                         #deviseのヘルパーメソッドです、省いても問題ありません。
    <h5>商品名検索</h5>
    <%= form_with url: search_path, method: :get, local: true do |f| %>
      <%= f.text_field 'search[value]', size: "26x4" %><br>
      <span class="search-button col-xs-12 text-right">
        <%= f.select 'search[how]', options_for_select({ "部分一致" => "partical", "前方一致" => "forward", "後方一致" => "backward", "完全一致" => "match" }) %>
        <%= f.submit :"検索" %>
      </span>
    <% end %>
  <% end %>
</div>
<table class="table table-bordered genre-table">
    <thead>
      <tr>
        <th>ジャンル検索</th>
      </tr>
    </thead>
    <tbody>
    <% @genres.each do |genre| %>
      <tr>
        <td>
          <%= link_to genre.name, search_path('search[value]': genre.id, 'search[how]': "match") %>
        </td>
      </tr>
    <% end %>
    </tbody>
</table>

3⃣コントローラー作成

筆者は商品名検索機能を実装してからジャンル検索をどのように実装しようか悩み、初めはsearchアクションとは別のアクションとViewページを作成する必要があるかなぁ、、と悩みましたがitemsテーブルにgenre_idを持たせていることと、itemのnameは文字列(string)なのに対してgenre_idは整数型(integer)なので下記にある.orのwhereで取得できるのではないかと考えました。(商品名が1とかだとどうなってしまうのかは試してませんが、、)

search_controller.erb
class Customer::SearchController < ApplicationController
  def search
    @value = params["search"]["value"]         #データを代入
    @how = params["search"]["how"]             #データを代入
    @datas = search_for(@how, @value)          #def search_forを実行(引数に検索ワードと検索方法)
  end

  private

  def match(value)
    #.orを使用することで、valueに一致するカラムのデータをnameカラムとgenre_idカラムから探してきます。
    Item.where(name: value).or(Item.where(genre_id: value))
  end

  def forward(value)                              #forward以降は商品名検索の定義しかしてません。
    Item.where("name LIKE ?", "#{value}%")        
  end

  def backward(value)
    Item.where("name LIKE ?", "%#{value}")
  end

  def partical(value)
    Item.where("name LIKE ?", "%#{value}%")
  end


  def search_for(how, value)
    case how                     #引数のhowと一致する処理に進むように定義しています。
    when 'match'                 #ジャンル検索の場合はmatchで固定してるので、必ず'match'の処理に進みます。
      match(value)
    when 'forward'
      forward(value)
    when 'backward'
      backward(value)
    when 'partical'
      partical(value)
    end
  end
end

検索結果表示(search.html.erb)

後は検索結果を表示するだけです。@datasをeachで該当商品の数だけ繰り返し処理します。[ブロック変数data.カラム名]でitemsテーブルの情報を表示しています。

search.html.erb
<% if @datas.empty? %>
  <h2>該当する商品はありません</h2>
<% end %>
  <% @datas.each do |data| %>
    <div>
      <%= link_to item_path(data.id) do %>         #写真を商品詳細へのリンクにしていますが、必要なければ省略してください。
        <%= attachment_image_tag data, :image, size: "190x150", class: "img_hover" %><br>
      <% end %>
      <p><%=  data.name %></p><br>
      <p>¥<%= data.price_without_tax %></p>
    </div>
  <% end %>

以上で実装できたかと思います。ジャンル検索、商品名検索セットでの記述なので参考にならなかった方はすいません。

おまけ

筆者はwhere分の.orを思いつく前までは、def match(value)の部分でif文を使ってvalue == integerならgenre_idを探しに行かせようと定義してみたりしましたが結果的に色々失敗して(他に何種類か試しましたが)、上記の処理に行きつきました。なにかもっと効率的な処理があればコメントお願いします。

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

【Ruby】バイナリーサーチを用いた配列内の要素検索

配列内に任意の値が存在するかを確認するプログラムを作成します。
任意の値が配列内に存在しない場合は、「値は配列内に存在しません」と表示し、存在する場合は、配列の何番目にあるかを表示します。

今回のプログラムではバイナリーサーチという、配列を2分割した検索方法を用います。
例えば以下のような配列がある場合、中央値である「3」から左右と比較して検索する手法です。
これにより検索したい値が「1」であった場合、「4,5」は比較を行わないため処理速度の向上につながります。

ruby
array = [1, 2, 3, 4, 5]

回答例

ruby
def binary_search(array, number_of_elements, target)
  left = 0                       //配列の先頭の番号
  right = number_of_elements - 1 //配列の末尾の番号
  while left <= right            //配列番号が逆転するまで繰り返し
    center = (left + right) / 2  //中央値の配列番号の決定
    if array[center] == target   //配列の中央値が検索したい値であった場合
      return center
    elsif array[center] < target //配列の中央値<検索した値であった場合
      left = center + 1          //arrayの「1,3,5,6,9」を対象外とする設定
    else
      right = center - 1         //それ以外の場合、arrayの「9,10,13,20,26,31」を対象外とする設定
    end
  end
  return -1                      //何も当てはまらなかった(配列に存在しなかった場合の返り値)
end

array=[1,3,5,6,9,10,13,20,26,31]

puts "検索したい数字を入力してください"
target = gets.to_i
number_of_elements = array.length

result = binary_search(array, number_of_elements, target)

if result == -1
  puts "#{target}は配列内に存在しません"
else
  puts "#{target}は配列の#{result}番目に存在します "
end

解説

まずはメソッド外。
1. 配列「array」の準備
2. 任意の文字「target」の準備
3. 配列の要素数「number_of_elements」の準備
これら3つの変数を実引数に設定し「binary_serach」メソッドを実行しています。

続いてメソッド内。
1. 配列の先頭及び末尾の配列番号を「left」「right」に代入。
2. while文にて、この「left」「right」の値が逆転するまで繰り返しを行う設定。
3. 以下の記述で中央値の配列番号を取得しています。今回の配列の要素は偶数個であるため、「(0+9) / 2 = 4.5」となります。配列番号4.5は存在しませんが、rubyにおける計算式の小数点は切り捨てられるため、今回の配列における中央値は「array[4] = 9」となります。

ruby
center = (left + right) / 2

if文による条件分岐を行い、左右のどちらに値が存在するかを繰り返し判定します。
最終的に配列内に任意の値が存在しなかった場合は「return -1」という返り値を渡してメソッドを終了します。

最後に再びメソッド外。
1. 任意の値が存在しなかった場合は「-1」が戻ってくるので、存在しない旨のメッセージを出力。
2. 任意の値が存在した場合は正しい値が戻ってくるので、存在する箇所を出力。

バイナリーサーチ知らないと思いつかない方法だと思いますので、しっかり覚えておきたいです。
ご意見等ございましたらコメントをいただけますと幸いです。

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

Ruby oci8 で k:v形式のArrayでレコード取得

Ruby oci8 で oracle接続して各行のデータをk:v形式のArrayで取り出す話

#!/home/user/local/bin/ruby

require 'oci8'
oracle = OCI8.new('id','pw','service')
oracle.prefetch_rows = 100

result = []
sql = "select * from table_name"

oracle.exec(sql).fetch_hash(){|r| result << r.map{|colname,coldata| "#{colname} : #{coldata}"} }

fetch_hash()っていうふうに()を付けないとだめなのね。
レコードを普通にArrayで取り出したいときはfetch()
oci8は癖があるな。mysql2のほうが扱いやすい。

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

RubyエンジニアがPHPを勉強し始めるときに見る記事

この記事 is 何

RubyエンジニアがPHPを勉強し始めるときに見る記事です。

動機

今までRubyをやってきていて、PHPを使うことになったというケースは少なくないと思っています。
特に実務未経験の方々では、このケースが多いのではないでしょうか?
というのは、実務未経験が学習している言語は、Rubyがファーストチョイスである一方で、PHPを使う企業は少なくないためです。
ちなみに、僕もそのケースでした。
そういう方々が過度に不安を持ったりせず、スムーズに転向できるようにしたいと思い、この記事を書きました。

この記事の主なターゲット

  • 今までRubyを勉強してたが、PHPを使うことになった人
  • というか半年前の自分

この記事で書くつもりのないこと

  • 技術選定時・初学者が学ぶ言語としてRuby or PHPはどっちが良いのかということ
  • 網羅的・辞書的にRubyとPHPを比較した結果
  • RubyとPHPの文法の正確な対応関係

学習のし始め

RubyからPHPへの転向コスト

Rubyをちゃんと習得できた方であれば、転向・学習コストにおいて問題はほぼ起きないと思います。
文法を調べながらとりあえず書き始められる、というレベルに達するには半日〜3日程度の勉強で済むと思います。

文法を調べるときに何を使うか

php.net
PHPは公式ドキュメントが神なので、基本的にこのサイトを見ればPHPの文法的な解決してくれるはずです。

Screen Shot 2020-11-15 at 18.55.08.png

ググラビリティも良好で、「array_merge php」などと検索すれば公式ドキュメントが先頭に出てくれます。

本題

動作確認バージョン
$ php -v
PHP 7.3.11 ...

$ ruby -v
ruby 2.6.3p62 ...

基本的なオブジェクトの概念

出力

Ruby PHP
改行なし出力 print echo
型情報付き出力 p var_dump()
配列に対し改行付き出力 puts echo, PHP_EOL

Rubyのprintは、PHPでいうとechoに近いです。
Rubyのpは、PHPでいうとvar_dumpに近いです。

Rubyのputsは?と言われると、対応したものはありません。
echoPHP_EOLを組み合わせる必要があります。

objects/print.rb
print 'sample!'
# sample!

sample_array = [1, 'sample']
print sample_array
# [1, "sample"]

p sample_array
# [1, "sample"]

puts sample_array
# 1
# sample
objects/class.php
<?php
echo 'sample!';
// sample!

$sample_array = [1, 'sample'];
echo $sample_array;
// Array

var_dump($sample_array);
// array(2) {
//   [0]=>
//   int(1)
//   [1]=>
//   string(6) "sample"
// }

foreach ($sample_array as $value) {
    echo $value . PHP_EOL;
}
// 1
// sample

プリミティブ型のクラス

Ruby PHP
プリミティブ型は クラス ただの型
文字列への変換 to_s "{$変数}"
数値への変換 to_i intval()

RubyではString, Integer, Arrayといったプリミティブ型も全てがオブジェクトです。
一方、PHPにはプリミティブ型においてはクラスの概念はありません。

PHPではstringへの変換は、ダブルクウォートで囲った上で変数展開することによって実現します。
また、intに変換するにはintval()を使います。

objects/class.rb
int3 = 3

string3 = int3.to_s
p string3
# "3"

p string3.class
# String

re_int3 = string3.to_i
p re_int3
# 3

p re_int3.class
# Integer
objects/method.php
<?php
$int3 = 3;

$string3 = "{$int3}";
var_dump($string3);
// string(1) "3"

get_class($string3);
// Warning: get_class() expects parameter 1 to be object, string given in ...

$re_int3 = intval($string3);
var_dump($re_int3);
// int(3)

get_class($re_int3);
// Warning: get_class() expects parameter 1 to be object, int given in ...

値・オブジェクトに対する基本的な操作・変換

Ruby PHP
基本的な操作・変換は オブジェクトメソッド 標準関数の引数に渡す
文字数を調べる length / size mb_strlen()
配列を合計する sum array_sum()

Rubyでは値・オブジェクトに対する基本的な操作・変換はオブジェクトメソッドで実現します。
参考: class Object(Ruby 2.7.0 リファレンスマニュアル)

一方、PHPは標準関数が充実しており、その標準関数の引数として対象を渡すのが一般的です。

objects/method.rb
address = '千代田区千代田1-1'
puts address.length
# 10

fibonacci_numbers = [1, 1, 2, 3, 5, 8, 13]
puts fibonacci_numbers.sum
# 33
objects/method.php
<?php
$address = '千代田区千代田1-1';
echo mb_strlen($address);
// 10

$fibonacci_numbers = [1, 1, 2, 3, 5, 8, 13];
echo array_sum($fibonacci_numbers);
// 33

配列・ハッシュと、連想配列

Ruby PHP
配列の概念 配列 連想配列
連想配列の概念 ハッシュ 連想配列
配列の要素の取得 配列[インデックス] 連想配列[インデックス]
連想配列の要素の取得 配列[シンボル] 連想配列[キー]

Rubyでは配列と連想配列(ハッシュ)を使い分けます。
一方で、PHPはちょっと特殊で、連想配列しかありません。PHPにおける(連想配列でない)配列は、キーが数値である連想配列です。
また、Rubyでは連想配列のキーにはシンボルを使うことが多いと思いますが、PHPでは文字列をキーとすることが多いです。

objects/array.rb
days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
p days_of_week
# ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

puts days_of_week[0]
# Monday

fruits = {red: 'apple', purple: 'grape', yellow: 'lemon'}
p fruits
# {:red=>"apple", :purple=>"grape", :yellow=>"lemon"}

puts fruits[:red]
# apple
objects/array.php
<?php
$days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
var_dump($days_of_week);
// array(7) {
//   [0]=>
//   string(6) "Monday"
//   [1]=>
//   string(7) "Tuesday"
//   [2]=>
//   string(9) "Wednesday"
//   [3]=>
//   string(8) "Thursday"
//   [4]=>
//   string(6) "Friday"
//   [5]=>
//   string(8) "Saturday"
//   [6]=>
//   string(6) "Sunday"
// }

echo $days_of_week[0];
// Monday

$fruits = ['red' => 'apple', 'purple' => 'grape', 'yellow' => 'lemon'];
var_dump($fruits);
// array(3) {
//   ["red"]=>
//   string(5) "apple"
//   ["purple"]=>
//   string(5) "grape"
//   ["yellow"]=>
//   string(5) "lemon"
//}

echo $fruits['red'];
// apple

配列への操作

Ruby PHP
配列への追加 配列 << 新要素 配列[] = 新要素
連想配列への追加 配列[シンボル] = 新要素 配列[キー] = 新要素
繰り返し処理 each foreach
繰り返し処理(返り値使用) map array_map()
繰り返し処理内での行いたい処理 ブロック コールバック関数

配列への追加は、Rubyは<<によってできる一方、PHPでは配列[] = 新要素という書き方を使います。
連想配列への追加は、書き方的にほぼ違いは無いかと思います。

手続き的な繰り返し処理は、Rubyならeach, PHPならforeachです。
返り値を使用したいときはRubyならmap, PHPならarray_map()を使います。

繰り返し処理中に行いたい処理を表現するのは、Rubyならブロック、PHPならコールバック関数を使います。
array_map()はよく使う割にはちょっと複雑な見た目をしていますね。

objects/array_function.rb
sequence_numbers = [1, 2, 3, 4, 5]
sequence_numbers << 6
p sequence_numbers
# [1, 2, 3, 4, 5, 6]

fruits = {red: 'apple', purple: 'grape', yellow: 'lemon'}
fruits[:orange] = 'pumpkin'
p fruits
# {:red=>"apple", :purple=>"grape", :yellow=>"lemon", :orange=>"pumpkin"}

sequence_numbers.each do |number|
  multiple_of_3 = number * 3
  puts multiple_of_3
end
# 3
# 6
# 9
# 12
# 15
# 18

multiples = sequence_numbers.map do |number|
  number * 3
end
p multiples
# [3, 6, 9, 12, 15, 18]
objects/array_function.php
<?php
$sequence_numbers = [1, 2, 3, 4, 5];
$sequence_numbers[] = 6;
var_dump($sequence_numbers);
// array(6) {
//   [0]=>
//   int(1)
//   [1]=>
//   int(2)
//   [2]=>
//   int(3)
//   [3]=>
//   int(4)
//   [4]=>
//   int(5)
//   [5]=>
//   int(6)
// }

$fruits = ['red' => 'apple', 'purple' => 'grape', 'yellow' => 'lemon'];
$fruits['orange'] = 'pumpkin';
var_dump($fruits);
// array(4) {
//   ["red"]=>
//   string(5) "apple"
//   ["purple"]=>
//   string(5) "grape"
//   ["yellow"]=>
//   string(5) "lemon"
//   ["orange"]=>
//   string(7) "pumpkin"
// }

foreach ($sequence_numbers as $number) {
    $multiple_of_3 = $number * 3;
    echo $multiple_of_3 . PHP_EOL;
}
// 3
// 6
// 9
// 12
// 15
// 18

$multiples = array_map(
    function ($number) {
        return $number * 3;
    },
    $sequence_numbers
);
var_dump($multiples);
// array(6) {
//   [0]=>
//   int(3)
//   [1]=>
//   int(6)
//   [2]=>
//   int(9)
//   [3]=>
//   int(12)
//   [4]=>
//   int(15)
//   [5]=>
//   int(18)
// }

クラス関連

クラスは重要なので、重点的に解説します。

インスタンス変数=プロパティ

Ruby PHP
メンバ変数 インスタンス変数 プロパティ
privateなメンバ変数の定義 不要 private $変数;
publicなメンバ変数の定義 @変数に代入し、attr_accessor public $変数;
コンストラクタ def initialize function __construct()
メンバ変数への代入 @変数に代入 $this->変数に代入
メンバ変数の呼び出し . ->

Rubyのインスタンス変数は、PHPのプロパティに該当します。
Rubyはインスタンス変数の宣言は特に必要なく、コンストラクタで@変数に代入してしまえば完了です。
一方、PHPだと宣言が必要で、private $変数;のように定義します。

また、メンバ変数を公開可能にしたいのであれば、Rubyではattr_accessorを使う一方、PHPではpublic $変数によって定義します。

classes/Person.rb
class Person
  attr_accessor :name

  def initialize name, age
    @name = name
    @age = age
  end
end

person = Person.new '一郎', 10
p person
# #<Person:0x00007ff39f158750 @name="一郎", @age=10>

puts person.name
# 一郎

puts person.age
# Traceback (most recent call last):
# classes/Person.rb:16:in `<main>': undefined method `age' for #<Person:0x00007ff39f158750 @name="一郎", @age=10> (NoMethodError)
classes/Person.php
<?PHP

class Person
{
    public $name;
    private $age;

    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

$person = new Person('一郎', 10);
var_dump($person);
// object(Person)#1 (2) {
//   ["name"]=>
//   string(6) "一郎"
//   ["age":"Person":private]=>
//   int(10)
// }

echo $person->name;
// 一郎

echo $person->age;
// Fatal error: Uncaught Error: Cannot access private property Person::$age in ...

クラスの定数

Ruby PHP
定数の定義 大文字 const 大文字
クラス外からの定数の呼び出し クラス::定数 クラス::定数
クラス内からの定数の呼び出し 定数 self::定数

PHPだと定数はconstを付けて宣言します。
クラス外からの呼び出しは、RubyでもPHPでも同じ書き方です。
PHPだとクラス外からの呼び出しではself::をつけます。

classes/Sample.rb
class Sample
  TEST_CODE = 'test_code'

  def print_code
    puts "コードは#{TEST_CODE}です。";
  end
end

puts Sample::TEST_CODE
# test_code

sample = Sample.new
sample.print_code
# コードはtest_codeです。
classes/Sample.php
<?PHP

class Sample
{
    const TEST_CODE = 'test_code';

    public function printCode()
    {
        $code = self::TEST_CODE;
        echo "コードは「{$code}」です。";
    }
}

echo Sample::TEST_CODE;
// test_code

$sample = new Sample();
$sample->printCode();
// コードは「test_code」です。

インスタンスメソッド

Ruby PHP
publicメソッドの定義方法 ただのdef public function()
privateメソッドの定義方法 privateの下でdef private function()

Rubyではクラス内で何も行わずにdefをするとpublicメソッドとなり、privateの下で定義されるとprivateメソッドになります。
一方、PHPではメソッドの定義時に必ずpublic/privateを定義します。
(本当はもっと色々種類がありますが割愛)

classes/Product.rb
class Product
    def initialize price, cost
        @price = price
        @cost = cost
    end

    def print_judgment
        if calc_profit > 0
            puts 'この商品は利益が出ます!どんどん売りましょう!';
        else
            puts 'この商品は利益が出ません...売るべきではないかもしれません...'
        end
    end

    private

    def calc_profit
        @price - @cost
    end
end

product = Product.new 120, 100
product.print_judgment
# この商品は利益が出ます!どんどん売りましょう!

product =  Product.new 90, 100
product.print_judgment
# この商品は利益が出ません...売るべきではないかもしれません...

product.calc_profit
# Traceback (most recent call last):
# classes/Product.rb:30:in `<main>': private method `calc_profit' called for #<Product:0x00007fc9f19dc408 @price=120, @cost=100> (NoMethodError)
classes/Product.php
<?php

class Product
{
    private $price;
    private $cost;

    public function __construct($price, $cost)
    {
        $this->price = $price;
        $this->cost = $cost;
    }

    public function printJudgment()
    {
        if ($this->calcProfit() > 0) {
            echo 'この商品は利益が出ます!どんどん売りましょう!';
        } else {
            echo 'この商品は利益が出ません...売るべきではないかもしれません...';
        }
    }

    private function calcProfit()
    {
        return $this->price - $this->cost;
    }
}

$product = new Product(120, 100);

$product->printJudgment();
// この商品は利益が出ます!どんどん売りましょう!

$product = new Product(90, 100);
$product->printJudgment();
// この商品は利益が出ません...売るべきではないかもしれません...

$product->calcProfit();
// Fatal error: Uncaught Error: Call to private method Product::calcProfit() from context '' in ...

クラスメソッドとstaticメソッド

Ruby PHP
静的メソッド クラスメソッド staticメソッド
インスタンスメソッド インスタンスメソッド staticでないメソッド
静的メソッドの定義方法 self static function()
静的メソッドの呼び出し クラス::メソッド クラス::メソッド

Rubyにおけるクラスメソッドは、PHPではstaticメソッドに該当します。
逆に、インスタンスメソッドはstaicでないメソッドということになります。

Rubyでクラスメソッドを定義するときは、class << selfの中で定義することが多いと思います。
一方で、PHPではメソッドの定義ごとにstaticを付与することで定義します。

また、public/privateはどんなメソッドでも付けるので、staticメソッドにもpublic/privateを付けて定義します。
静的メソッドの呼び出しの書き方は、RubyとPHPとでほぼ差は無いかと思います。

classes/VertebrateAnimal.rb
class VertebrateAnimal
  class << self
    def is_vertebrate_animal group
      get_vertebrate_animals.include? group
    end

    private

    def get_vertebrate_animals
      %w[fishes amphibians reptiles birds mammals]
    end
  end
end

is_vertebrate_animal1 = VertebrateAnimal::is_vertebrate_animal 'fishes'
puts is_vertebrate_animal1
# true

is_vertebrate_animal2 = VertebrateAnimal::is_vertebrate_animal 'insects'
puts is_vertebrate_animal2
# false
classes/VertebrateAnimal.php
<?PHP

class VertebrateAnimal
{
    public static function isVertebrateAnimal($group)
    {
        $vertebrate_animals = self::getVertebrateAnimals();
        return in_array($group, $vertebrate_animals, true);
    }

    private static function getVertebrateAnimals()
    {
        return ['fishes', 'amphibians', 'reptiles', 'birds', 'mammals'];
    }
}

$is_vertebrate_animal1 = VertebrateAnimal::isVertebrateAnimal('fishes');
var_dump($is_vertebrate_animal1);
// bool(true)

$is_vertebrate_animal2 = VertebrateAnimal::isVertebrateAnimal('insects');
var_dump($is_vertebrate_animal2);
// bool(false)

ゲッターとセッター

Ruby PHP
ゲッター attr_reader getXX()
セッター attr_writer setXX()

Rubyにおけるattr_readerおよびattr_writerに該当するような文法は、PHPにはありません。
よってgetXX, setXXという命名で自作することになります。

classes/Capsule.rb
class Capsule
  attr_reader :element
  attr_writer :element

  def initialize element
    @element = element
  end
end

capsule = Capsule.new '有効成分'
puts capsule.element
# 有効成分

p capsule
# #<Capsule:0x00007f8d909ac660 @element="有効成分">

capsule.element = '毒'
p capsule
# #<Capsule:0x00007f8d909ac660 @element="毒">
classes/Capsule.php
<?PHP

class Capsule
{
    private $element;

    public function __construct($element)
    {
        $this->element = $element;
    }

    public function getElement()
    {
        return $this->element;
    }

    public function setElement($element)
    {
        $this->element = $element;
    }
}

$capsule = new Capsule('有効成分');
echo $capsule->getElement();
// 有効成分

var_dump($capsule);
// object(Capsule)#1 (1) {
//   ["element":"Capsule":private]=>
//   string(12) "有効成分"
// }

$capsule->setElement('毒');
var_dump($capsule);
// object(Capsule)#1 (1) {
//   ["element":"Capsule":private]=>
//   string(3) "毒"
// }

その他ハマりポイント

ライブラリの対応関係

Ruby PHP
ライブラリ管理 Gem Composer
テスト Rspec PHPUnit
Linter Rubocop PHP-CS-Fixerなど
コメント RDoc PHPDoc
フレームワーク Ruby on Rails Laravel, CakePHPなど

有名どころのライブラリなどの対応関係のみを記載しました。
Rubyと言ったらRails、Railsと言ったらRubyという印象ですが、PHPではフレームワークは複数の選択肢があります。
シェアが高いという意味であればLaravelが近く、MVCの思想に忠実という意味ではCakePHPに近いかなという印象を持っています。

真偽判定・nil/null判定

Ruby PHP
== そこそこ使う 絶対に使わない
Null nil null
オブジェクトのNull確認 ! empty()

PHPの==はあまりにもヤバいことで有名だと思いますので、比較のときには必ず===を使うようにします。
Rubyでのnilは、PHPだとnullに該当します。

Rubyでは全てがオブジェクトであり、真偽値判定・nil判定がうまく設計されていて、オブジェクトのみで直感的に真偽値判定・nil判定をできることも多いです。
一方、PHPの場合は直感的に真偽値判定・null判定を行うのは危険なので、型を意識して判定した方が安全です。
オブジェクトがnullや空かどうかを確認するなら、empty()を使うのが有効です。

others/User.rb
class User
  class << self
    def find_by_id id
      # IDが1のときはユーザオブジェクトを返し、それ以外のときはユーザオブジェクトを返さないとします
      if id == 1
        User.new '最初のユーザ'
      else
        nil
      end
    end
  end

  def initialize name
    @name = name
  end
end

def print_user user
  # ユーザオブジェクトが存在するかを確認する
  if !user
    puts 'ユーザはいません'
  else
    p user
  end
end

user2 = User::find_by_id 2
print_user user2
# ユーザはいません

p user2
# nil

user1 = User::find_by_id 1
print_user user1
# #<User:0x00007fe70f173f88 @name="最初のユーザ">
others/User.php
<?php

class User
{
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    static function findById($id)
    {
        /** IDが1のときはユーザオブジェクトを返し、それ以外のときはユーザオブジェクトを返さないとします */
        if ($id === 1) {
            return new static('最初のユーザ');
        } else {
            return null;
        }
    }
}

function printUser($user)
{
    // ユーザオブジェクトが存在するかを確認する
    if (empty($user)) {
        echo 'ユーザはいません';
    } else {
        var_dump($user);
    }
}

$user2 = User::findById(2);
printUser($user2);
// ユーザはいません

var_dump($user2);
// NULL

$user1 = User::findById(1);
printUser($user1);
// object(User)#1 (1) {
//   ["name":"User":private]=>
//   string(18) "最初のユーザ"
// }

if文の返り値

Ruby PHP
if文の返り値 最後の値 なし
分岐によって値を生成する場合 ifの返り値に入れる 一度値を初期化し、再代入する

PHPはif文の返り値が返ってきません。
そのため、分岐によって値を生成させるケースでは一度値を初期化するなどしてから再代入する、みたいな方法を取る必要があります。

どうしても再代入がいやだ、イミュータブルにしたい、という場合は三項演算子という方法もあります。
ただし、三項演算子は多くの場合は処理が分かりにくくなるだけなので、非推奨です。

others/day.rb
def print_ymd is_beginning_of_month
  today = Time.now

  datetime = if is_beginning_of_month
               Time.new today.year, today.month, 1 # is_beginning_of_month = falseでは現在日を返す
             else
               today # 月初を返す
             end

  puts datetime.strftime "%Y年%m月%d日です"
end

print_ymd false
# 2020年12月07日です

print_ymd true
# 2020年12月01日です
others/day.php
<?php

function printYmd($is_beginning_of_month)
{
    $today = new DateTimeImmutable();

    $datetime = null;
    if ($is_beginning_of_month) {
        // 月初を返す
        $datetime = new DateTimeImmutable($today->format('Y-m-01'));
    } else {
        // $is_beginning_of_month = falseでは現在日を返す
        $datetime = $today;
    }

    echo $datetime->format('Y年m月d日です');
}

printYmd(false);
// 2020年12月07日です

printYmd(true);
// 2020年12月01日です

/**
 * 別の書き方。コード量は少ないが分かりにくい
 */
function printYmd2($is_beginning_of_month)
{
    $today = new DateTimeImmutable();
    $datetime = $is_beginning_of_month ? new DateTimeImmutable($today->format('Y-m-01')) : $today;

    echo $datetime->format('Y年m月d日です');
}

名前付き引数がない(PHP7以前)

Ruby PHP7以前 PHP8以後
名前付き引数 引数名: なし 引数名:

PHP7以前では名前付き引数がありません。
よって、オプション引数と言いながらも順番を意識して代入する必要があります(辛い)。
このため、依存性注入が凄まじくやりづらいです。

PHP8には名前付き引数が導入されています。
【PHP8.0】PHPに名前付き引数が実装される

others/argument.rb
def calc_goku_scouter original_power, kaio_ken: 1, is_suppressed: false, is_super_saiyan: false
  power = original_power * kaio_ken

  if power > 5000 && is_suppressed
    power = 5000
  end

  if is_super_saiyan
    power += 100000000
  end

  power
end

puts calc_goku_scouter 85000
# 85000

puts calc_goku_scouter 85000, kaio_ken: 2
# 170000

puts calc_goku_scouter 85000, is_suppressed: true
# 5000

puts calc_goku_scouter 85000, kaio_ken: 20, is_super_saiyan: true
# 101700000
others/argument.php
<?php

function calcGokuScouter($original_power, $kaio_ken = 1, $is_suppressed = false, $is_super_saiyan = false)
{
    $power = $original_power * $kaio_ken;

    if ($power > 5000 && $is_suppressed) {
        $power = 5000;
    }

    if ($is_super_saiyan) {
        $power += 100000000;
    }

    return $power;
}

echo calcGokuScouter(85000);
// 85000

echo calcGokuScouter(85000, 2);
// 170000

echo calcGokuScouter(85000, 1, true);
// 5000

echo calcGokuScouter(85000, 20, false, true);
// 101700000

interfaceとabstract

Ruby PHP
インターフェース なし interface
抽象クラス なし abstract

Rubyには無い概念として、PHPにはinterface, abstractというものがあります。

特にinterfaceは重要なのですが、中々概念の理解が難しいかもしれません。
とても勉強になった記事があるので、貼っておきます。
脱PHP初心者!インターフェイスを理解しよう

abstractはGoFのデザインパターンでいうTemplate Methodパターンを実装するのに必要です。
参考になった記事を貼っておきます。
PHPによるデザインパターン入門 - TemplateMethod〜処理を穴埋めする - Do You PHP はてブロ

結び

いかがでしたか?
もし誤記・間違い・より良い書き方などあれば、ご指摘いただけると嬉しいです。

一応GitHubにもコードをまとめておきました。
https://github.com/kumackey/php-ruby

他参考になりそうなもの

PHPer向けのRuby入門
この記事の逆パターンです。演習問題が付いてる優しさ。

PHPerがRubyを勉強するのに役に立つTips
同じくこの記事の逆パターン。

元PHPエンジニアがPHPとRubyを比較してみた
PHPとRubyの文法の違いなど。

PHPからRubyへ
Rubyの公式が出している、PHPとの違いというドキュメント

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

【PHP入門】RubyエンジニアがPHPを勉強し始めるときに見る記事

この記事 is 何

RubyエンジニアがPHPを勉強し始めるときに見る、PHPの入門記事です。

動機

今までRubyをやってきていて、PHPを使うことになったというケースは少なくないと思っています。
特に実務未経験の方々では、このケースが多いのではないでしょうか?
というのは、実務未経験が学習している言語は、Rubyがファーストチョイスである一方で、PHPを使う企業は少なくないためです。
ちなみに、僕もそのケースでした。
そういう方々が過度に不安を持ったりせず、スムーズに転向できるようにしたいと思い、この記事を書きました。

この記事の主なターゲット

  • 今までRubyを勉強してたが、PHPを使うことになった人
  • というか半年前の自分

この記事で書くつもりのないこと

  • 技術選定時・初学者が学ぶ言語としてRuby or PHPはどっちが良いのかということ
  • 網羅的・辞書的にRubyとPHPを比較した結果
  • RubyとPHPの文法の正確な対応関係

学習のし始め

RubyからPHPへの転向コスト

Rubyをちゃんと習得できた方であれば、転向・学習コストにおいて問題はほぼ起きないと思います。
文法を調べながらとりあえず書き始められる、というレベルに達するには半日〜3日程度の勉強で済むと思います。

文法を調べるときに何を使うか

php.net
PHPは公式ドキュメントが神なので、基本的にこのサイトを見ればPHPの文法的な解決してくれるはずです。

Screen Shot 2020-11-15 at 18.55.08.png

ググラビリティも良好で、「array_merge php」などと検索すれば公式ドキュメントが先頭に出てくれます。

本題

動作確認バージョン
$ php -v
PHP 7.3.11 ...

$ ruby -v
ruby 2.6.3p62 ...

基本的なオブジェクトの概念

出力

Ruby PHP
改行なし出力 print echo
型情報付き出力 p var_dump()
配列に対し改行付き出力 puts echo, PHP_EOL

Rubyのprintは、PHPでいうとechoに近いです。
Rubyのpは、PHPでいうとvar_dumpに近いです。

Rubyのputsは?と言われると、対応したものはありません。
echoPHP_EOLを組み合わせる必要があります。

objects/print.rb
print 'sample!'
# sample!

sample_array = [1, 'sample']
print sample_array
# [1, "sample"]

p sample_array
# [1, "sample"]

puts sample_array
# 1
# sample
objects/class.php
<?php
echo 'sample!';
// sample!

$sample_array = [1, 'sample'];
echo $sample_array;
// Array

var_dump($sample_array);
// array(2) {
//   [0]=>
//   int(1)
//   [1]=>
//   string(6) "sample"
// }

foreach ($sample_array as $value) {
    echo $value . PHP_EOL;
}
// 1
// sample

プリミティブ型のクラス

Ruby PHP
プリミティブ型は クラス ただの型
文字列への変換 to_s "{$変数}"
数値への変換 to_i intval()

RubyではString, Integer, Arrayといったプリミティブ型も全てがオブジェクトです。
一方、PHPにはプリミティブ型においてはクラスの概念はありません。

PHPではstringへの変換は、ダブルクウォートで囲った上で変数展開することによって実現します。
また、intに変換するにはintval()を使います。

objects/class.rb
int3 = 3

string3 = int3.to_s
p string3
# "3"

p string3.class
# String

re_int3 = string3.to_i
p re_int3
# 3

p re_int3.class
# Integer
objects/method.php
<?php
$int3 = 3;

$string3 = "{$int3}";
var_dump($string3);
// string(1) "3"

get_class($string3);
// Warning: get_class() expects parameter 1 to be object, string given in ...

$re_int3 = intval($string3);
var_dump($re_int3);
// int(3)

get_class($re_int3);
// Warning: get_class() expects parameter 1 to be object, int given in ...

値・オブジェクトに対する基本的な操作・変換

Ruby PHP
基本的な操作・変換は オブジェクトメソッド 標準関数の引数に渡す
文字数を調べる length / size mb_strlen()
配列を合計する sum array_sum()

Rubyでは値・オブジェクトに対する基本的な操作・変換はオブジェクトメソッドで実現します。
参考: class Object(Ruby 2.7.0 リファレンスマニュアル)

一方、PHPは標準関数が充実しており、その標準関数の引数として対象を渡すのが一般的です。

objects/method.rb
address = '千代田区千代田1-1'
puts address.length
# 10

fibonacci_numbers = [1, 1, 2, 3, 5, 8, 13]
puts fibonacci_numbers.sum
# 33
objects/method.php
<?php
$address = '千代田区千代田1-1';
echo mb_strlen($address);
// 10

$fibonacci_numbers = [1, 1, 2, 3, 5, 8, 13];
echo array_sum($fibonacci_numbers);
// 33

配列・ハッシュと、連想配列

Ruby PHP
配列の概念 配列 連想配列
連想配列の概念 ハッシュ 連想配列
配列の要素の取得 配列[インデックス] 連想配列[インデックス]
連想配列の要素の取得 配列[シンボル] 連想配列[キー]

Rubyでは配列と連想配列(ハッシュ)を使い分けます。
一方で、PHPはちょっと特殊で、連想配列しかありません。PHPにおける(連想配列でない)配列は、キーが数値である連想配列です。
また、Rubyでは連想配列のキーにはシンボルを使うことが多いと思いますが、PHPでは文字列をキーとすることが多いです。

objects/array.rb
days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
p days_of_week
# ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

puts days_of_week[0]
# Monday

fruits = {red: 'apple', purple: 'grape', yellow: 'lemon'}
p fruits
# {:red=>"apple", :purple=>"grape", :yellow=>"lemon"}

puts fruits[:red]
# apple
objects/array.php
<?php
$days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
var_dump($days_of_week);
// array(7) {
//   [0]=>
//   string(6) "Monday"
//   [1]=>
//   string(7) "Tuesday"
//   [2]=>
//   string(9) "Wednesday"
//   [3]=>
//   string(8) "Thursday"
//   [4]=>
//   string(6) "Friday"
//   [5]=>
//   string(8) "Saturday"
//   [6]=>
//   string(6) "Sunday"
// }

echo $days_of_week[0];
// Monday

$fruits = ['red' => 'apple', 'purple' => 'grape', 'yellow' => 'lemon'];
var_dump($fruits);
// array(3) {
//   ["red"]=>
//   string(5) "apple"
//   ["purple"]=>
//   string(5) "grape"
//   ["yellow"]=>
//   string(5) "lemon"
//}

echo $fruits['red'];
// apple

配列への操作

Ruby PHP
配列への追加 配列 << 新要素 配列[] = 新要素
連想配列への追加 配列[シンボル] = 新要素 配列[キー] = 新要素
繰り返し処理 each foreach
繰り返し処理(返り値使用) map array_map()
繰り返し処理内での行いたい処理 ブロック コールバック関数

配列への追加は、Rubyは<<によってできる一方、PHPでは配列[] = 新要素という書き方を使います。
連想配列への追加は、書き方的にほぼ違いは無いかと思います。

手続き的な繰り返し処理は、Rubyならeach, PHPならforeachです。
返り値を使用したいときはRubyならmap, PHPならarray_map()を使います。

繰り返し処理中に行いたい処理を表現するのは、Rubyならブロック、PHPならコールバック関数を使います。
array_map()はよく使う割にはちょっと複雑な見た目をしていますね。

objects/array_function.rb
sequence_numbers = [1, 2, 3, 4, 5]
sequence_numbers << 6
p sequence_numbers
# [1, 2, 3, 4, 5, 6]

fruits = {red: 'apple', purple: 'grape', yellow: 'lemon'}
fruits[:orange] = 'pumpkin'
p fruits
# {:red=>"apple", :purple=>"grape", :yellow=>"lemon", :orange=>"pumpkin"}

sequence_numbers.each do |number|
  multiple_of_3 = number * 3
  puts multiple_of_3
end
# 3
# 6
# 9
# 12
# 15
# 18

multiples = sequence_numbers.map do |number|
  number * 3
end
p multiples
# [3, 6, 9, 12, 15, 18]
objects/array_function.php
<?php
$sequence_numbers = [1, 2, 3, 4, 5];
$sequence_numbers[] = 6;
var_dump($sequence_numbers);
// array(6) {
//   [0]=>
//   int(1)
//   [1]=>
//   int(2)
//   [2]=>
//   int(3)
//   [3]=>
//   int(4)
//   [4]=>
//   int(5)
//   [5]=>
//   int(6)
// }

$fruits = ['red' => 'apple', 'purple' => 'grape', 'yellow' => 'lemon'];
$fruits['orange'] = 'pumpkin';
var_dump($fruits);
// array(4) {
//   ["red"]=>
//   string(5) "apple"
//   ["purple"]=>
//   string(5) "grape"
//   ["yellow"]=>
//   string(5) "lemon"
//   ["orange"]=>
//   string(7) "pumpkin"
// }

foreach ($sequence_numbers as $number) {
    $multiple_of_3 = $number * 3;
    echo $multiple_of_3 . PHP_EOL;
}
// 3
// 6
// 9
// 12
// 15
// 18

$multiples = array_map(
    function ($number) {
        return $number * 3;
    },
    $sequence_numbers
);
var_dump($multiples);
// array(6) {
//   [0]=>
//   int(3)
//   [1]=>
//   int(6)
//   [2]=>
//   int(9)
//   [3]=>
//   int(12)
//   [4]=>
//   int(15)
//   [5]=>
//   int(18)
// }

クラス関連

クラスは重要なので、重点的に解説します。

インスタンス変数=プロパティ

Ruby PHP
メンバ変数 インスタンス変数 プロパティ
privateなメンバ変数の定義 不要 private $変数;
publicなメンバ変数の定義 @変数に代入し、attr_accessor public $変数;
コンストラクタ def initialize function __construct()
メンバ変数への代入 @変数に代入 $this->変数に代入
メンバ変数の呼び出し . ->

Rubyのインスタンス変数は、PHPのプロパティに該当します。
Rubyはインスタンス変数の宣言は特に必要なく、コンストラクタで@変数に代入してしまえば完了です。
一方、PHPだと宣言が必要で、private $変数;のように定義します。

また、メンバ変数を公開可能にしたいのであれば、Rubyではattr_accessorを使う一方、PHPではpublic $変数によって定義します。

classes/Person.rb
class Person
  attr_accessor :name

  def initialize name, age
    @name = name
    @age = age
  end
end

person = Person.new '一郎', 10
p person
# #<Person:0x00007ff39f158750 @name="一郎", @age=10>

puts person.name
# 一郎

puts person.age
# Traceback (most recent call last):
# classes/Person.rb:16:in `<main>': undefined method `age' for #<Person:0x00007ff39f158750 @name="一郎", @age=10> (NoMethodError)
classes/Person.php
<?PHP

class Person
{
    public $name;
    private $age;

    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

$person = new Person('一郎', 10);
var_dump($person);
// object(Person)#1 (2) {
//   ["name"]=>
//   string(6) "一郎"
//   ["age":"Person":private]=>
//   int(10)
// }

echo $person->name;
// 一郎

echo $person->age;
// Fatal error: Uncaught Error: Cannot access private property Person::$age in ...

クラスの定数

Ruby PHP
定数の定義 大文字 const 大文字
クラス外からの定数の呼び出し クラス::定数 クラス::定数
クラス内からの定数の呼び出し 定数 self::定数

PHPだと定数はconstを付けて宣言します。
クラス外からの呼び出しは、RubyでもPHPでも同じ書き方です。
PHPだとクラス外からの呼び出しではself::をつけます。

classes/Sample.rb
class Sample
  TEST_CODE = 'test_code'

  def print_code
    puts "コードは#{TEST_CODE}です。";
  end
end

puts Sample::TEST_CODE
# test_code

sample = Sample.new
sample.print_code
# コードはtest_codeです。
classes/Sample.php
<?PHP

class Sample
{
    const TEST_CODE = 'test_code';

    public function printCode()
    {
        $code = self::TEST_CODE;
        echo "コードは「{$code}」です。";
    }
}

echo Sample::TEST_CODE;
// test_code

$sample = new Sample();
$sample->printCode();
// コードは「test_code」です。

インスタンスメソッド

Ruby PHP
publicメソッドの定義方法 ただのdef public function()
privateメソッドの定義方法 privateの下でdef private function()

Rubyではクラス内で何も行わずにdefをするとpublicメソッドとなり、privateの下で定義されるとprivateメソッドになります。
一方、PHPではメソッドの定義時に必ずpublic/privateを定義します。
(本当はもっと色々種類がありますが割愛)

classes/Product.rb
class Product
    def initialize price, cost
        @price = price
        @cost = cost
    end

    def print_judgment
        if calc_profit > 0
            puts 'この商品は利益が出ます!どんどん売りましょう!';
        else
            puts 'この商品は利益が出ません...売るべきではないかもしれません...'
        end
    end

    private

    def calc_profit
        @price - @cost
    end
end

product = Product.new 120, 100
product.print_judgment
# この商品は利益が出ます!どんどん売りましょう!

product =  Product.new 90, 100
product.print_judgment
# この商品は利益が出ません...売るべきではないかもしれません...

product.calc_profit
# Traceback (most recent call last):
# classes/Product.rb:30:in `<main>': private method `calc_profit' called for #<Product:0x00007fc9f19dc408 @price=120, @cost=100> (NoMethodError)
classes/Product.php
<?php

class Product
{
    private $price;
    private $cost;

    public function __construct($price, $cost)
    {
        $this->price = $price;
        $this->cost = $cost;
    }

    public function printJudgment()
    {
        if ($this->calcProfit() > 0) {
            echo 'この商品は利益が出ます!どんどん売りましょう!';
        } else {
            echo 'この商品は利益が出ません...売るべきではないかもしれません...';
        }
    }

    private function calcProfit()
    {
        return $this->price - $this->cost;
    }
}

$product = new Product(120, 100);

$product->printJudgment();
// この商品は利益が出ます!どんどん売りましょう!

$product = new Product(90, 100);
$product->printJudgment();
// この商品は利益が出ません...売るべきではないかもしれません...

$product->calcProfit();
// Fatal error: Uncaught Error: Call to private method Product::calcProfit() from context '' in ...

クラスメソッドとstaticメソッド

Ruby PHP
静的メソッド クラスメソッド staticメソッド
インスタンスメソッド インスタンスメソッド staticでないメソッド
静的メソッドの定義方法 self static function()
静的メソッドの呼び出し クラス::メソッド クラス::メソッド

Rubyにおけるクラスメソッドは、PHPではstaticメソッドに該当します。
逆に、インスタンスメソッドはstaicでないメソッドということになります。

Rubyでクラスメソッドを定義するときは、class << selfの中で定義することが多いと思います。
一方で、PHPではメソッドの定義ごとにstaticを付与することで定義します。

また、public/privateはどんなメソッドでも付けるので、staticメソッドにもpublic/privateを付けて定義します。
静的メソッドの呼び出しの書き方は、RubyとPHPとでほぼ差は無いかと思います。

classes/VertebrateAnimal.rb
class VertebrateAnimal
  class << self
    def is_vertebrate_animal group
      get_vertebrate_animals.include? group
    end

    private

    def get_vertebrate_animals
      %w[fishes amphibians reptiles birds mammals]
    end
  end
end

is_vertebrate_animal1 = VertebrateAnimal::is_vertebrate_animal 'fishes'
puts is_vertebrate_animal1
# true

is_vertebrate_animal2 = VertebrateAnimal::is_vertebrate_animal 'insects'
puts is_vertebrate_animal2
# false
classes/VertebrateAnimal.php
<?PHP

class VertebrateAnimal
{
    public static function isVertebrateAnimal($group)
    {
        $vertebrate_animals = self::getVertebrateAnimals();
        return in_array($group, $vertebrate_animals, true);
    }

    private static function getVertebrateAnimals()
    {
        return ['fishes', 'amphibians', 'reptiles', 'birds', 'mammals'];
    }
}

$is_vertebrate_animal1 = VertebrateAnimal::isVertebrateAnimal('fishes');
var_dump($is_vertebrate_animal1);
// bool(true)

$is_vertebrate_animal2 = VertebrateAnimal::isVertebrateAnimal('insects');
var_dump($is_vertebrate_animal2);
// bool(false)

ゲッターとセッター

Ruby PHP
ゲッター attr_reader getXX()
セッター attr_writer setXX()

Rubyにおけるattr_readerおよびattr_writerに該当するような文法は、PHPにはありません。
よってgetXX, setXXという命名で自作することになります。

classes/Capsule.rb
class Capsule
  attr_reader :element
  attr_writer :element

  def initialize element
    @element = element
  end
end

capsule = Capsule.new '有効成分'
puts capsule.element
# 有効成分

p capsule
# #<Capsule:0x00007f8d909ac660 @element="有効成分">

capsule.element = '毒'
p capsule
# #<Capsule:0x00007f8d909ac660 @element="毒">
classes/Capsule.php
<?PHP

class Capsule
{
    private $element;

    public function __construct($element)
    {
        $this->element = $element;
    }

    public function getElement()
    {
        return $this->element;
    }

    public function setElement($element)
    {
        $this->element = $element;
    }
}

$capsule = new Capsule('有効成分');
echo $capsule->getElement();
// 有効成分

var_dump($capsule);
// object(Capsule)#1 (1) {
//   ["element":"Capsule":private]=>
//   string(12) "有効成分"
// }

$capsule->setElement('毒');
var_dump($capsule);
// object(Capsule)#1 (1) {
//   ["element":"Capsule":private]=>
//   string(3) "毒"
// }

その他ハマりポイント

ライブラリの対応関係

Ruby PHP
ライブラリ管理 Gem Composer
テスト Rspec PHPUnit
Linter Rubocop PHP-CS-Fixerなど
コメント RDoc PHPDoc
フレームワーク Ruby on Rails Laravel, CakePHPなど

有名どころのライブラリなどの対応関係のみを記載しました。
Rubyと言ったらRails、Railsと言ったらRubyという印象ですが、PHPではフレームワークは複数の選択肢があります。
シェアが高いという意味であればLaravelが近く、MVCの思想に忠実という意味ではCakePHPに近いかなという印象を持っています。

真偽判定・nil/null判定

Ruby PHP
== そこそこ使う 絶対に使わない
Null nil null
オブジェクトのNull確認 ! empty()

PHPの==はあまりにもヤバいことで有名だと思いますので、比較のときには必ず===を使うようにします。
Rubyでのnilは、PHPだとnullに該当します。

Rubyでは全てがオブジェクトであり、真偽値判定・nil判定がうまく設計されていて、オブジェクトのみで直感的に真偽値判定・nil判定をできることも多いです。
一方、PHPの場合は直感的に真偽値判定・null判定を行うのは危険なので、型を意識して判定した方が安全です。
オブジェクトがnullや空かどうかを確認するなら、empty()を使うのが有効です。

others/User.rb
class User
  class << self
    def find_by_id id
      # IDが1のときはユーザオブジェクトを返し、それ以外のときはユーザオブジェクトを返さないとします
      if id == 1
        User.new '最初のユーザ'
      else
        nil
      end
    end
  end

  def initialize name
    @name = name
  end
end

def print_user user
  # ユーザオブジェクトが存在するかを確認する
  if !user
    puts 'ユーザはいません'
  else
    p user
  end
end

user2 = User::find_by_id 2
print_user user2
# ユーザはいません

p user2
# nil

user1 = User::find_by_id 1
print_user user1
# #<User:0x00007fe70f173f88 @name="最初のユーザ">
others/User.php
<?php

class User
{
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    static function findById($id)
    {
        /** IDが1のときはユーザオブジェクトを返し、それ以外のときはユーザオブジェクトを返さないとします */
        if ($id === 1) {
            return new static('最初のユーザ');
        } else {
            return null;
        }
    }
}

function printUser($user)
{
    // ユーザオブジェクトが存在するかを確認する
    if (empty($user)) {
        echo 'ユーザはいません';
    } else {
        var_dump($user);
    }
}

$user2 = User::findById(2);
printUser($user2);
// ユーザはいません

var_dump($user2);
// NULL

$user1 = User::findById(1);
printUser($user1);
// object(User)#1 (1) {
//   ["name":"User":private]=>
//   string(18) "最初のユーザ"
// }

if文の返り値

Ruby PHP
if文の返り値 最後の値 なし
分岐によって値を生成する場合 ifの返り値に入れる 一度値を初期化し、再代入する

PHPはif文の返り値が返ってきません。
そのため、分岐によって値を生成させるケースでは一度値を初期化するなどしてから再代入する、みたいな方法を取る必要があります。

どうしても再代入がいやだ、イミュータブルにしたい、という場合は三項演算子という方法もあります。
ただし、三項演算子は多くの場合は処理が分かりにくくなるだけなので、非推奨です。

others/day.rb
def print_ymd is_beginning_of_month
  today = Time.now

  datetime = if is_beginning_of_month
               Time.new today.year, today.month, 1 # is_beginning_of_month = falseでは現在日を返す
             else
               today # 月初を返す
             end

  puts datetime.strftime "%Y年%m月%d日です"
end

print_ymd false
# 2020年12月07日です

print_ymd true
# 2020年12月01日です
others/day.php
<?php

function printYmd($is_beginning_of_month)
{
    $today = new DateTimeImmutable();

    $datetime = null;
    if ($is_beginning_of_month) {
        // 月初を返す
        $datetime = new DateTimeImmutable($today->format('Y-m-01'));
    } else {
        // $is_beginning_of_month = falseでは現在日を返す
        $datetime = $today;
    }

    echo $datetime->format('Y年m月d日です');
}

printYmd(false);
// 2020年12月07日です

printYmd(true);
// 2020年12月01日です

/**
 * 別の書き方。コード量は少ないが分かりにくい
 */
function printYmd2($is_beginning_of_month)
{
    $today = new DateTimeImmutable();
    $datetime = $is_beginning_of_month ? new DateTimeImmutable($today->format('Y-m-01')) : $today;

    echo $datetime->format('Y年m月d日です');
}

名前付き引数がない(PHP7以前)

Ruby PHP7以前 PHP8以後
名前付き引数 引数名: なし 引数名:

PHP7以前では名前付き引数がありません。
よって、オプション引数と言いながらも順番を意識して代入する必要があります(辛い)。
このため、依存性注入が凄まじくやりづらいです。

PHP8には名前付き引数が導入されています。
【PHP8.0】PHPに名前付き引数が実装される

others/argument.rb
def calc_goku_scouter original_power, kaio_ken: 1, is_suppressed: false, is_super_saiyan: false
  power = original_power * kaio_ken

  if power > 5000 && is_suppressed
    power = 5000
  end

  if is_super_saiyan
    power += 100000000
  end

  power
end

puts calc_goku_scouter 85000
# 85000

puts calc_goku_scouter 85000, kaio_ken: 2
# 170000

puts calc_goku_scouter 85000, is_suppressed: true
# 5000

puts calc_goku_scouter 85000, kaio_ken: 20, is_super_saiyan: true
# 101700000
others/argument.php
<?php

function calcGokuScouter($original_power, $kaio_ken = 1, $is_suppressed = false, $is_super_saiyan = false)
{
    $power = $original_power * $kaio_ken;

    if ($power > 5000 && $is_suppressed) {
        $power = 5000;
    }

    if ($is_super_saiyan) {
        $power += 100000000;
    }

    return $power;
}

echo calcGokuScouter(85000);
// 85000

echo calcGokuScouter(85000, 2);
// 170000

echo calcGokuScouter(85000, 1, true);
// 5000

echo calcGokuScouter(85000, 20, false, true);
// 101700000

interfaceとabstract

Ruby PHP
インターフェース なし interface
抽象クラス なし abstract

Rubyには無い概念として、PHPにはinterface, abstractというものがあります。

特にinterfaceは重要なのですが、中々概念の理解が難しいかもしれません。
とても勉強になった記事があるので、貼っておきます。
脱PHP初心者!インターフェイスを理解しよう

abstractはGoFのデザインパターンでいうTemplate Methodパターンを実装するのに必要です。
参考になった記事を貼っておきます。
PHPによるデザインパターン入門 - TemplateMethod〜処理を穴埋めする - Do You PHP はてブロ

結び

いかがでしたか?
もし誤記・間違い・より良い書き方などあれば、ご指摘いただけると嬉しいです。

一応GitHubにもコードをまとめておきました。
https://github.com/kumackey/php-ruby

他参考になりそうなもの

PHPer向けのRuby入門
この記事の逆パターンです。演習問題が付いてる優しさ。

PHPerがRubyを勉強するのに役に立つTips
同じくこの記事の逆パターン。

元PHPエンジニアがPHPとRubyを比較してみた
PHPとRubyの文法の違いなど。

PHPからRubyへ
Rubyの公式が出している、PHPとの違いというドキュメント

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

自走力に必要なもの?

私の結論は楽しみながら学習することです。
なぜなら、嫌々でプログラミングをしても挫折してしまうからです。

スクール2週間目に突入して…

スクールのカリキュラムを2週間終え感じたことは、「事前に勉強していて良かった...」
スクールでの受講を悩んでいる方や、これから受講するって方は”未経験からエンジニアへ”という謳い文句を勘違いして知識ゼロで臨むとなかなか苦労すると思いました。実際に自分の所属している6人のグループではProgateで事前学習していたのは自分だけでした。

私の場合は、ブラインドタッチがあまり出来ず、苦労したのでタイピング練習をもっとすべきだったなと反省をしています。また、カリキュラムの進行はスピーディで、合間にある中間試験など理解度を高めながら、スピード感を持ってカリキュラムを進めるのに、不安と焦りを感じながら学習を続けています。

転職活動の心構え :beginner:

一度でも内容に触れたことがあれば記憶の定着もしやすいですし、理解度も増します。そして、気持ちにも余裕が生まれます。

この点は、転職活動においても言えます。内定をもらって終わり!ではなく、やっとスタート地点に立てただけなので、入社前にどれだけ内定先で必要となるプログラミング言語、SQLや基本情報試験を事前学習できるかで職場での貢献度を大きく左右すると言えます。

スクール受講前(2ヶ月間) :alarm_clock:

・エンジニア界隈のリサーチ
(親戚の自社開発企業を訪問)
・キタミ式基本情報技術者
・Progate2周(HTML,CSS,Ruby,Rails)
・Youtube(自社開発、受託開発、SESの違い)

8月からProgateで独学し、スクールの無料カウンセリングを受けて受講する覚悟が決まったのが9月上旬。スクール受講開始が10月上旬。その間に23年間お世話になったWindowsからスタバで作業しているとかっこいいと言われるMacへシフト
https://twitter.com/by_miwa30/status/1301541645859434496

エンジニア界隈のリサーチ :pencil:
私は、高校生の時に情報ビジネス科でJavaとC言語を学んでいたこともあり、進路先にエンジニアを考えていました。ただ、求人が1件しかないこと、エンジニアとして働くイメージが湧かず断念。その当時、進路相談でお世話になったのが親戚の自社開発企業です。

そして、今回2度目のエンジニア現場で1時間半、話を伺いました。「基本情報技術者資格を取得するつもりがなくても、学習しておいたほうが良い!クラウドインフラではAWSが主流だよ。」などネットだけでは知り得ない現場の声を伺えました。とても貴重なご意見ありがとうございました。

キタミ式基本情報技術者 :books:
上記にある通り、現役のエンジニアのご意見から基本情報技術者を早速購入。人から言われたことは、すぐに一度調べて必要なら実践してみる考えがモットーな私。このスピード感と素直さを強みとして有益な情報かどうかを取捨選択できる収集心と分析力を磨き上げ自走力を最大化させていければと思います。

本の内容は、試験対策のためだけの資格本ではない!と思いました。なぜなら、イラストやマンガを取り入れながら暗記ではなく、楽しく理解することを大切にしている印象を持ったからです。プログラミングも暗記するつもりで学習しては行けないと言われます。いくら知識があっても、その知識を応用できる柔軟性が必要で知識を繰り返し学習することで記憶に定着されます。その為には、学習したことをアウトプットすること、復習することが欠かせません。この考えを改めて痛感させられただけでも、購入した甲斐があって良かったです。

Progate :computer:
カリキュラムが分かりやすいこと、書いたコードの動きも確認できる。環境構築が不要で自分が書いたコードを体感でき楽しくゲーム感覚で進めることが出来ました。高校の時のような講義形式で学ぶよりも、自分のペースで進められ、わからない点すぐにカリキュラムを見返すことが出来るのが嬉しかったです。
https://twitter.com/by_miwa30/status/1291192741280399360

Youtube :film_frames:
Web系エンジニアとは何か?IT業界とは?等に特化して学びました。現役の方や元々やっていた方の話を視聴して、自分のこれから進んで行く道をじっくり考える時間となりました。自社開発、受託開発、SESそれぞれのメリットデメリットを学べたので今後の転職活動にとても参考になりました。

振り返り・感想 :triangular_flag_on_post:

今思うと、Progateのコードの中身はめちゃくちゃだと思います。DRYの原則を意識すれば、もっとスッキリさせられるはず。でも、何事も初めから完璧にできる人はいません。

冒頭で「事前に勉強していて良かった‥.」と言いましたが、1日10時間、10週間を終えた時に後悔することがないようにしたいです。その為には、愚直に毎日インプットしてはアウトプットを繰り返すこれに尽きると思います。この考えを持つきっかけをくれたプログラミング。本当に出会えて良かったなと思いました。

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

【Rubyによるデザインパターン】ファクトリーメソッドパターンのメモ

プログラムの設計力向上のため『Rubyによるデザインパターン』を読んでおり、気になるデザインパターンを、1つずつまとめています。

今回は、ファクトリーメソッドパターンについてまとめました。

デザインパターン記事一覧

【Rubyによるデザインパターン】テンプレートメソッドパターンのメモ - Qiita
【Rubyによるデザインパターン】ファクトリーメソッドパターンのメモ - Qiita
【Rubyによるデザインパターン】ストラテジーパターンのメモ - Qiita

ファクトリーメソッドパターンについて

オブジェクトの生成をするメソッド、クラスを定義するパターンです。

Factory(Creator) というクラスを定義し、Factory がクラスを選択しオブジェクト生成を担うようにします。

サンプルコード

毎日愛飲しているプロテインを題材にサンプルコードを書きます。
僕の場合、プロテインの種類によって準備工程が異なるので、プロテインの準備工程を出力するプログラムを書きます。

まずはファクトリーメソッドパターン適用前からです。

class WheyProtein
  def initialize
    @name = 'ホエイプロテイン'
  end

  def add_water
    puts "#{@name}: 水を入れます"
  end


  def shake
    puts "#{@name}: 10回シェイクします"
  end
end

class SoyProtein
  def initialize
    @name = 'ソイプロテイン'
  end

  def add_milk
    puts "#{@name}: 牛乳を入れます"
  end

  def shake
    puts "#{@name}: 20回シェイクします"
  end
end

class ProteinPreparer
  def initialize(water, milk)
    @protein =
      if water && !milk
        WheyProtein.new
      elsif !water && milk
        SoyProtein.new
      end
  end

  def execute
    if @protein.class == WheyProtein
      @protein.add_water
    elsif @protein.class == SoyProtein
      @protein.add_milk
    end
    @protein.shake
  end
end

実行時はこのとおりです。

whey_protein_preparer = ProteinPreparer.new(true, false)
whey_protein_preparer.execute
# ホエイプロテイン: 水を入れます
# ホエイプロテイン: 10回シェイクします

soy_protein_preparer = ProteinPreparer.new(false, true)
soy_protein_preparer.execute
# ソイプロテイン: 牛乳を入れます
# ソイプロテイン: 20回シェイクします

ProteinPreparer の問題は 2つの責務を担っている点です。

  • initialize で、@protein のクラスを選択しオブジェクト生成
  • execute で、準備工程の実行

本来 ProteinPreparer は準備工程の責務だけをもつクラスですので、クラスを選択しオブジェクト生成する責務は別クラスへ委譲したいところです。

そこで、ファクトリークラスを生成し、責務を分離することにします。
次は、ファクトリーメソッドパターンを適用して書き換えた場合です。

class WheyProtein
  def initialize
    @name = 'ホエイプロテイン'
  end

  def prepare
    add_water
    shake
  end

  def add_water
    puts "#{@name}: 水を入れます"
  end

  def shake
    puts "#{@name}: 10回シェイクします"
  end
end

class SoyProtein
  def initialize
    @name = 'ソイプロテイン'
  end

  def prepare
    add_milk
    shake
  end

  def add_milk
    puts "#{@name}: 牛乳を入れます"
  end

  def shake
    puts "#{@name}: 20回シェイクします"
  end
end

class ProteinPreparer
  def initialize(proteins)
    @protein = ProteinFactory.create(proteins)
  end

  def execute
    @protein.prepare
  end
end

class ProteinFactory
  def self.create(proteins)
    protein.new
  end
end

実行結果はこのとおりです。

whey_protein_preparer = ProteinPreparer.new(WheyProtein)
whey_protein_preparer.execute
# ホエイプロテイン: 水を入れます
# ホエイプロテイン: 10回シェイクします

soy_protein_preparer = ProteinPreparer.new(WheyProtein)
soy_protein_preparer.execute
# ソイプロテイン: 牛乳を入れます
# ソイプロテイン: 20回シェイクします

プロテインのオブジェクト生成用の ProteinFactory を定義しています。

これによって、オブジェクト生成の責務を ProteinPreparer から分離でき、コードの見通しが良くなったことがわかるのではいでしょうか。
今後プロテインの種類が増えた場合には、 ProteinPreparer の修正は不要で、 ProteinFactory のみを修正すれば良くなるのが嬉しいポイントですね。

まとめ

ファクトリーメソッドパターンはオブジェクト生成だけを担うクラスを定義するパターンでした。
オブジェクトのバリエーションが多いような場合に特に活用できるパターンだと思います。

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

個人アプリ開発日記 #3

まずはuserリソースから

ユーザー登録機能
ログイン機能
ユーザー詳細表示機能
ユーザー編集機能
ユーザー削除機能

を実装していきます!

users_controller の実装

class UsersController < ApplicationController
  include SessionsHelper
  before_action :set_user, only: [:show,:edit,:update,:correct_user]
  before_action :logged_in_user, only: [:index,:edit,:update]
  before_action :correct_user, only: [:edit, :update]

  def index
    @users = User.all
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      log_in(@user)
      redirect_to @user
    else
      render 'new'
    end
  end

  def show
  end

  def edit
  end

  def update
    if @user.update(user_params)
      redirect_to @user
    else
      render
    end
  end

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

  private
    def user_params
      params.require(:user).permit(:nickname,:email,:password,:password_confirmation)
    end

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

    def logged_in_user
      unless logged_in?
        store_location
        # ユーザーがいきたがってたページを記憶
        flash[:danger] = "Please log in"
        redirect_to login_url
      end
    end

    def correct_user
      redirect_to(root_url) unless current_user?(@user)
    end
end

長いので、注目ポイントは フレンドリーフォワーディングを実装したlogged_in_userメソッドと、correct_userメソッドです
store_locationメソッドと、current_user?(@User
は sessions_helperメソッドに定義されてるので見ていきましょう

sessions_helper

module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
      # このコードを実行すると、ユーザーのブラウザ内
      # の一時cokkiesに暗号化済みのユーザーidが自動で
      # 作成されます
      # この後のページでsession[:user_id]を使って
      # 元通りにIDを取り出すことができる
  end

  def log_out
    session.delete(:user_id)
     @current_user = nil
  end

  # ユーザーのセッションを永続的にする
  def remember(user)
    user.remember
    # rememberメソッド呼び出し
    # つまりハッシュ化したトークンをDBに保存
    cookies.permanent.signed[:user_id] = user.id
    # 永続的に保存できるクッキーの保存期限示すため
    # permanentを書く(本当は20年)
    # 生のuser_idができるとだめなので
    # signedで暗号化
    cookies.permanent[:remember_token] = user.remember_token
    # 期限はOK
    # ランダムな文字列のトークンをクッキーに保存
  end

    # 永続セッションを破棄する
    def forget(user)
      user.forget
      #DBのremember_digestからデータ破棄
      cookies.delete(:user_id)
      cookies.delete(:remember_token)
      # クッキーからもユーザーの情報削除
    end

  # 現在ログインしているユーザーの情報を取得
  def current_user
    # DBの問い合わせの数を可能な限り小さくしたい
    # logged_in?メソッドでも使われてるし、、、
    if user_id = session[:user_id]
      # セッションがある場合
      # すなわちログインしてる時のみ

      # sessionにアクセスした結果を変数に
      # 入れておいてあとで使いまわした方が
      # 早くなる
      @current_user ||= User.find_by(id: user_id)
      # find_byでデータベースにクエリを投げる
      # ブラウザのセッションにあるuser_idをもとにUser定義

      # find_byの実行結果をインスタンス変数に保存する
      # ことで、1リクエスト内におけるデータベースへの
      # 問い合わせは最初の一回だけになり、 
      # 以後の呼び出しではインスタンス変数の結果を
      # 再利用する

      # すでに@current_userが存在する場合って何?
      # 一回current_userを実行したら、
      # @current_userがあるのでそれを使ってね

      # sessionのuser_idがあるということは
      # 既にログインしてるといてDBにユーザーの情報があるはず。
      # だからsessionのuser_idをDBでfind_byかければいい
    elsif  (user_id = cookies.signed[:user_id])
      # sessionが張られてなかったらcookiesにあるかも
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        # nilガード
        # クッキーのuser_idとremember_tokenが一致してる
        log_in user
        @current_user = user
      end
    end
  end

  def current_user?(user)
    user && user == current_user
    # nilガード
  end

  # ユーザーがログインしていればtrueを返す、
  # その他ならfalse
  def logged_in?
    !current_user.nil?
    # nilじゃなかったら
    # すなわちログインしてたらfalseが
    # 返ってくるけど、それだとif文とか
    # 使う時にややこしいので、!
    # で返り値の真偽値を逆にして
    # trueを返すようにしてる
  end

  # 現在のユーザーをログアウトする
  def log_out 
    forget(current_user)
    # カレントユーザーのremember_digestを破棄
    # トークン、user_idを破棄
    session.delete(:user_id)
    # セッションも破棄
    @current_user = nil
    # インスタンス変数の値を更新
  end

  # フレンドリーフォワーディングの処理

  # 記憶したURL(もしくはデフォルト値にリダイレクト)
  def redirect_back_or(default)
    # これはいい命名!!
    redirect_to(session[:forwarding_url] || default)
    # store_locationでsession[:forwarding_url]を定義
    session.delete(:forwarding_url)
    # redirectできたらforwarding_urlって情報はいらないので
    # 破棄しましょう
    # セッション情報を保持したままにしておくと、次のログインの
    # 時もこれを読み込んでおかしくなる
  end

  # アクセスしようとしたURLを覚えとく
  def store_location 
    session[:forwarding_url] = request.original_url if request.get?
    # request.original_url はリクエストされたurl
    # それをセッションに保存

    # if request.getでなぜGETリクエストだけ対応してるかと
    # いうと、before_actionのlogged_in_userで
    # updateに制限かかってるけど,PATCH users/:idを
    # 保存する意味はない
    # なぜならユーザーが元々いきたいのはeditとかだから
  end
end

僕の下手くそなコメントだけじゃ意味わからないと思うので、rails tutorialやってみてください、、、笑
本当に色々勉強になりました、、、

sessions_controller

class SessionsController < ApplicationController
  include SessionsHelper
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    # 受け取ったemailからfind_byでデータベースに
    # 問い合わせてユーザー取得
    if user && user.authenticate(params[:session][:password])
        # まずそのユーザーがいるかnilガード
        # いたらpasswordとauthenticateかける
        log_in user
        params[:session][:remember_me] == '1' ? remember(user) : forget(user)
        # check boxがonの時は1になるので1の時は
        # ? 以降がtrueの処理
        # : 以降がfalseの処理
        redirect_back_or user
        # redirect_back_or メソッド呼び出し
        # 引数にuser_url(user)を渡す
    else
      flash.now[:danger] = 'invalid email/password combination'
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    # 二つのブラウザで同時ログアウトとかされたらバグが起こるので
    # ログイン
    redirect_to root_url
  end
end

注目ポイントはnilガードです

nilガードとは、例えば

user = User.find_by(email: params[:session][:email].downcase)

とかでユーザーを取得できない場合に

if user && user.authenticate(params[:session][:password])

まずuserで本当にuserがいたのか確かめます

コンピューターは Aor B と条件式があったら左(A)から実行するので、
その特性を生かしたプログラミングです。面白いですね。

user.rb

class User < ApplicationRecord
  attr_accessor :remember_token
  # remember_tokenというメソッドを作成
  # password,password_comfirmationみたいな
  # 変数のように扱える
  # u.remember_token的なことができる
  has_many :drinks
  before_save  { self.email = email.downcase }
  has_secure_password
  validates :nickname,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  validates :password, presence: true, length: { minimum: 6 },allow_nil: true
  # ユーザー更新時に空のパスワードでも大丈夫
  # has_secure_passwordの方でpasswordの存在性を検証するから大丈夫



  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end

  # 永続的にログインするためにトークンをDBに保存
  def remember
    self.remember_token = User.new_token
    # 何のトークンか分かりやすいから
    # remember_tokenって名前を作った
    update_attribute(:remember_digest,User.digest(remember_token))
    # ハッシュ化したトークンをremember_digestに保存
  end

  # 渡されたトークンがダイジェストと一致したらtrue
  # を返す
  def authenticated?(remember_token)
    return false if remember_digest.nil?
    # 二種類のブラウザを使用してログアウトした場合
    # cookiesのremember_tokenはあるけど、
    # サーバー側でremember_digestをnilにしてるから
    # nilに対して.is_password?とかやるとfor nil classエラーが起きちゃう
    # remember_digestがnilの場合はfalseを返して、処理を止める

    # 後置if文に当てはまる条件があれば処理を止めて!
    # って場合はreturnとかで明示的に書くとif ~ else ~ end 
    # とか書かなくて済む
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

  # ユーザーのログイン情報を破棄する
  def forget
    update_attribute(:remember_digest,nil)
  end
end

これでユーザー機構に必要であろうメソッドが揃いました、、、!

どのメソッドをどのコントローラーや、ヘルパーに配置すべきかまだイマイチよく分かってないので、これから学んでいきたいですね

本当にこれでしっかり動くのか不安なので実際にビューを動かして次回試してみたいと思います、、、!

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

個人アプリ開発日記 #2

基本的なテーブル構造

users テーブル

Column Type Options
nickname string null: false
email string null: false, unique: true
password_digest string null: false
remember_digest string
reset_digest string
activation_digest string null: false
activated boolean default: false
activated_ad datetime null: false

Association

  • has_many :drinks
  • has_many :comments
  • has_many :trades

drinks テーブル

coffeeは不可算名刺なので初心者が扱うのが怖かった、、

Column Type Options
name string null: false
price integer null: false
explain text null: false
user_id integer null: false,foreign_key: true

Association

  • belongs_to :user
  • has_many :comments
  • has_many :tags,
  • has_many :tags,through: :coffee_tags
  • has_one :trade

tags テーブル

Column Type Options
name string null: false
  • has_many :drinks
  • has_many :tags,through: :coffee_tags

coffee_tags テーブル

Column Type Options
drink_id integer null: false,foreign_key: true
tag_id integer null: false,foreign_key: true
  • belongs_to :drink
  • belongs_to :tag

comments テーブル

Column Type Options
content text null: false
user_id integer null: false,foreign_key: true
drink_id integer null: false,foreign_key: true

Association

  • belongs_to :user
  • belongs_to :drink

addresses テーブル

Column Type Options
postal_code string null: false
prefecture_id integer null: false
city string null: false
house_num string null: false
building_name string
phone_num string null:false,unique: true
trade_id integer null:false,foreign_key: true

Association

  • belongs_to :trade

trades テーブル

Column Type Options
user_id integer null: false,foreign_key: true
drink_id integer null: false,foreign_key: true

Association

  • belongs_to :user
  • belongs_to :drink
  • has_one :address

次回は実際のコードに触れていきます

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

初心者はDeviseをカスタマイズするなという話

Deviseを実装したら迷走した

Deviseのことをよくわかっておらず、Contorollerやviewから全部作って設定しようとしてたので、戒めとして記事を書きます。

ControllerとViewは最初からある

Ruby初心者の私はこんなことも直感的に分からなかったんですね。

https://qiita.com/hakatatech/items/f991d54ff527edb844d9

この辺りの記事をみて「はえ〜コマンドでコントローラー作るんだなあ」とコピペで実装していきましたが、最初からあるのでこういうのは実装しなくてOKです。

それでも最初はコマンドを打たねばならない

まあgemファイル追加して必要なものは設定しないといけないので、こちらの記事を参考に実装してみてください。

実際に3日悩んだ結果、ここが一番簡潔で明快でした。

https://qiita.com/salvage0707/items/d3ddc889458ac186e62e

もう初心者はここの「Deviseの使い方」だけやってればいいと思いました。

email認証じゃなくしたいんですけど?

そういう方へは、なんとDeviseの公式記事がございます。

https://github.com/heartcombo/devise/wiki/How-To:-Allow-users-to-sign-in-with-something-other-than-their-email-address

英語ですが、email認証からusername認証にするための方法が全部書かれいてるのでDeepLで翻訳して読んでください。

終わりに

プログラミングの鉄則に「複数のタスクを同時に解決しようとするんじゃない、一個ずつやりなさい」というものがあるみたいですが、今回で身に沁みて分かりました。

あまりにもタスクが多いと「分からないことが分からない」みたいな状況に陥りがちなので、エラー画面から読み取って一つひとつ解決していきます。

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