20200706のRubyに関する記事は17件です。

Ruby学習要点(基礎)

  • 文字列はクォーテーション( ' もしくは " )で囲う。また、クォーテーションは必ず半角で記述する。

  • putsの後には半角スペースをあける必要がある。

  • コメントアウトは「#」を行の先頭に記述
    ※複数行のコメントアウト
    mac:「command + /」
    windows:「ctlr + /」

  • 変数を使うのは、必ず変数を定義(変数名 = 値)した後でなければならない。(プログラムが上から順に実行されるため)

  • 変数名は自由に決めることができるが、Rubyではいくつか命名のルールがある。特に、2語以上を組み合わせた変数名をつけるときは、アンダーバー(_)を用いる。

  • 変数の値を文字列の中に含める方法がある。文字列の中で、#{変数名}とすることで、変数を代入している値に置き換えて、文字列に含めることができる。これを「変数展開」と呼ぶ。

  • ダブルクォーテーションを使った文字列の場合しか変数展開はされない。シングルクォーテーションの場合は変数展開が行われず、そのまま文字列として出力されてしまうので注意する。

  • 配列も1つの値なので、変数に代入することができる。配列を代入する変数名は慣習上、複数形にすることが多い。

  • 繰り返し処理には、each文を使う。each文を使うと、配列の要素を順番に取り出して処理を行うことができる。each文は、「配列.each do |変数名|」と書き、「end」までの間に実行したい処理を書く。
    each文は配列の要素の数だけ繰り返し処理を行う。「|」で囲まれた変数に配列の要素が1つずつ入っていき、その上でeach文の中の処理が実行されていく。each文内の変数名(name)は好きな名前をつけられるが、配列の変数名(names)の単数形にすることが慣習上多い。

  • それぞれの変数の使用できる範囲のことをスコープと呼ぶ。

  • ハッシュは以下の図のように、キーの部分を文字列ではなく、先頭にコロン「:」を付けた書き方をすることもできる。この「:name」という書き方をシンボルと言う。シンボルとは、文字を「"」や「'」で囲む代わりに、先頭に「:」をつけた書き方をいう。文字列とシンボルは厳密には異なるが、基本的には同じように使用することができる。

  • ハッシュのキーにシンボルを用いるときには、省略した書き方をすることができる。具体的には「:key =>」を「key:」というように省略することができる。省略した書き方の場合でも、あくまでキーはシンボルなので、要素を取得する場合には puts user[:name] のようにシンボルを用いる必要がある。

  • インスタンスメソッド内で、「self」はそのインスタンス自身を指す。

  • メソッドを呼び出すには明示的なレシーバが必要になる。クラスメソッドの場合はレシーバとしてselfが指定されている。

  • 各インスタンスで値が固有:インスタンス変数
    各インスタンスで値が共通:クラス変数

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

Sinatraで家族用todoリストを作る

Sinatraで家族用todoリストを作ってみる

Sinatraでアプリを一つ作ってみようということで作成しました。
と言っても、ベースとしての機能はこちらを参考にしています。

Sinatra入門 (全17回) - プログラミングならドットインストール


今回は、家族で使えるtodoリストになるように機能を追加します。
追加する主な機能としては以下の4点となります。

  • 投稿時間の表示
  • ユーザーを選択するセレクトボックス
  • ユーザーに対応したtodo表示
  • ユーザーの新規登録

sinatra入門からのステップアップとして良い題材になると思います。
まず初めに完成アプリの画面です。
view90%.png

以降コードの解説です。

投稿時間の表示

rbファイルにDateクラスの読み込み。

tlist.rb
require date

SQLファイルで作成したテーブルにcreated_atを追加(レコードの生成時間を自動で取得してくれる)。

table.sql
create table todos
(
  id integer PRIMARY key,
  users_id integer,
  body text,
  created_at
);

インスタンス変数の@todosに対してeachメソッドで順番にbody要素を表示するブロック内に、投稿時間を表記するブロックを追加。
To_timeメソッドでTimeクラスに変更し、strftimemeメソッドで見やすく整形してあげる。

index.erb
<div class=“time”>
  <%= todo.created_at.to_time.strftime(%Y/%m/%d %H:%M:%S)%>
  <span class=“delete delete_todo>[x]</span>
</div>

ユーザーを指定するセレクトボックスの設置

SQLファイルにusersテーブルを作成。

table.sql
create table users
(
  id integer primary key,
  userName text
);

ActiveRecord::Baseメソッドでテーブル情報をUsersクラスに格納する。

tlist.rb
class Users < ActiveRecord::Base
  validates :userName, presence: true
end

Usersクラスをインスタンス変数に渡す。

tlist.rb
get / do
  @title = “やることリスト”
  @todos = Todos.all
  @users = Users.all
  erb :index
end

Eachメソッドでselectボックスに値を順次表示させることで、セレクトボックスでユーザーを選択できるようにする。

index.erb
<select name=“users_id” >
  <% @users.each do |user|%>
    <option value=<%= user.id%>><%= user.userName%></option>
  <% end %>
</select>

ユーザーに対応したtodo項目の表示

テーブルの1対多を用いた実装方法がわからなかったため、if文でidの一致するときの結果を表記するようにした。
先ほどのselectで 送信される値は、usersテーブルのidであり、それがtodosテーブルのusers_idに渡されるように設定する。



postで送られたデータをtodosテーブルに追加。

tlist.rb
post /create_todo do
  Todos.create(body: params[:body],users_id: params[:users_id])
  redirect to(/)
end



それぞれのユーザーに対するtodoリストの表示。
@users.eschの部分で順番にユーザーを表示していく
次に与えられたusersテーブルのidとtodosテーブルのusers_idが一致する場合、そのtodoリストを表示するように指定してあげる。

index.erb
  <% @users.each do |user| %>
    <div class=“user_box” data-user_id = <%=user.id%> data-token = <%= Rack::Csrf.csrf_token(env)%>>
        <div class=“user_name”>
          <%= Rack::Utils.escape_html(user.userName)%>
          <span class=“delete delete_user>[x]</span>
        </div>
        <ul>
          <% @todos.each do |todo|%>
            <%if user.id == todo.users_id%>
              <li data-id = <%= todo.id%> data-token = <%= Rack::Csrf.csrf_token(env)%>>
                <div class=“todo_body”>
                  <%= Rack::Utils.escape_html(todo.body)%>
                </div>
              </li>
          <% end%>
        </ul>
      <% end %>
    </div>
  <% end %>

ユーザーの新規登録機能

Todoリストの入力フォームと同様にフォームを作成、入力した値をuserNameとして返すように設定。

index.erb
<div class=“user_form”>
  <form action=“/create_user” method = “post”>
    <%= Rack::Csrf.csrf_tag(env)%>
    <span>新規ユーザー名:</span>
    <input type=“text” name=“userName” class=“user_input_box”>
    <input type=“submit” value=“新規登録” class = ‘btn btn_user>
  </form>
</div>

usersテーブルにデータを生成。

tlist.rb
post /create_user do
  Users.create(userName: params[:userName])
  redirect to(/)
end

以上で大半は完成です。
その他トークン処理、ユーザー削除機能について、ここでは割愛します。
制作物はgithubにあげていますのでそちらをご覧ください。

その他メモ

Csrf(リクエスト強要)対策について
※(CSRF:Cross-site Request Forgery)
ユーザーの意図に反してリクエストが送信されることを防止するために設ける。
リクエスト送信先のページで正規のページからリクエストが送信されたか確認すれば良い。
処理としてはinputデータにhidden要素でトークンを仕込んで、リクエスト送信先でそのトークンが正式なものであるか確認をする。

参考:CSRF対策をひよっこエンジニアがまとめてみた(随時更新予定)|クロノドライブ

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

[RubyOnRails] active_hashはどんなデータに使うべきなのか

はじめに

Railsでアプリケーション開発を行う際に、ある程度実世界に存在するであろうレベル間のフォームを実装しようとすると必然的にactive_hashの存在にたどり着くと思います。

僕もまんまと辿りつき「便利やな〜」って思ってたんですが、色々作業を進める内に使うべき時とそうじゃない時の考え方が少しずつ定まってきたので、備忘として残しておく物です。

環境

以下の環境で行っています。

ruby 2.6.5
rails 6.0.3

active_hashを使うべきデータ

結論からいうと以下の考え方でいいのかなと。
①あまり重要ではないデータに使う
②静的なデータに使う
③複雑な構造を持たないデータに使う

結論から言うと上の二つだよねって感じでどこにでも書いてあるのですが、僕は②の認識が甘かったり、③をそもそも考えてなかったりだったなーと開発を進めてく中で学んでいった形です。詳しくは後述します。

そもそもactive_hashとは

こちらの記事こちらの記事が非常にわかりやすいです。おかげさまであまり困らず導入できました。(いつもいつも先人には頭があがりません)

gem active_hashを利用することで、テーブルを作らなくともアクティブレコードの様に使える静的データを作っておくことが出来るというもの。
(ちなみにgemの最新バージョンは許容する記載方法が減っているとのことで導入時には注意が必要です)

よく例に出されるのだと都道府県データとかがありますね。実際に記載すると以下の様になります。

prefecture.rb
class Prefecture < ActiveHash::Base
  self.data = [
      {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'},
      {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'},
      {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'},
      {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'},
      {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'},
      {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'},
      {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'},
      {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'},
      {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'},
      {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'},
      {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'},
      {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'},
      {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'},
      {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'},
      {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'},
      {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
  ]
end

modelの中にこの様なファイルを作るだけです。非常にシンプル。id:やname:がテーブルでいうカラム名ですね。
あとはアソシエーションをactive_hash用の記載で書いておくだけで簡単に使えます。

item.rb
class Item < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :prefecture
end

今回はitemテーブルにprefecture_idを持たせたかったので、モデルに上記の様に記載します。
アクティブハッシュ側にはアソシエーションの記載は不要です。
これでテーブルを作った場合とある程度同様に使える様になります。お手軽で便利。

静的なデータ、複雑じゃないデータって?

静的なデータ

静的なデータと聞いて、僕は最初クライアントサイドが増やさず、サーバーサイドで用意しておくデータくらいの認識でいました。

ただ、進めてる内にこの認識は不十分だなーと思って行った次第です。
具体的にいうと、例えサーバーサイド側で用意しておくデータであろうと、それが使用変更でどんどん増えて行ったり、後から値が変わる様なものならそれは静的とは言えないな、と

上記の書き方を見ても分かる通り、アクティブハッシュの各値はファイルにベタ内なので、これの更新行為ってめんどいやらリスクあるやらなんですよね(アナログ作業)

そこまで考えて、active_hashの使用例にやたら都道府県が出てくる理由をようやく理解しました。
都道府県って、クライアントサイドの都合でもサーバーサイドの都合でも増えないですもんね。

複雑なデータ

これは最初に知った時に少し思い当たる節がありました。僕は多対多のアソシエーションをくみたかったのですが、それってアクティブハッシュでは困難とのことだったので、あながち間違いじゃない認識だったんだなと。

あとは階層構造を持つテーブルとかも複雑なのでアクティブハッシュは使えないみたいですね。
そこらへんの認識が抜け落ちてました。

終わりに

「静的なデータ」と言う語の意味を正確に捉えられてなかったんだな〜と言う感想を開発を進めていく中で持った次第です。
やっぱ基本やってみないとわからないですね何事も。手を動かすあるのみ。

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

【Ruby】小数点同士の計算結果がズレる、違う、意図したものにならない

詳細は下記。

https://qiita.com/yusabana/items/fd4a0185c1f120403a74

BigDecimalを設定しておかないとだめって話なのだが、
インスタンス宣言元では使ってるのに対応されないってのが一つと、恐らくBigDecimal使うとインスタンス元と同じ桁数になるから、
.add、.sub、.mult、.divを使って第2引数に桁数指定して計算しないといけない可能性がある。

しかもこれ既存不具合で潜在してた。
それ以前に今回の変更箇所が不明になったので優先的では無いが、
何故か試験で精度を求められるようなので追加で報告相談して対応方針をクライアントに決めてもらう流れになるだろう。(勝手に決めてはいけない)

まーやらんといけんだろうが、
一度対応方針確認して了承得てると補足しておく。

既存仕様からの差分変更対応の落とし穴として
変更ないところで最終的にひっくり返されることになるのは勉強した。。。

もう二度とヤダ。。。

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

【Ruby】繰り返し処理について

本日はRubyの基礎的な繰り返し処理について書いていこうと思います。

for式

色々なプログラミング言語で見ると思います。
私は大分前にC言語をかじっていた事がありましたが、C言語とはまた記述が異なります。

for式の基本構文は以下です。

for 変数 in 繰り返し要素 do
  繰り返し処理
end
# サンプルプログラム
for num in 1..10 do
  puts num
end

doは省略可能です。
繰り返し要素の 1..10 はレンジオブジェクトと呼ばれ、
上記サンプルの場合は1から始まり、繰り返す度に1ずつ加算しながら10まで出力されます。
10まで出力されたらfor式の繰り返し処理は終了となります。

while式

while式は条件が真の間、処理を繰り返します。

# サンプルプログラム
num = 0
while num < 5
  puts num
  num += 1
end

0〜4まで順に出力されます。
while文の注意点としては終了条件にならないプログラムにしてしまうと無限ループとなり、処理が終わらなくなってしまいます。
num += 1を記述をしなかった場合、延々と0が出力され続けます。
終了条件はしっかり確認した上で記述しましょう。無限ループは誰もが一度は通る道だと思います…

until式

while式と逆で条件が偽の間、処理を繰り返します。

# サンプルプログラム
num = 0
until num >= 5
  puts num
  num += 1
end

numが5以上なら真、つまり4以下なら偽となるので、0〜4まで順に出力されます。
until文も無限ループに注意です。

私見では条件を真を前提にする方が分かりやすいと考えますので、
あまり使われないのかなとは感じていますが、実際のところはどうでしょうか。

loopメソッド

無限ループを記述する機能のようです。
while式などで意図的な無限ループを作るより、専用の構文を使うことで明確にできるというメリットがあるでしょうか。
実務のどういう局面で使われるかは判断し難いですが…

# サンプルプログラム
num = 0
loop do
  puts num
  num += 1
  break if num > 10
end

何も対応しなければもちろん無限ループしてしまうのですが、
breakを使用することにより、処理を中断する事が可能です。
サンプルではif式により、numが10未満の間はループし、0〜10まで順に出力されます。

breakなどの繰り返し処理の制御については次回の記事で改めて書きます。
今回は以上となります。

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

permutationメソッドを使って初期設定ユーザー達を全員相互フォロー関係にする

はじめに

初めてqiitaに投稿します初学者と申します。
文言に誤り等がありましたらご指摘いただけますと幸いです。

作成中の転職活動用ポートフォリオにて、初期データのユーザー達をseeds.rbで相互フォロー関係にさせておりました。
とある企業のエンジニア様からwantedlyにてコードレビューを含んだ返信をいただき、
このseeds.rbに関して「ロジックが異常に分かりにくい」と指摘を受けました。

修正前のseeds.rb

下記のコードで初期設定ユーザー達を相互フォロー関係にさせておりました。

db/seeds_relationships.rb
19.times do |n| # 19回処理を繰り返す
  users = User.all # 変数usersに全てのUserオブジェクトを代入
  user = users.find(n + 1) # 変数usersから指定したid(n+1)のUserオブジェクトを変数userに代入
  following = users[0..18] # 変数followingに配列usersの1~19番目までのUserオブジェクトを代入
  following.shift(n + 1) # shiftメソッドで配列followingの先頭から(n+1)つの要素を取り除く
  followers = users[0..18] # 変数followersに配列usersの1~19番目までのUserオブジェクトを代入
  followers.shift(n + 1) # shiftメソッドで配列followersの先頭から(n+1)つの要素を取り除く
  following.each { |followed| user.follow(followed) } # userが配列followingに含まれる各ユーザー(followed)をフォローする
  followers.each { |follower| follower.follow(user) } # 配列followersに含まれる各ユーザー(follower)がuserをフォローする
end

一目でわかると思いますが・・・異常な分かりづらさです。

Array#permutationを使いなさい

そのエンジニア様からは上記のように指摘を受け、
permutationとは何だろう?と思い、翻訳してみると「順列」という意味であることが分かりました。

順列とは

異なるn個の中から異なるr個を取り出して1列に並べる数のことです。
【高校数学】1から分かる順列と組み合わせの違い

AB,BA,BC,CB,AC,CAみたいなヤツのことですね()。

順列を作成する

[1, 2, 3].permutation do |x|
  p x
end

配列の中の要素から下記のような順列が作成されます。

[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]

permutationの後ろに(n)とすることで、配列の中から異なるn個の要素を取り出して並べることができます。

[1, 2, 3].permutation(2) do |x|
  p x
end
[1, 2]
[1, 3]
[2, 1]
[2, 3]
[3, 1]
[3, 2]

引用元:順列 (permutation) を作成する

修正後のseeds.rb

db/seeds_relationships.rb
users = User.all.to_a
users.permutation(2) do |n|
  n[0].follow(n[1])
end

1番目のユーザーが2,3,4,5…番目のユーザーをフォロー、
2番目のユーザーが1,3,4,5…番目のユーザーをフォロー、
3番目のユーザーが1,2,4,5…番目のユーザーをフォロー、となり、
permutationメソッドを使って初期設定ユーザー達を全員相互フォロー関係にすることができました。

【追記】もっと見やすくseeds.rb

db/seeds_relationships.rb
users = User.all.to_a
users.permutation(2) do |user1, user2|
  user1.follow(user2)
end

コメントをいただき修正しました。
可読性がかなり良いですね!

環境

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

[Ruby] yieldがあるのにblock引数がない

こういう関数

enumerable.rb
module Enumerable
  #省略
  def all?(*several_variants)
    yield to_enum.next; __unknown
  end
end

all?はこんな感じで使う

sample.rb
%w[ant bear cat].all? { |word| word.length >= 3 } #=> true

引数に&blockがあるわけでもないのに、なんで渡せるのか?
それはメソッド中にyieldがあればブロック引数は省略できるからです。

ただ、メソッドの中を見ないとブロックを渡せるかわからなくなるので、省略するかは考える必要がありそうです。

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

ユーザー退会機能の実装

こんにちは、tt_tsutsumiです。
今回はユーザー退会機能について実装を行いたいと思います。
こちらの記事が何方かのお役に立てると嬉しいです。

ユーザー新規登録、編集の記事は折を見て記載したいと思いますので、
今回はユーザー退会機能と退会済みユーザーがログインを行えない様に実装致します。

1. 会員状況の定義

ユーザーの会員状況をenum(boolean型)で設定をする。
boolean型は真偽値を保存する型で、2つの状況しか登録する事が出来ません。
今回はユーザーが 有効会員か退会済み会員か の2択なのでこの型を使用します。
また is_activetrue(有効会員) の場合ログインが出来る設定を行います。

is_activefalse(退会済み会員) の場合はログインが行えません。

user.rb
    enum is_active: {Available: true, Invalid: false}
    #有効会員はtrue、退会済み会員はfalse

    def active_for_authentication?
        super && (self.is_active === "Available")
    end
    #is_activeが有効の場合は有効会員(ログイン可能)

2. routeの記載

routes.rb
    resources :users do
        member do
            get "check"
            #ユーザーの会員状況を取得
            patch "withdrawl"
            #ユーザーの会員状況を更新
        end
    end

3. アクションの作成

次にコントローラーにアクションの作成を行います。
※ 今回は退会確認を行うページの作成も行ったのでアクションが2つとなります。

users_controller
def check
    @user = User.find(params[:id])
    #ユーザーの情報を見つける
end

def withdrawl
    @user = User.find(current_user.id)
    #現在ログインしているユーザーを@userに格納
    @user.update(is_active: "Invalid")
    #updateで登録情報をInvalidに変更
    @user.destroy
    #destroyを行う
    reset_session
    #sessionIDのresetを行う
    redirect_to root_path
    #指定されたrootへのpath
end

private

def user_params
    params.require(:user).permit(:active)
end

4. リンク先の作成

リンク先の作成を行いユーザーを退会させます。
methodは削除ではなく更新になるのでpatchの記載となります。

withdrawl.html.erb
<div class="withdrawl">
    <%= link_to "Withdrawal", withdrawl_user_path(@user.id), method: :patch %>
</div>

これでユーザー退会機能と退会済みユーザーがログインを行えない様に実装が行えました。
ご覧いただきありがとうございました !!

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

[Rails]ユーザー退会機能の実装

こんにちは、tt_tsutsumiです。
今回はユーザー退会機能について実装を行いたいと思います。
こちらの記事が何方かのお役に立てると嬉しいです。

ユーザー新規登録、編集の記事は折を見て記載したいと思いますので、
今回はユーザー退会機能と退会済みユーザーがログインを行えない様に実装致します。

1. 会員状況の定義

ユーザーの会員状況をenum(boolean型)で設定をする。
boolean型は真偽値を保存する型で、2つの状況しか登録する事が出来ません。
今回はユーザーが 有効会員か退会済み会員か の2択なのでこの型を使用します。
また is_activetrue(有効会員) の場合ログインが出来る設定を行います。

is_activefalse(退会済み会員) の場合はログインが行えません。

user.rb
    enum is_active: {Available: true, Invalid: false}
    #有効会員はtrue、退会済み会員はfalse

    def active_for_authentication?
        super && (self.is_active === "Available")
    end
    #is_activeが有効の場合は有効会員(ログイン可能)

2. routeの記載

routes.rb
    resources :users do
        member do
            get "check"
            #ユーザーの会員状況を取得
            patch "withdrawl"
            #ユーザーの会員状況を更新
        end
    end

3. アクションの作成

次にコントローラーにアクションの作成を行います。
※ 今回は退会確認を行うページの作成も行ったのでアクションが2つとなります。

users_controller
def check
    @user = User.find(params[:id])
    #ユーザーの情報を見つける
end

def withdrawl
    @user = User.find(current_user.id)
    #現在ログインしているユーザーを@userに格納
    @user.update(is_active: "Invalid")
    #updateで登録情報をInvalidに変更
    @user.destroy
    #destroyを行う
    reset_session
    #sessionIDのresetを行う
    redirect_to root_path
    #指定されたrootへのpath
end

private

def user_params
    params.require(:user).permit(:active)
end

4. リンク先の作成

リンク先の作成を行いユーザーを退会させます。
methodは削除ではなく更新になるのでpatchの記載となります。

withdrawl.html.erb
<div class="withdrawl">
    <%= link_to "Withdrawal", withdrawl_user_path(@user.id), method: :patch %>
</div>

これでユーザー退会機能と退会済みユーザーがログインを行えない様に実装が行えました。
ご覧いただきありがとうございました !!

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

【忘備録】実務に入ったので正規表現をしっかり勉強(したい)【正規表現】

 参考記事

https://qiita.com/jnchito/items/893c887fbf19e17d3ff9
https://qiita.com/jnchito/items/64c3fdc53766ac6f2008

正直、この記事の執筆者のお世話になりまくりである。なんならこの記事は上の記事のアウトプット記事なので、この記事を読むより良いかもしれない。

 正規表現とは

「パターンを指定して、文字列を効率よく検索・置換するためのミニ言語」

と上記の記事に書かれてました。ふむふむ。確かにおぼろげでは、あるが「パターン」や検索,置換には覚えがある。

 とりあえずやってみる。みてみる。

正規表現を学ぶためにhttps://rubular.com/ のサイトで遊んでみる。

test string はこんな感じにした。

名前:おにかん
電話:03-1234-5678
住所:東京都中央区1-2-3

\dと打つと数値のところだけ青く表示された。(ちなみにバックスラッシュはmacならoption+¥でいける)

つまり、\dは1個の半角数字(0123456789)を表す。\dメタ言語とも言い、文字の集合を表すことも意味するので文字クラスとも言うらしい。ふむふむ。

こいつはさっき出てきた「文字列を効率よく検索,置換するためのミニ言語」であり、「パターン」の一つと言うことか。

ともかく、\dは1個の半角数字を表すと覚えておこう。

 メタ文字を繋げてみる。

\dは1つの半角数字を表すので、2つ、3つを表す場合を学習してみる。

\d\d-\d\d\d\d-\d\d\d\dと打ってみた。今度はハイフンも含めた全体が選択されている。

\d\dは二つの連結した半角数字を表す。(12や34みたいな。)

 Rubyで動かしてみる

text = <<-TEXT
名前:おにかん
電話:03-1234-5678
住所:東京都中央区1-2-3
TEXT
text.scan /\d\d-\d\d\d\d-\d\d\d\d/
# => ["03-1234-5678"]

ちなみに、text = <<- TEXTの部分が見慣れていない人は「Ruby ヒアドキュメント」とかで検索すると良いかも。

 JavaScriptでも動かしてみる

const text = "名前:おにかん\n電話:03-1234-5678\n住所:東京都中央区1-2-3";
text.match(/\d\d-\d\d\d\d-\d\d\d\d/g);
// => ["03-1234-5678"]

\nは改行コード。gはグローバルオプションと呼ばれる。有りと無しでは以下の違いがある。

  • あり:最初の一件が見つかったら、検索終了。
  • なし:一致する文字列を抽出。

 市外局番に対応

/\d\d-\d\d\d\d-\d\d\d\d/は全ての番号に対応しているわけではありません。例えば、

  • 090-1234-5678
  • 0120-1234-5678

などなど。これに対応できるような正規表現を学ぶ。ここで大切なことは、検索対象のパターンを見つけること。今回の場合は下記の通り。

  • 半角数字の2~5個
  • ハイフン
  • 半角数字の1~4個
  • ハイフン
  • 半角数字4個

この順番で並ぶ。ここで出てくる新知識は{n,m} や {n}というメタ文字を使う。文字量を指定しているので、量指定子と呼ばれる。

{n,m}は「直前の文字が n 個以上、m 個以下」であることを表す。例えば、\d{1,4}だったら半角数字で1文字以上4文字以下を表す。

ということで、さっきのパターンに当てはめるとこんな感じになる。

\d{2,5}-\d{1,4}-\d{4}となるはず。

 かっこにも対応したい!

上記のような例では、"03(1234)5678"などに対応できない。なので、ハイフンでも、かっこでも対応できるように変えたい

新しくパターンとして組み込まれるのは「ハイフンまたは(」と「ハイフンまたは)」です。ここで新知識が出てきます。

「AまたはBのいずれか1文字」→[AB]を意味する。(文字の集合を表すので、文字クラスの一種。)ちなみに、[]の文字数に制限はない。[ABC]はどれかの一文字を表す。

ということで、「ハイフンまたは(」は[-(]。「ハイフンまたは)」[-)]と表す。では、全体を書いてみる。

\d{2,5}[-(]\d{1,4}[-)]\d{4}

ハイフンは特別な意味を持つことがあります。例えば[A-Z]は「AまたはBまたはCまたは...Z」を表す。つまり全角の英一文字を表します。つまり、文字の範囲を表すことがあります。

[-AB][AB-]みたいに[]の中で最初か最後にハイフンが入るとハイフンそのものとしてとらえられます。

 まとめ

  • \dは半角数字一文字を表す
  • {n,m}は直前の文字がn以上、m文字以下であることを表す。
  • {n}は、ちょうど一文字を表す。
  • [ab]aまたはbの一文字
  • [a-z]は、aまたはbまたはcまたは...zの一文字
  • [-az]は、-またはaまたはzを表す。

 最後に

冒頭でも言いましたが、この記事はアウトプット記事なのでそちらを参考にするといいと思います。

https://qiita.com/jnchito/items/893c887fbf19e17d3ff9

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

permission denied(パーミッション ディナイド)とは? 

概要

今日は自動デプロイの学習中にpermission denied問題で悩み、権限系の知識が浅いと感じましたので復習を兼ねて詳しく調べてみました。

permissionとは英語で「許可や権限」を指し、deniedとは「否定の過去形で否定した」という意味を持ちます。つまり、permission deniedとは「権限がありません」というような意味を持ちます。
そして、プログラミングにおけるpermissionは「誰に、どのような操作を許可するのか」という権限を規定する情報が設定されています。この情報のことを、パーミッションと呼びます。

パーミッションを確認するには?

lsコマンド-lオプションで実行します。
するとこんな感じの画面になるかと思います。

ターミナル
$ ls -l 
drwxr-xr-x. 20 root  root  2077 July  5 14:34 bin/cat

「d」はディレクトリを指し、rwxはr(読み込み)、w(書き込み)、x(実行)を表し、それぞれ左側から「オーナー」、「グループユーザー」、「その他ユーザー」を表しています。
したがって「rwxr-xr-x」のような表示になっています。
※この場合だと、オーナーには「全権限」があって、グループユーザーとその他ユーザーは「読み込みと実行」が可能で「書き込み」の権限がないと言われています。

権限を変更するには?

chmodコマンドを指定します。chmodは「チェンジモード」と読みます。
書式は以下のようになります。
chmod <8進数の数値> <ファイル名>

意味  数字
読み取り(r) 4
書き込み(w) 2
実行(x) 1

例として、以下のようにコマンドを入力することで権限を変更できます。
今回だと2行目のchmod 755 file.txtの部分が当たります。

ターミナル
$ ls -l file.txt
-rw-r--r--  1 takuya  staff  0 Jul 12 18:58 file.txt

$ chmod 755 file.txt

$ ls -l file.txt
-rwxr-xr-x  1 takuya  staff  0 Jul 12 18:58 file.txt*

つまり、「755」というのは「7(4+2+1)5(4+1)5(4+1)」であり、ユーザには全てに権限を、グループとそののユーザには「読み込み」と「実行」する権限を与えているわけです。

スーパーユーザー

ありとあらゆる操作が許可された強い権限を持つユーザがスーパーユーザです。ユーザ名がrootであることから「rootユーザ」とも呼ばれます。スーパーユーザはあらゆる操作が許可された強い権限をもつユーザであり、設定ファイルを変更したり、新しいアプリケーションをインストールしたりすることができますが、強い権限をもつだけにリスクも伴いますので、通常は一般ユーザでログインして操作し、必要な時だけスーパーユーザとして作業するといったことが大切になります。
コマンドとしてsuコマンドsudoコマンドがありますが、違いは、suコマンドを実行したのちには、exitコマンドでsuperuser状態から抜ける必要があります。
また、sudoコマンドはrootユーザーに成り代わってコマンドを実行するのに対して、suコマンドはrootユーザーにそのまま成り代わって操作することを可能にするため、rootユーザーのパスワードが求められます。

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

【Rails】型が変換される前の値にバリデーションをかけたい(_before_type_cast)

はじめに

ActiveRecordではDate型やInteger型のカラムに文字列を入れると自動でそのカラムの型に変換してくれます。

例えば以下のようなusersテーブルがあったとします。(簡単の為にcreated_atやupdated_atは省略してます。)

db/schema.rb
  create_table "users", force: :cascade do |t|
    t.string "name"
    t.integer "age"
  end

試しにRailsコンソールを使っていろいろな値を入れてみましょう。

irb(main):001:0> User.new(name: "太郎", age: 21) #ageに数値を入れてみる
=> #<User name: "太郎" , age: 21>

irb(main):002:0> User.new(name: "太郎", age: "21") #ageに文字列を入れてみる
=> #<User name: "太郎", age: 21>

irb(main):003:0> User.new(name: "太郎", age: "21") #ageに文字列を入れてみる(1は全角数字)
=> #<User name: "太郎", age: 2>

1つ目は数値を入れています。数値なのでそのまま入りますね。

2つ目は文字列を入れています。文字列の"21"が数値の21に変換されていますね、すばらしいです。

3つ目はどうでしょうか、"21"の1の部分は全角数字です。
なんと、age: 2になってしまっています。21とは全然違いますね。

ちなみに"2 1"(間にスペースを入れる)や"2ア"(数字以外の文字を入れる)などしても、age: 2となります。

何が困るのか

数字以外の入力を弾こうとして以下のようなバリデーションをかけたとします。

models/user.rb
  validates :age, format: { with: /\A[0-9]+\z/}

この場合、ユーザーが"2 1", "2ア"などの入力をするととageの2に対してバリデーションがかかることになり、正しいデータとして保存されてしまいます。

解決策 「_before_type_cast」

解決策として、ageに_before_type_castをつけると型が変換された後の値ではなくユーザーの入力した値に対して、バリデーションをかけることができます。※参考「rails/validatesでbefore_type_cast」

models/user.rb
  validates :age_before_type_cast, format: { with: /\A[0-9]+\z/}, presence: true

応用(文字列の変換など)

_before_type_castはバリデーションの他に、文字列の英数字を全角から半角に変換したり、空白を取り除いてから保存したい場合などにも便利です。

以下のコードではMojiというgemを使って全角→半角の変換をしています。
age_before_type_castでユーザーの入力したそのままの文字列を受け取って変換した後、ageに変換した文字列を入れています。

models/user.rb
  validates :age_before_type_cast, format: { with: /\A[0-9]+\z/}, presence: true

  before_validation do
    string = self.send(:age_before_type_cast)
    string = Moji.normalize_zen_han(string) 
    send(:write_attribute, :age, string)
  end

_before_type_castの性質

〇〇と〇〇_before_type_castの違い

まず通常の属性と_before_type_castのついた属性の違いです。
[:〇〇]では値が取得できません。
具体的には

irb(main):001:0> user = User.new(name: "太郎", age: "21")

irb(main):002:0> user.age
=> 21
irb(main):003:0> user.age_before_typecast
=> "21"

irb(main):004:0> user[:age] 
=> 21
irb(main):005:0> user[:age_before_typecast] 
=> nil 

user.age_before_type_castなら値が取得できますが、
user[:age_before_type_cast]を使って値を取得しようとするとnilが返ってきてしまいます。

〇〇_before_type_castの更新はできない

irb(main):001:0> user.age = 21
=> 21
irb(main):002:0> user.age_before_type_cast = 21
=> #NoMethodErrorが発生します。

_before_type_castはageへの型変換前の入力を確認するためのものなので、それ自体を更新することはもちろんできないですね。

save後はどうなるか

また、saveすると〇〇と〇〇_before_type_castは同じになります。
具体的には、

irb(main):001:0> user = User.new(name: "太郎", age: "21")
irb(main):002:0> user.save
irb(main):003:0> user.age_before_typecast = 21

文字列の"21"ではなく、数値の21が返ってきています。

以上のような性質があるので実装やテストの際は気をつけましょう!

参考記事

rails/validatesでbefore_type_cast

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

【Rails/ActiveRecord】型が変換される前の値にバリデーションをかけたい(_before_type_cast)

はじめに

ActiveRecordではDate型やInteger型のカラムに文字列を入れると自動でそのカラムの型に変換してくれます。

例えば以下のようなusersテーブルがあったとします。(簡単の為にcreated_atやupdated_atは省略してます。)

db/schema.rb
  create_table "users", force: :cascade do |t|
    t.string "name"
    t.integer "age"
  end

試しにRailsコンソールを使っていろいろな値を入れてみましょう。

irb(main):001:0> User.new(name: "太郎", age: 21) #ageに数値を入れてみる
=> #<User name: "太郎" , age: 21>

irb(main):002:0> User.new(name: "太郎", age: "21") #ageに文字列を入れてみる
=> #<User name: "太郎", age: 21>

irb(main):003:0> User.new(name: "太郎", age: "21") #ageに文字列を入れてみる(1は全角数字)
=> #<User name: "太郎", age: 2>

1つ目は数値を入れています。数値なのでそのまま入りますね。

2つ目は文字列を入れています。文字列の"21"が数値の21に変換されていますね。素晴らしいです。

3つ目はどうでしょうか、"21"の1の部分は全角数字です。
なんと、age: 2になってしまっています。21とは全然違いますね。

ちなみに"2 1"(間にスペースを入れる)や"2ア"(数字以外の文字を入れる)などしても、age: 2となります。

何が困るのか

数字以外の入力を弾こうとして以下のようなバリデーションをかけたとします。

models/user.rb
  validates :age, format: { with: /\A[0-9]+\z/}

この場合、ユーザーが"2 1", "2ア"などの入力をするととageの2に対してバリデーションがかかることになり、正しいデータとして保存されてしまいます。

解決策 「_before_type_cast」

解決策として、ageに_before_type_castをつけると型が変換された後の値ではなくユーザーの入力した値に対して、バリデーションをかけることができます。※参考「rails/validatesでbefore_type_cast」

models/user.rb
  validates :age_before_type_cast, format: { with: /\A[0-9]+\z/ }, presence: true

応用(文字列の変換など)

_before_type_castはバリデーションの他に、文字列の英数字を全角から半角に変換したり、空白を取り除いてから保存したい場合などにも便利です。

例として以下のコードではMojiというgemを使って全角→半角の変換をしています。

今回はユーザーの“21”(1は全角)などの全角と半角が混じった入力をバリデーション前に半角に揃えたいので_before_type_castを使います。

age_before_type_castでユーザーの入力したそのままの文字列を受け取って変換した後、ageに変換した文字列を入れています。

models/user.rb
  validates :age_before_type_cast, format: { with: /\A[0-9]+\z/ }, presence: true

  before_validation do
    string = self.send(:age_before_type_cast)
    string = Moji.normalize_zen_han(string) 
    send(:write_attribute, :age, string)
  end

_before_type_castの性質

〇〇と〇〇_before_type_castの違い

まず通常の属性とbefore_type_castのついた属性の違いです。
[:〇〇_before
type_cast]では値が取得できません。
具体的には

irb(main):001:0> user = User.new(name: "太郎", age: "21")

irb(main):002:0> user.age
=> 21
irb(main):003:0> user.age_before_typecast
=> "21"

irb(main):004:0> user[:age] 
=> 21
irb(main):005:0> user[:age_before_typecast] 
=> nil 

user.age_before_type_castなら値が取得できますが、
user[:age_before_type_cast]を使って値を取得しようとするとnilが返ってきてしまいます。

〇〇_before_type_castの更新はできない

irb(main):001:0> user.age = 21
=> 21
irb(main):002:0> user.age_before_type_cast = 21
=> #NoMethodErrorが発生します。

_before_type_castはageへの型変換前の入力を確認するためのものなので、それ自体を更新することはもちろんできないですね。

save後はどうなるか

また、saveすると〇〇と〇〇_before_type_castは同じになります。
具体的には、

irb(main):001:0> user = User.new(name: "太郎", age: "21")
irb(main):002:0> user.save
irb(main):003:0> user.age_before_typecast
=> 21

文字列の"21"ではなく、数値の21が返ってきています。

以上のような性質があるので実装やテストの際は気をつけましょう!

参考記事

rails/validatesでbefore_type_cast

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

(初心者向け)【Rails】Deviseのインストール

はじめに

こんにちは! Rails学習中のYori-goreng(ヨリゴレン)です:sunny:
本記事では、ログイン機能を司るdeviseの導入と、ユーザーのアクセス許可についてご紹介します:relaxed:

より詳しく知りたい方は以下の参考記事をご覧ください。

  • 参考記事

https://qiita.com/cigalecigales/items/f4274088f20832252374

https://qiita.com/tobita0000/items/866de191635e6d74e392

環境

  • ruby 2.6.3
  • rails 5.2.4.3
  • MacOS Catalina バージョン10.15.4

deviseとは

Rubyのgemの一つで、deviseを使うことでログインの機能を容易に実装できるようになります。

1. 【アプリの準備】

1.1. アプリ作成

rails new アプリ名でアプリを作成します。
ここでは、devise_testという名前にしてみます。

rails new devise_test
cd devise_test

1.2. DB作成

rails db:create

2. 【deviseの準備】

2.1. gemのインストール

gemfileにdeviseのgemを追記します。

gem 'devise'

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

bundle install

2.2. deviseの関連ファイルを作成

rails g devise:install

以下のような長文がターミナルに表示されれば、成功です。

===============================================================================

Some setup you must do manually if you haven't yet:

  1.Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4.You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================

2.3. モデル作成

以下のコマンドにより、Userモデルを作成します。

rails g devise user

この時点でマイグレーションを実行すると、簡易的なログインページが完成します。

rails db:migrate
rails s

rails sのあと、http://localhost:3000/users/sign_in でログインページを開きます。

スクリーンショット 2020-06-15 12.02.35.png

2.4. before_action :authenticate_user!

コントローラーにbefore_action :authenticate_user!を記載すると、ここで行われる処理がログインしたユーザーによってのみ実行することができるようになります。
例として、Homesコントローラーを作成します。
※コントローラーの命名規則に、homesのように複数形にするというものがあります。コントローラーの名前は自由に決めても問題ないようです。

rails g controller homes index

作成したhomes_controller.rbbefore_action :authenticate_user!を加えます。

app/controllers/home_controller.rb
class HomesController < ApplicationController
  before_action :authenticate_user!
  def index
  end
end

これにより、indexアクションによる一覧表示はログインしたユーザーのみ確認できます。

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

[Rails]belongs_toが定義されているモデルをcreateする時に発行されるSELECTを回避せよ!

問題編

Railsでbelongs_toが定義されているモデルをcreateする時にSELECT文が実行されることを知っていますか?

例えば下記のようなモデルがあるとします。

def User < ApplicationRecord
  has_many :reviews
end

def Review < ApplicationRecord
  belongs_to :user
  belongs_to :book
end

def Book < ApplicationRecord
  has_many :reviews
end

この時にreviewを作成すると下記のようにSQLが発行されます。

# id=1のuserとbookが存在すること
irb(main):001:0> Review.create!(user_id: 1, book_id: 1)
   (0.5ms)  BEGIN
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.5ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1 LIMIT 1
  Review Create (0.6ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-30 14:31:27.343637', '2020-06-30 14:31:27.343637')
   (2.0ms)  COMMIT

belongs_toが定義されているusersとbooksにSELECTされていますね。

SELECTされる理由は単純です。
belongs_toはデフォルトでは関連モデルの存在が必須です。
では必須の確認はどうしているのか?
先ほどのようにcreateする直前にSELECTして存在チェックしているのです。

ちなみに、もし関連モデルの存在が任意の場合はbelongs_to :user, optional: trueと書きます。
このように書くとSELECTは実行されません。
参照:Railsガイド
https://railsguides.jp/association_basics.html#optional

ということで、タイトルに書いてあるSELECTを回避する方法はoptional: trueを設定するです!...ではありません!!

確かにSELECTは発行されなくなりますが、関連モデルが必須なのであればoptional: trueを設定することは適切ではありません。

解決編

ではoptional: trueを設定せずにSELECTを回避するにはどうすればよいのか?
それはcreateにオブジェクトを渡してあげれば良いのです。

irb(main):002:0> user = User.first
  User Load (0.8ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User id: 1, name: "1234567890", created_at: "2019-12-12 05:43:52", updated_at: "2019-12-12 05:43:52">
irb(main):003:0> book = Book.first
  Book Load (0.6ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1
=> #<Book id: 1, title: "book1", created_at: "2020-06-15 14:21:15", updated_at: "2020-06-15 14:21:15">
irb(main):004:0> review = Review.create!(user: user, book: book)
   (0.4ms)  BEGIN
  Review Create (0.6ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-30 14:32:14.911478', '2020-06-30 14:32:14.911478')
   (2.0ms)  COMMIT

オブジェクトを渡すことでSELECTをするまでもなく存在が確認できているのでSELECT文が発行されなくなってます。
ただし、この例の場合だと直前で各モデルを別途SELECTしているからSQLの数は同じじゃねーか!!
というツッコミを受けそうですが、例えば同じuserのreviewを複数作るときなどはSQLの数が全然違います。

下記はuser1にbook1〜5のreviewを作成するコードです(可読性を高めるために一部省略しています)。
1つ目はidを指定してcreateします。

irb(main):039:0> user = User.first
irb(main):040:0> books = Book.where(id: [1, 2, 3, 4, 5])
irb(main):042:0> books.each do |book|
irb(main):043:1*   Review.create!(user_id: user.id, book_id: book.id)
irb(main):044:1> end
  Book Load (0.8ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` IN (1, 2, 3, 4, 5)
   (0.3ms)  BEGIN
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1 LIMIT 1
  Review Create (0.8ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-30 15:10:00.009569', '2020-06-30 15:10:00.009569')
   (3.6ms)  COMMIT
   (0.3ms)  BEGIN
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 2 LIMIT 1
  Review Create (0.4ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 2, '2020-06-30 15:10:00.020722', '2020-06-30 15:10:00.020722')
   (1.8ms)  COMMIT
   (0.4ms)  BEGIN
  User Load (0.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 3 LIMIT 1
  Review Create (0.4ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 3, '2020-06-30 15:10:00.029827', '2020-06-30 15:10:00.029827')
   (1.9ms)  COMMIT
   (0.3ms)  BEGIN
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 4 LIMIT 1
  Review Create (0.3ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 4, '2020-06-30 15:10:00.037725', '2020-06-30 15:10:00.037725')
   (1.7ms)  COMMIT
   (0.3ms)  BEGIN
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 5 LIMIT 1
  Review Create (0.4ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 5, '2020-06-30 15:10:00.045389', '2020-06-30 15:10:00.045389')
   (1.6ms)  COMMIT

次にオブジェクトを渡してcreateします。

irb(main):045:0> user = User.first
irb(main):046:0> books = Book.where(id: [1, 2, 3, 4, 5])
irb(main):047:0> books.each do |book|
irb(main):048:1*   Review.create!(user: user, book: book)
irb(main):049:1> end
  Book Load (0.8ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` IN (1, 2, 3, 4, 5)
   (0.5ms)  BEGIN
  Review Create (0.5ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-30 15:12:05.610003', '2020-06-30 15:12:05.610003')
   (2.8ms)  COMMIT
   (0.4ms)  BEGIN
  Review Create (0.4ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 2, '2020-06-30 15:12:05.617125', '2020-06-30 15:12:05.617125')
   (1.7ms)  COMMIT
   (0.3ms)  BEGIN
  Review Create (0.5ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 3, '2020-06-30 15:12:05.622432', '2020-06-30 15:12:05.622432')
   (1.8ms)  COMMIT
   (0.4ms)  BEGIN
  Review Create (0.5ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 4, '2020-06-30 15:12:05.627957', '2020-06-30 15:12:05.627957')
   (2.0ms)  COMMIT
   (0.4ms)  BEGIN
  Review Create (0.6ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 5, '2020-06-30 15:12:05.634191', '2020-06-30 15:12:05.634191')
   (1.8ms)  COMMIT

2つ目の実装だと10本のSELECTが省略できていますね!!
これを知っているかどうかで発行されるSQLが変わってくるので覚えておきましょう!

おまけ

前述の通りcreateをする時にbelongs_toの存在チェックをしているわけですが、createで渡したモデルや直前でSELECTした結果は存在チェックに使うだけではなく、きちんとアソシエーションにセットされているようです。

createで作成したモデルのアソシエーションを参照してもSQLが発行されません。

irb(main):051:0> review = Review.create!(user: user, book: book)
   (2.3ms)  BEGIN
  Review Create (0.6ms)  INSERT INTO `reviews` (`user_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-30 15:20:01.755647', '2020-06-30 15:20:01.755647')
   (2.5ms)  COMMIT
=> #<Review id: 36, content: "", user_id: 1, book_id: 1, status: "draft", created_at: "2020-06-30 15:20:01", updated_at: "2020-06-30 15:20:01">
# create時に渡したモデルが設定されているので、アソシエーションを参照してもSELECTが実行されない
irb(main):052:0> review.user
=> #<User id: 1, name: "1234567890", created_at: "2019-12-12 05:43:52", updated_at: "2019-12-12 05:43:52">
irb(main):053:0> review.book
=> #<Book id: 1, title: "book1", created_at: "2020-06-15 14:21:15", updated_at: "2020-06-15 14:21:15">
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails モデル作成・削除コマンド

モデル作成

モデルの作成コマンドは
rails g model モデル名(単数形、頭文字は大文字) カラム名1:データ型 カラム名2:データ型 …

例:
rails g model User name:string email:string

コマンド実行後はマイグレーションファイルを適用
rails db:migrate

モデル削除

モデルを削除するコマンドは
rails destroy model モデル名

例:
rails destroy model User

※ destroyを省略してdと書くことは不可

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

ruby でプログラミング(途中)

アウトプットです

前回学んだことを生かしてrubyを使ってプログラミングを組んでます。

まだ途中ですが今回はMUPで学んだ店舗の利益計算を組んでみたいと思います。
本当はささっと仕上げる予定が構想をねるほど長くなりそうに。。。

とりあえず下記が途中です

def input_data(total_datas)
  puts "店名を入力してください"
    store_name = gets.chomp
  puts "平日ランチの客数"
    weekday_lunch_guest = gets.to_i
  puts "平日ディナーの客数"
    weekday_dinner_guest = gets.to_i
  puts "休日ランチの客数"
    weekend_lunch_guest = gets.to_i
  puts "休日ディナーの客数"
    weekday_dinner_guest = gets.to_i
  puts "平日ランチの客単価"
    weekday_lunch_guest_price = gets.to_i
  puts "平日ディナーの客単価"
    weekday_dinner_guest_price = gets.to_i
  puts "休日ランチの客単価"
    weekend_lunch_guest_price = gets.to_i
  puts "休日ディナーの客単価"
    weekend_dinner_guest_price = gets.to_i
  puts "平日の営業日"
    weekday_business = gets.to_i
  puts "休日の営業日"
    weekend_business = gets.to_i
  puts "ランチの原価"
    lunch_cost = gets.to_i
  puts "ディナーの原価"
    dinner_cost = gets.to_i
  puts "家賃を入力してください"
    rent = gets.to_i
  puts "光熱費を入力してください"
    utility_cost = gets.to_i
  puts "人件費を入力してください"
    saraly = gets.to_i
  puts "月間仕入れを入力してください"
    stocking = gets.to_i

  total_data = {store_name: store_name, weekday_lunch_guest: weekday_lunch_guest, weekday_dinner_guest: weekday_dinner_guest, weekend_lunch_guest: weekend_lunch_guest, weekday_dinner_guest: weekday_dinner_guest, weekday_lunch_guest_price: weekday_lunch_guest_price,  weekday_dinner_guest_price:  weekday_dinner_guest_price, weekend_lunch_guest_price: weekend_lunch_guest_price, weekend_dinner_guest_price: weekend_dinner_guest_price, weekday_business: weekday_business, weekend_business: weekend_business, lunch_cost: lunch_cost, dinner_cost: dinner_cost, rent: rent, utility_cost: utility_cost, saraly: saraly, stocking: stocking}

  total_datas << total_data
end

def show_store(total_datas)
  total_datas.each_with_index do |total_data, i|
    puts "[#{i}]: #{total_data[:store_name]}"
  end

  puts "確認したい番号を入力して下さい。"
  input = gets.to_i
  total_data = total_datas[input]

  if total_data
    show_data(total_data)
  else
    puts "該当する番号はありません。"
  end
end

def show_data(total_data)
end




def end_program
  exit
end

def exception
  puts "入力された値は無効な値です"
end

total_datas = []

while true do
  puts "番号を入力してください"
  puts "[0]店を登録する"
  puts "[1]データを参照する"
  puts "[2]アプリを終了する"
    input = gets.to_i

  if input == 0 then
    input_data
  elsif input == 1 then
    show_store(total_datas)
  elsif input == 2 then
    end_program
  else
    exception
  end
end

show_data(total_data)で
ハッシュ内の値の数値を順次計算して最終的には利益予測をたてるみたいなのを組みたいなと考えています。
まぁExcelの方が手っ取り早いんですが笑

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