20200320のRubyに関する記事は27件です。

Rubyでシーザー暗号の解読

Rubyでシーザー暗号を解読して見た時のコードと解説をします。

シーザー暗号とは?

そもそもシーザー暗号とはなにかなんですが、単一換字式暗号の一種らしいのですが、
簡単に言うと文字を特定数の文字ずらした際にそれが暗号鍵になる暗号の事です。

例)
暗号
hsnvld
2文字進めた文字が答えとなると
jupxnf
になります。

これをRubyですぐ表示できる様にアルゴリズムを書いてみました。

Rubyでシーザー暗号の解読

暗号文に対して3文字進めるのが答えとします。
暗号文 hgmgkrd

def decryption(char)
  char_ary = char.split("")#1文字ずつに分割する
  changed_char_ary = []#空の配列を用意する
  char_ary.each do |char|
    changed_char_ary << (char.ord - 3).chr
     #ordメソッドで整数にしchrメソッドで文字に変換する
   #空の配列に入れる
  end
  puts changed_char_ary.join
     # =>edjdhoa
     #joinメソッドで配列を連結して文字列にする
end

char = "hgmgkrd" #暗号文を変数に入れる
decryption(char)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【忘備録】Ruby on rails : パスワード登録のバリデーション

前提

今回はユーザーの新規登録する場面を想定しています。
以下がユーザーを登録する為のusersテーブルです。
password_digest カラムが新規登録する際に設定するパスワードを格納しています。

+-----------------+--------------+------+-----+---------+----------------+
| Field           | Type         | Null | Key | Default | Extra          |
+-----------------+--------------+------+-----+---------+----------------+
| id              | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name            | varchar(255) | YES  |     | NULL    |                |
| email           | varchar(255) | YES  |     | NULL    |                |
| created_at      | datetime     | NO   |     | NULL    |                |
| updated_at      | datetime     | NO   |     | NULL    |                |
| password_digest | varchar(255) | YES  |     | NULL    |                |
+-----------------+--------------+------+-----+---------+----------------+

本題

railsではパスワードを設定する際に便利な機能が既に用意されています。
以下はusersテーブルを操作する為のモデルクラスです。
validates 群の下に has_secure_password とありますがこれはrailsに備わっている
メソッドです。
このメソッドをモデルクラスに追加することで今回の場合、usersテーブルのpassword_digest カラムに
暗号化されたパスワードを保存することが可能になります。(password_digest カラムは自分で作る必要があります。)厳密にはこの他にbcryptというgemが必要になるのでインストールしましょう。
インストールが済んだら正式に完了です。

class User < ApplicationRecord
  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, format: { with: /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i }
  validates :password, presence: true, length: { minimum: 6, maximum: 25 }, format: { with: /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,}+\z/i }

  has_secure_password
end

あとはこのメソッドを使用すると自動的に通常のパスワードと確認用のパスワードが一致しているかの確認を行ってくれます。フロント側に普通のパスワード用のフォームと確認用パスワード用のフォームを用意してsubmitすれば自動的に確認してくれます。

一応下記がそのフォームです。

        <%= form_for @user do |f| %>
          <div>
            <%= f.label :name %>
            <%= f.text_field :name %>
          </div>
          <div>
            <%= f.label :email %>
            <%= f.text_field :email %>
          </div>
          <div>
            <%= f.label :password %>
            <%= f.password_field :password %>
          </div>
          <div>
            <%= f.label :password_confirmation %>
            <%= f.password_field :password_confirmation%>
          </div>

        <%= f.submit "登録", class: "btn-block btn-white" %>
        <% end %>
        <%= link_to "ログイン", "#", class: "text-white" %>
      </div>
    </div>
  </div>
</div>

これがパスワードフォームで
<%= f.password_field :password %>

これがパスワード確認用のフォームです。
<%= f.password_field :password_confirmation%>

:passwordpassword_confirmationがparamsのハッシュのkeyとなって送信され、
keyのvalueが一致しているかモデルクラスで確認されますので名前は変えない方がいいと思います。

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

【忘備録】Ruby on rails : パスワード登録

前提

今回はユーザーの新規登録する場面を想定しています。
以下がユーザーを登録する為のusersテーブルです。
password_digest カラムが新規登録する際に設定するパスワードを格納しています。

+-----------------+--------------+------+-----+---------+----------------+
| Field           | Type         | Null | Key | Default | Extra          |
+-----------------+--------------+------+-----+---------+----------------+
| id              | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name            | varchar(255) | YES  |     | NULL    |                |
| email           | varchar(255) | YES  |     | NULL    |                |
| created_at      | datetime     | NO   |     | NULL    |                |
| updated_at      | datetime     | NO   |     | NULL    |                |
| password_digest | varchar(255) | YES  |     | NULL    |                |
+-----------------+--------------+------+-----+---------+----------------+

本題

railsではパスワードを設定する際に便利な機能が既に用意されています。
以下はusersテーブルを操作する為のモデルクラスです。
validates 群の下に has_secure_password とありますがこれはrailsに備わっている
メソッドです。
このメソッドをモデルクラスに追加することで今回の場合、usersテーブルのpassword_digest カラムに
暗号化されたパスワードを保存することが可能になります。(password_digest カラムは自分で作る必要があります。)厳密にはこの他にbcryptというgemが必要になるのでインストールしましょう。
インストールが済んだら正式に完了です。

class User < ApplicationRecord
  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, format: { with: /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i }
  validates :password, presence: true, length: { minimum: 6, maximum: 25 }, format: { with: /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,}+\z/i }

  has_secure_password
end

あとはこのメソッドを使用すると自動的に通常のパスワードと確認用のパスワードが一致しているかの確認を行ってくれます。フロント側に普通のパスワード用のフォームと確認用パスワード用のフォームを用意してsubmitすれば自動的に確認してくれます。

一応下記がそのフォームです。

        <%= form_for @user do |f| %>
          <div>
            <%= f.label :name %>
            <%= f.text_field :name %>
          </div>
          <div>
            <%= f.label :email %>
            <%= f.text_field :email %>
          </div>
          <div>
            <%= f.label :password %>
            <%= f.password_field :password %>
          </div>
          <div>
            <%= f.label :password_confirmation %>
            <%= f.password_field :password_confirmation%>
          </div>

        <%= f.submit "登録", class: "btn-block btn-white" %>
        <% end %>
        <%= link_to "ログイン", "#", class: "text-white" %>
      </div>
    </div>
  </div>
</div>

これがパスワードフォームで
<%= f.password_field :password %>

これがパスワード確認用のフォームです。
<%= f.password_field :password_confirmation%>

:passwordpassword_confirmationがparamsのハッシュのkeyとなって送信され、
keyのvalueが一致しているかモデルクラスで確認されますので名前は変えない方がいいと思います。

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

Rails アプリを EC2 にデプロイしよう!(サーバー構築編)

Rails アプリを Amazon Web Service を使ってデプロイするまでの手順をまとめました。
次の順番でデプロイまで持っていきます。

1, 準備編
2, サーバー構築編
3, 環境構築編
4, デプロイ編

今回は「サーバー構築編」です。
準備編」を読んでない方は先に読んでください。

EC2 インスタンスの作成

EC2とは Elastic Compute Cloud の略で、クラウド上に存在する仮想のサーバーのことです。
サーバーは、サービスを提供する (server) コンピューターのことです。
要するにパソコンを借ります。

早速借りましょう。

1, コンソール画面から検索するなりして「EC2」をクリックしてください。
2, 左のメニューバーから「インスタンス」をクリックしてください。
3, 左上辺りにある[インスタンスの作成]をクリックしてください。

AMIの選択

最初に「AMIの選択」を行います。
AMIとは Amazon Machine Image の略で、ここでは今作成しようとしているパソコン(EC2)のタイプを選択します。

4, 今回は Ruby を使用するので、
「Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type」
を選択しましょう。
Elastic Compute Cloud 2[AMI]
AMIを間違えるとRubyが動かないので注意しましょう。
OSもここで決まります。今回はAmazon Linuxを使用します。
名称が変わっている場合は、次のような説明文が書かれているものを選択してください。

Amazon Linux AMI は、AWS がサポートする EBS-backed イメージです。デフォルトのイメージには、AWS コマンドラインツール、Python、Ruby、Perl、および Java が含まれます。レポジトリには、Docker、PHP、MySQL、PostgreSQL、およびその他のパッケージが含まれます。

インスタンスタイプの選択

次に「インスタンスタイプの選択」を行います。
AWSにはさまざまな種類のCPUやメモリ、ストレージなどを組み合わせたインスタンスが存在しています。
今回は具体的なインスタンスタイプの種類について割愛します。

5, 今回は t2.micro を使用するので
次のインスタンスタイプを選択してください。
Elastic Compute Cloud 2[Instance Type]
今回は「無料枠」を選択しています。
無料枠が表示されない方もいるかもしれませんが、それについてはご了承ください。

インスタンスの詳細の設定

次は「インスタンスの詳細の設定」を行います。
いろいろ項目がありますが、意味は次の通りです。

名称 意味
インスタンス数 作成するインスタンスの数
スポットインスタンスのリクエスト 同じ地域の使用されていないインスタンスを、入札制で使用出来る仕組み。費用削減が期待できる。今回はチェックしない。
ネットワーク 使用するVPCを選択する。準備編で作成したVPCをプルダウンで選択しましょう。
サブネット 使用するサブネットを選択する。準備編で作成したサブネットをプルダウンで選択しましょう。
自動割り当てパブリックIP インターネットを通じてインスタンスにアクセスするためのIPアドレス。有効化します。
配置グループ プレイスメントグループというグループを作成することで、インスタンス間の通信速度を高速化させることができます。今回は使用しません。
キャパシティーの予約 任意の時間やタイミングでキャパシティー(性能)を上げることができます。今回は使用しません。
IAMロール IAMというユーザーの種類別にリソースの使用権限を割り振ったもの。誰がこのインスタンスにアクセス出来て、誰が出来ないかなどを設定できる。今回は設定しない。
シャットダウン動作 EC2をシャットダウンした際に削除するのか停止させるのか選択できる。
停止 - 休止動作 インスタンスの停止中に「休止状態」が選択できるようにする。
終了保護の有効化 インスタンスを誤って削除しないように設定できる。
モニタリング CloudWatchというインスタンス監視サービスを利用するかどうかを選択できる。利用すると追加料金が発生する。
テナンシー ハードウェアを占有するか、それとも他の利用者と共有するかを選択できる。
Elastic Inference 説明を割愛します。
T2/T3 無制限 説明を割愛します。

6, 今回は次のように設定します。
Elastic Compute Cloud 2[Config]

ストレージの追加

次は「ストレージの追加」を行います。
ストレージシステム(EC2)の管理に役立つ特別なディレクトリと構成ファイルが格納されている場所で、Windowsで言う所の「Cドライブ」に該当します。

7, 今回は次のように設定します。
Elastic Compute Cloud 2[Storage]

タグの追加

次は「タグの追加」を行います。
ここでEC2に名前をつけましょう。

8, 今回は次のように設定します。
Elastic Compute Cloud 2[Tag]

セキュリティグループの設定

最後に「セキュリティグループの設定」を行います。
準備編で設定したセキュリティグループを選択しましょう。

9, 今回は次のように設定します。
Elastic Compute Cloud 2[Security Group]

インスタンス作成の確認

これまでの手順を振り返って問題がなければ、
右下にある「起動」をクリックしましょう。

クリックすると「既存のキーペアを選択するか、新しいキーペアを作成します。」という画面が出てきます。

新しいキーペアの作成

SSH という通信プロトコルを使って安全な通信を行います。
この通信では、ユーザーはプライベートファイルをAWSから保存して、これを使用してインスタンスに接続します。

10, 新しいキーペアを作成して、キーペアをダウンロードしてください。
Elastic Compute Cloud 2[Key Pair]
再度ダウンロードすることはできないので注意してください。
また、安全な場所に保存するようにしてください。

これを保存した後、やっとインスタンスを作成することができます。

11, 「インスタンスの作成」をクリックしましょう。
12, 作成ステータスが表示されますが、そのまま「インスタンスの表示」をクリックしましょう。

以下のようにインスタンスが作成されていれば OK です。

Elastic Compute Cloud 2[Instance Index]

Elastic IP の設定

今回の最後に Elastic IP を設定していきます。

EC2サーバーはインターネットに接続を行う場合、どのサーバーも例外なくパブリックIPというものを持つ必要があります。そして、先ほど作成したEC2インスタンスにも作成時にパブリックIPが自動で割り振られるのですが、サーバーを再起動させるたびにこのパブリックIPが変わってしまうという欠点を持っています。

そこで Elastic IP を使用します。
これは、先に固定のIPアドレスを他に使用されないように押さえてしまい、それをパブリックIPとしてEC2インスタンスに紐付けることで、インスタンスの起動、停止に関わらず常に同じIPで通信を行うことができます。

では早速作成していきましょう。

1, 左のメニューバーから「Elastic IP」をクリックしてください。
2, 右上辺りにある「セキュリティグループの作成」をクリックしてください。
3, 「Amazon の IPv4 アドレスプール」が設定されていると思います。
4, そのまま「割り当て」をクリックしましょう。
Elastic IP[一覧]
ここでは既にNameタグを設定していますが、「タグの管理」をクリックすれば設定できます。

次に、このIPを先ほど作成したインスタンスに割り当てましょう。

5, 右上辺りにある「Action」から「Elastic IP アドレスの関連付け」を選択してください。
Elastic IP[割り当て01]
6, 先ほど作成したインスタンスとそのパブリックIPを選択して「関連付ける」をクリックしてください。
Elastic IP[割り当て02]

以下の画面のように、インスタンスが関連づけられていれば OK です。
Elastic IP[割り当て03]

最後に

今回は「サーバー構築編」でした。
次回からやっとコマンドラインを使用することになります(長い!)。

それでは次回「環境構築編」お楽しみに。

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

【Rails】bundler-auditの使い方

bundler-auditとは

bundler-auditはプロジェクトで利用しているGemの脆弱性の有無をチェックしてくれるGemです。
システムの脆弱性チェックの一つとして利用できますね。

検証した環境

  • Ruby 2.7.0
  • bundler-audit 0.6.1

準備

bundle install

Gemfileに以下を追加してbundle install

Gemfile
group :development do
  gem 'bundler-audit'
end
$ bundle install

脆弱性チェックに使用するDBを更新する

脆弱性チェックを実行する前に、bundler-auditが使用するDBの更新を行いましょう。

$ bundle exec bundler-audit update

このコマンドでは、実際には$HOME/.local/shareディレクトリ配下にrubysec/ruby-advisory-dbgit cloneしているようです。

脆弱性の存在するGemをチェックする

実際にbundler-auditを実行してGemの脆弱性をチェックしてみましょう。
以下のコマンドで実行できます。
脆弱性が見つからなかった場合はNo vulnerabilities foundと出力されます。

$ bundle exec bundler-audit
No vulnerabilities found

DBの更新と脆弱性チェックを同時に実行する

以下のコマンドで脆弱性チェックに使用するDBの更新と脆弱性チェックを同時に実行できます。
DBの更新忘れを防ぐためにも、基本的には以下のコマンドを利用するほうがいいでしょう。

$ bundle exec bundler-audit check --update

実際に脆弱性があるGemをインストールして検証してみる

bundler-auditの出力を確認するために、実際に脆弱性が存在するGemのインストール~脆弱性チェック~修正までやってみます。

脆弱性があるGemをインストール

実際に脆弱性が存在するGemをインストールしてみます。
ここでは試しにbootstrapv4.3.0をインストールしてみます。
Gemfileに以下を追記してbundle install

Gemfile
gem 'bootstrap', '4.3.0'
$ bundle install

脆弱性をチェックする

bundler-auditを実行してみます。

$ bundle exec bundler-audit
Name: bootstrap
Version: 4.3.0
Advisory: CVE-2019-8331
Criticality: Medium
URL: https://blog.getbootstrap.com/2019/02/13/bootstrap-4-3-1-and-3-4-1/
Title: XSS vulnerability in bootstrap
Solution: upgrade to >= 4.3.1

Vulnerabilities found!

脆弱性が見つかったGemとその内容、解決するバージョンまでわかりやすく出力してくれます。

脆弱性を無視する

場合によってはbundler-auditによって見つかった脆弱性を無視したいこともあるかと思います。
そういうときは--ignoreオプションに無視したいAdvisoryを指定しましょう。

$ bundle exec bundler-audit --ignore CVE-2019-8331
No vulnerabilities found

脆弱性のあるGemのバージョンを上げる

先ほどbundler-auditで出力された内容には

Solution: upgrade to >= 4.3.1

と書いてあったので、それに従ってGemfileを修正してみます。
(※実際にはGemfileにバージョンを指定していない場合の方が多いと思うので、その場合はbundle updateコマンド等を利用してください。)

Gemfile
-gem 'bootstrap', '4.3.0'
+gem 'bootstrap', '4.3.1'
$ bundle install

bootstrapのバージョンを上げたのでもう一度bundler-auditを実行してみます。

$ bundle exec bundler-audit
No vulnerabilities found

警告が出なくなりました!

参考

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

bundle install でNokorigiインストール時に「Gem::Ext::BuildError: ERROR: Failed to build gem native extension.」エラーが出た場合の対処方法

bundle installでnokogiriインストール時に下記のようなエラー

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

が出てしまった場合

gem install時であれば --use-system-libraries オプションを付けて再実行してみるところですがbundlerの場合はどうすればいいかというと

bundle config --local build.nokogiri --use-system-libraries

というような感じでコマンドを実行します。

--localオプションはapp/.bundle/configへ設定を書きます。
--globalオプションは~/.bundle/configに書くようです。

まあよほどの事情がない限り、プロジェクトごとの.bundle/configに書いておくほうが良いと思います。その他、環境変数にも定義できます。

bundle configの優先順位

bundle configですが、mysqlなどと同様、複数の箇所に設定を保持しておけます。用途によって適切な場所に設定を書いておくのがいいと思いますが、bundleコマンドのhelpによると

DESCRIPTION
       This command allows you to interact with Bundler´s configuration system.

       Bundler loads configuration settings in this order:

       1.  Local config (app/.bundle/config)

       2.  Environmental variables (ENV)

       3.  Global config (~/.bundle/config)

       4.  Bundler default config

という優先順位で参照されます。

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

【14日目】Ruby(メソッド,クラス,インスタンス)

はじめに

こんばんは。
今日でprogateのRubyカリキュラムは終了しました!
こんなレベルで大丈夫なのか一抹の不安は残りますが、
とりあえず進んでいこうと思います。

本日の学び

  • progate Ruby3
  • progate Ruby4
  • progate Ruby5

Ruby

メソッド

複数の処理を1つにまとめたもの

def print_info #def メソッド
  puts "わんこでんきへようこそ!"
  puts "今日はヘッドホンがセール中です!"
end

引数

複数の処理を1つにまとめたもの
メソッド内で作った変数はメソッド内でしか使えないので注意
キーワード引数は項目が分かりやすくなる

def print_info(item,price) #itemとpriceが引数
  puts "わんこでんきへようこそ!" 
  puts "今日は#{item}がセール中で#{price}円です!!"
end

print_info("SDカード",1200) #引数の値


def buy(item:, price:, count:) #キーワード引数
    puts "#{item}#{count}台のお買い上げです"
    puts "合計金額は#{price * count}円です"
end

buy(item:"テレビ", price:15000, count:2)

戻り値

戻り値が実行された場合その後の戻り値は実行されないので注意

def shipping_free?(price) #真偽値を返すメソッドには?を付ける
  return price >= 5000  #戻り値 5000以上
end

if shipping_free?(3000)
    puts "5000円以上のお買い上げなので送料はいただきません"
else
    puts "追加で送料をいただきます"
end

クラス インスタンス

  • クラス
    • 情報のまとめ(料理であれば、コース料理という枠組み)
  • インスタンス
    • クラスが持つ情報(コース料理の中身で前菜、メイン、デザートなど)
    • インスタンスの具体的内容(前菜やデザートの種類)
class Menu #クラスの名前
  attr_accessor :name #インスタンスの追加
  attr_accessor :price

  def initialize(name:, price:) 
    self.name = name #インスタンス変数(インスタンスを変数として扱える)
    self.price = price
  end

  def info #インスタンスの設定にはdef インスタンス名
    return "#{self.name} #{self.price}円" #戻り値
  end

get_total_price

  def get_total_price(count) #合計値を求めるメソッド
    total_price = self.price * count #total_priceにpriceを代入
    if count >= 3      #条件分岐
      total_price -= 100
    end
    return total_price
  end
end

menu1 = Menu.new #menu1に代入
 menu1.name = "ピザ" #menu1の名前に代入
 puts menu1.name
 menu1.price = 800
 puts menu1.price

menu1 = Menu.new(name: "ピザ", price: 800) #まとめた書き方
menu2 = Menu.new(name: "すし", price: 1000)
menu3 = Menu.new(name: "コーラ", price: 300)
menu4 = Menu.new(name: "お茶", price: 200)
menus = [menu1, menu2, menu3, menu4]

index = 0 #インデックスに番号を割り振る
menus.each do |menu| #menuに対して繰り返し同じ動作をさせる
  puts "#{index}. #{menu.info}"
  index += 1 #最初の数字を0ではなく1にするために足す
end

gets.chomp gets.chomp.to_i

入力された文字数値を受け取る

order = gets.chomp.to_i #数字の入力を受け取る
selected_menu = menus[order]

puts "選択されたメニュー: #{selected_menu.name}"
puts "個数を入力してください(3つ以上で100円割引)"

count = gets.chomp.to_i
puts "お会計は#{selected_menu.get_total_price(count)}円です"

intialize

クラスの初期化が主な役目(?)
理解していません。

def initialize(name:,price:)
    self.name = name
    self.price = price
  end

ファイルの分割

ファイルごとで処理を分けておくのに有効

require "./menu" #menu.rbのファイルを参照する

クラスの継承

インスタンス変数とインスタンスメソッドを引き継ぐ

class Food < Menu #MenuからFoodへ継承
end

日付

インスタンス変数とインスタンスメソッドを引き継ぐ

require "date" #日付データの読み込み
birthday = Date.new(2020,3,20) #区切り方に注意
puts birthday #日付の表示
puts birthday.sunday? #trueかfalse

today = Date.today #.todayがクラスメソッド

所感

今日感じたのはJavascriptとやれることが似ている?のかなと。
同じ概念も出てきますし、なんとなくまっさらから理解するよりは、
良かったかと思います。
これをどう使っていくのか、う~ん・・・イメージできず。

とりあえず明日からはRailsをやっていきます。
ボリュームが結構ありそうなので覚悟してやっていきます。

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

超最低限のRailsアプリをテストする(モデル編)

この記事の基本的な方針

テストは重要です。
なぜなら、人間は不完全であるからです。
ではもし自分が完全であったら、テストは不要でしょうか?
いいえ。なぜなら他人にコードの妥当性を証明しなくてはならないからです。

テストの目的以下です。
 ①コードの妥当性を自分で確認すること
 ②コードの妥当性を他人に示すこと

ここでは別記事、超最低限のRailsアプリを丁寧に作る(もう一度きちんと復習して初心者を卒業しよう)で作ったアプリをテストします。

手を動かしながら読みたいようでしたら、以下でこの3画面アプリを手に入れてください。

Terminal
$ git clone https://github.com/annaPanda8170/minimum_rails_application.git
$ bundle install
$ bundle exec rake db:create
$ bundle exec rake db:migrate

基本解説はしません。手順のみ示します。

想定する読み手

既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。

具体的な手順

完成品GitHub

①登録条件確認

deviseでデフォルトで設定されたEmailとPasswordの制約を確認します。

見るべきは、(1)devise公式GitHub内のバリデーションに関するファイルと(2)config/initializers/devise.rbです。

(1)devise公式GitHub内のバリデーションに関するファイルには、「Passwordは空ではならない」、「Emailはユニークで空ではならない」とあります。

(2)config/initializers/devise.rbには

config/initializers/devise.rb
#省略
config.password_length = 6..128
#省略
config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
#省略

とあります。

よって今回は、

Passwordは、
6文字から128文字である。
Emailは、
ユニークで かつ @の前後に@と空白以外が1文字以上ずつ」である。

を確認すべく、テストします。

②準備

Gemfile
#省略
group :development, :test do
#省略
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end

group :development do
#省略
  gem 'spring-commands-rspec'
end
#省略

※spring-commands-rspecは起動時間を速くするためのものでなくても問題はありません。

Terminal
$ bundle install
$ rails g rspec:install
$ rails g rspec:model user
$ rails g factory_bot:model user
$ bundle exec spring binstub rspec

control + c
$ rails s
.rspec
--format documentation

※これでRspecの出力が読みやすくなるそうです。

ここで空っぽのまま一度起動してみます。

Terminal
$ bundle exec rspec
Output
#省略

Finished in 0.00255 seconds (files took 1.69 seconds to load)
1 example, 0 failures, 1 pending

※1個のテストに対して0個の失敗があり、1個保留ですという意味です。

③テスト構築

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  it "Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる"
  it "Passwordが5文字で登録できない"
  #パスワード文字数上限の方は省きます
  it "passwordとpassword_confirmationが異なっていると登録できない"
  it "Emailが@がないと登録できない"
  it "Emailが@が二つあると登録できない"
  it "Emailが途中に空白があると登録できない" 
  it "2人のユーザーについて、Emailがユニークであれば登録できる" 
  it "2人のユーザーについて、Emailがユニークでなければ登録できない" 
end
Terminal
$ bundle exec rspec
Output
2020-03-20 00:51:43 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.

User
  Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる (PENDING: Not yet implemented)
  Passwordが5文字で登録できない (PENDING: Not yet implemented)
  passwordとpassword_confirmationが異なっていると登録できない (PENDING: Not yet implemented)
  Emailが@がないと登録できない (PENDING: Not yet implemented)
  Emailが@が二つあると登録できない (PENDING: Not yet implemented)
  Emailが途中に空白があると登録できない (PENDING: Not yet implemented)
  2人のユーザーについて、Emailがユニークであれば登録できる (PENDING: Not yet implemented)
  2人のユーザーについて、Emailがユニークでなければ登録できない (PENDING: Not yet implemented)

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) User Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる
     # Not yet implemented
     # ./spec/models/user_spec.rb:4

  2) User Passwordが5文字で登録できない
     # Not yet implemented
     # ./spec/models/user_spec.rb:5

  3) User passwordとpassword_confirmationが異なっていると登録できない
     # Not yet implemented
     # ./spec/models/user_spec.rb:7

  4) User Emailが@がないと登録できない
     # Not yet implemented
     # ./spec/models/user_spec.rb:8

  5) User Emailが@が二つあると登録できない
     # Not yet implemented
     # ./spec/models/user_spec.rb:9

  6) User Emailが途中に空白があると登録できない
     # Not yet implemented
     # ./spec/models/user_spec.rb:10

  7) User 2人のユーザーについて、Emailがユニークであれば登録できる
     # Not yet implemented
     # ./spec/models/user_spec.rb:11

  8) User 2人のユーザーについて、Emailがユニークでなければ登録できない
     # Not yet implemented
     # ./spec/models/user_spec.rb:12


Finished in 0.00215 seconds (files took 2.4 seconds to load)
8 examples, 0 failures, 8 pending
spec/factories/users.rb
FactoryBot.define do
  factory :user do
    email "a@a"
    password "111111"
    password_confirmation "111111"
  end
end
spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  it "Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる" do
    expect(FactoryBot.build(:user)).to be_valid
  end
  it "Passwordが5文字で登録できない"
  #パスワード文字数上限の方は省きます
  it "passwordとpassword_confirmationが異なっていると登録できない"
  it "Emailが@がないと登録できない"
  it "Emailが@が二つあると登録できない"
  it "Emailが途中に空白があると登録できない" 
  it "2人のユーザーについて、Emailがユニークであれば登録できる" 
  it "2人のユーザーについて、Emailがユニークでなければ登録できない" 
end
Terminal
$ bundle exec rspec
Output
#省略

8 examples, 0 failures, 7 pending

エラー文は以下のように確認します。

Terminal
irb(main):001:0> user = User.new(email: "a@a", password: "11111", password_confirmation: "11111")
   (0.9ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<User id: nil, email: "a@a", created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
  User Exists (0.4ms)  SELECT  1 AS one FROM `users` WHERE `users`.`email` = BINARY 'a@a' LIMIT 1
=> false
irb(main):003:0> user.errors
=> #<ActiveModel::Errors:0x00007fd9f12cc6f8 @base=#<User id: nil, email: "a@a", created_at: nil, updated_at: nil>, @messages={:password=>["is too short (minimum is 6 characters)"]}, @details={:password=>[{:error=>:too_short, :count=>6}]}>

④テスト本番

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  it "Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる" do
    expect(FactoryBot.build(:user)).to be_valid
  end
  it "Passwordが5文字で登録できない" do
    user = FactoryBot.build(:user, password: "11111", password_confirmation: "11111")
    user.valid?
    expect(user.errors[:password]).to include("is too short (minimum is 6 characters)")
  end
  #パスワード文字数上限の方は省きます
  it "passwordとpassword_confirmationが異なっていると登録できない" do
    user = FactoryBot.build(:user, password: "111111", password_confirmation: "211111")
    user.valid?
    expect(user.errors[:password_confirmation]).to include("doesn't match Password")
  end
  it "Emailが@がないと登録できない" do
    user = FactoryBot.build(:user, email: "aaa")
    user.valid?
    expect(user.errors[:email]).to include("is invalid")
  end
  it "Emailが@が二つあると登録できない" do
    user = FactoryBot.build(:user, email: "a@@a")
    user.valid?
    expect(user.errors[:email]).to include("is invalid")
  end
  it "Emailが途中に空白があると登録できない" do
    user = FactoryBot.build(:user, email: "a @a")
    user.valid?
    expect(user.errors[:email]).to include("is invalid")
  end
  it "2人のユーザーについて、Emailがユニークであれば登録できる" do
    FactoryBot.create(:user)
    expect(FactoryBot.build(:user, email: "b@b")).to be_valid
  end
  it "2人のユーザーについて、Emailがユニークでなければ登録できない" do
    FactoryBot.create(:user)
    user = FactoryBot.build(:user)
    user.valid?
    expect(user.errors[:email]).to include("has already been taken")
  end
end
Terminal
$ bundle exec rspec
Output
2020-03-20 02:01:54 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.

User
  Passwordが6文字で、Emailが@が一つだけあり@の前後に@と空白以外が1文字ずつあれば登録できる
  Passwordが5文字で登録できない
  passwordとpassword_confirmationが異なっていると登録できない
  Emailが@がないと登録できない
  Emailが@が二つあると登録できない
  Emailが途中に空白があると登録できない
  2人のユーザーについて、Emailがユニークであれば登録できる
  2人のユーザーについて、Emailがユニークでなければ登録できない

Finished in 0.06768 seconds (files took 2.31 seconds to load)
8 examples, 0 failures

まとめ

もし、パスワード全パターンを試すようなことができれば完璧なテストですが、それはできません。
例えば6桁に限定したとしても、数字10種とアルファベット26文字の大文字と小文字で計算すると、
(10 + 26 + 26)^6 = 56,800,235,584
つまり500億パターン以上で、これを128桁まで考えると気が遠くなるパターンがあることがわかります。
そもそも全パターン試せるようなパスワードがあればパスワードとしての価値がありません。
物理的に全パターンを試すことが出来ない我々は、限界値の両端をうまくテストしなくてはなりません。

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

【ruby】繰り返しについて

rubyの繰り返し文についてまとめておきます。

繰り返しを行うには?

以下のような構文又はメソッドを利用して繰り返し文を書くことが可能です。
今回はそれぞれの使い方をまとめて見ます。

・timesメソッド
・for文
・eachメソッド

timesメソッド

一定の回数だけ同じ処理をさせたい場合はtimesメソッドを利用します。

qiita.rb
5.times do
    p "りんご"
end

実行結果.rb
"りんご"
"りんご"
"りんご"
"りんご"
"りんご"

ブロック内で繰り返しの数値を使いたいときはdoの後に|変数|を加えます。

qiita.rb
5.times do |i|
    p "りんご#{i+1}個を食す"
end
実行結果.rb
"りんご1個を食す"
"りんご2個を食す"
"りんご3個を食す"
"りんご4個を食す"
"りんご5個を食す"

for文

timesメソッドとは異なりfor文はメソッドとは違います。
1から10まで加算する繰り返し文は以下のようにできます。

qiita.rb
sum =0

for i in 1..10 do
    p sum += i
end
実行結果.rb
1
3
6
10
15
21
28
36
45
55

1..10の部分をオブジェクトに変更することも可能です。

qiita.rb
fruits =["りんご","なし","さくらんぼ","みかん"]

for i in fruits do
    p i
end

実行結果.rb
"りんご"
"なし"
"さくらんぼ"
"みかん"

fruits配列の要素分処理を繰り返しています。
for文と同じことをeachメソッドでも記載することが可能です。

each文

each文は以下のように記述します。

each.rb
オブジェクト.each do |変数|
 繰り返したい処理
end

for文と同じ処理をeachメソッドで記述すると・・・・

qiita.rb
fruits =["りんご","なし","さくらんぼ","みかん"]

fruits.each do |i|
    p i
end

実行結果.rb
"りんご"
"なし"
"さくらんぼ"
"みかん"

同じ結果が得られました!

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

【Ruby】繰り返しについて

rubyの繰り返し文についてまとめておきます。

繰り返しを行うには?

以下のような構文又はメソッドを利用して繰り返し文を書くことが可能です。
今回はそれぞれの使い方をまとめて見ます。

・timesメソッド
・for文
・eachメソッド

timesメソッド

一定の回数だけ同じ処理をさせたい場合はtimesメソッドを利用します。

qiita.rb
5.times do
    p "りんご"
end

実行結果.rb
"りんご"
"りんご"
"りんご"
"りんご"
"りんご"

ブロック内で繰り返しの数値を使いたいときはdoの後に|変数|を加えます。

qiita.rb
5.times do |i|
    p "りんご#{i+1}個を食す"
end
実行結果.rb
"りんご1個を食す"
"りんご2個を食す"
"りんご3個を食す"
"りんご4個を食す"
"りんご5個を食す"

for文

timesメソッドとは異なりfor文はメソッドではないので記述方法が異なります。
1から10まで加算する繰り返し文は以下のようにできます。

qiita.rb
sum =0

for i in 1..10 do
    p sum += i
end
実行結果.rb
1
3
6
10
15
21
28
36
45
55

1..10の部分をオブジェクトに変更することも可能です。

qiita.rb
fruits =["りんご","なし","さくらんぼ","みかん"]

for i in fruits do
    p i
end

実行結果.rb
"りんご"
"なし"
"さくらんぼ"
"みかん"

fruits配列の要素分処理を繰り返しています。
for文と同じことをeachメソッドでも記載することが可能です。

each文

each文は以下のように記述します。

each.rb
オブジェクト.each do |変数|
 繰り返したい処理
end

for文と同じ処理をeachメソッドで記述すると・・・・

qiita.rb
fruits =["りんご","なし","さくらんぼ","みかん"]

fruits.each do |i|
    p i
end

実行結果.rb
"りんご"
"なし"
"さくらんぼ"
"みかん"

同じ結果が得られました!

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

【Rails】スレッドのレス投稿機能

プログラミング初心者です。
Ruby on Railsで掲示板のスレッドにレスを紐付けて投稿していく機能を作成しました。
結構苦戦したので、備忘のため貼り付けます。

【スレッド(親、tree)controller】

  def show
    @tree = Tree.find(params[:id])
    @response = Response.new(:tree_id => params[:id]) #ここを投稿用に使う
    @responses = @tree.responses.all
  end

【レス(子、response)controller】

  def create
    @response = current_user.responses.new(response_params)

    if @response.save
      redirect_to tree_url(@response.tree_id), notice: "投稿「#{@response.text}」を登録しました"
    else
      render tree_url(@response.tree_id)
    end
  end

  private

  def response_params
    params.require(:response).permit(:text, :user_id, :tree_id)
  end

【スレッド(親、tree)viewのshow】

= form_with model: @response, url_for: { controller: :responses, action: :create }, local: true do |f|
  .form-group
    = f.label :text, "コメント"
    = f.text_field :text, class: "form-control", id: "response_text"
  = f.hidden_field :tree_id
  = f.submit "投稿", class: "btn btn-primary"

【routes.rb】

  post "/responses", to: "responses#create"
  resources :trees

感想

特にルーティングエラーが多く発生しました。肝となるコードは以下の2点です。

# 入力フォーム
url_for: { controller: :responses, action: :create }
# ルーティング
post "/responses", to: "responses#create"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[学習ログ]CVE-2020-5267について簡単に調べてみた[Rails]

tl;dr

  • CVE-2020-5267とは、バージョン6.0.2.2および5.2.4.2以前のActionViewに存在する脆弱性である。
    該当バージョンのActionViewにおけるjおよびescape_javascriptメソッドにXSSの脆弱性が存在する可能性がある。

  • railsの場合はGemfile中のrailsをバージョンアップすることで対策が可能(6.0.2.2 or 5.2.4.2)


昨日、Githubから依存関係の脆弱性を指摘する以下のようなアラートが通知された。

We found potential security vulnerabilities in your dependencies.
Only the owner of this repository can see this message.
Manage your notification settings or learn more about vulnerability alerts.

セキュリティアラートを確認すると、actionviewに関してアラートが出ていることが分かった。

Remediation
Upgrade actionview to version 5.2.4.2 or later. For example:
gem "actionview", ">= 5.2.4.2"
Always verify the validity and compatibility of suggestions with your codebase.

CVE-2020-5267とは?

CVE-2020-5267についてググると、どうやらXSSに関する脆弱性らしい(私の認識が間違っていたら申し訳ないです)。
以下、コピペ

CVE-2020-5267 Detail
Description
In ActionView before versions 6.0.2.2 and 5.2.4.2, there is a possible XSS vulnerability in ActionView's JavaScript literal escape helpers. Views that use the j or escape_javascript methods may be susceptible to XSS attacks. The issue is fixed in versions 6.0.2.2 and 5.2.4.2.

https://nvd.nist.gov/vuln/detail/CVE-2020-5267

There is a possible XSS vulnerability in ActionView's JavaScript literal
escape helpers. Views that use the j or escape_javascript methods
may be susceptible to XSS attacks.
Versions Affected: All.
Not affected: None.
Fixed Versions: 6.0.2.2, 5.2.4.2

https://www.openwall.com/lists/oss-security/2020/03/19/1

 補足:XSS(クロスサイトスクリプティング)とは?

クロスサイトスクリプティングとは、攻撃者の作成したスクリプトを脆弱性のある標的サイトのドメインの権限において閲覧者のブラウザで実行させる攻撃一般を指す。

https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0

CVE-2020-5267による影響

この脆弱性を利用した際のコードの一例も載せておきます

<script>let a = `<%= j unknown_input %>`</script>
<script>let a = `<%= escape_javascript unknown_input %>`</script>

CVE-2020-5267の対処方法(Railsの場合)

対処方法については二種類ある。

1.Railsのバージョンを更新する

冒頭で述べた通り、Railsのバージョンを6.0.2.4か5.2.4.2に更新することで、Railsに依存しているactionviewのバージョンを更新する。

一例
Gemfile

- gem 'rails' ~>"5.1.4.2"
+ gem 'rails' ~>"5.2.4.2"

https://weblog.rubyonrails.org/2020/3/19/Rails-6-0-2-2-and-5-2-4-2-has-been-released/

2.モンキーパッチをあてる

Railsのバージョンを更新できない場合は、以下のようなモンキーパッチをあてて対処する。

ActionView::Helpers::JavaScriptHelper::JS_ESCAPE_MAP.merge!(
  {
    "`" => "\\`",
    "$" => "\\$"
  }
)
module ActionView::Helpers::JavaScriptHelper
  alias :old_ej :escape_javascript
  alias :old_j :j
  def escape_javascript(javascript)
    javascript = javascript.to_s
    if javascript.empty?
      result = ""
    else
      result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"']|[`]|[$])/u, JS_ESCAPE_MAP)
    end
    javascript.html_safe? ? result.html_safe : result
  end
  alias :j :escape_javascript
end

https://github.com/rails/rails/security/advisories/GHSA-65cv-r6x7-79hv

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

Sinatraでタスク管理アプリ作ってみた

概要

環境構築〜MVCの動きが一通り表現できるまでの道のりを解説します。
ruby on Railsを用いて、Webアプリを作成した経験があったので、MVCの動きをどのように表現できるのかを確認する練習として作成しました。
ruby on Railsでアプリケーションを作成したことがある人向けへの説明になります。

環境構築編

ruby,rails,mysqlがインストールされている前提での説明になります。
mysqlでのテーブル作成に関しての説明は他の記事に譲ります。
今回はDB名:sinatra_practice、テーブル名:commentとしています。

sinatraのインストール

Sinatraとは、軽量なWebアプリケーションフレームワークです。
gemとして用意されているので、下記のコマンドでインストールします。

sinatra.command
gem install sinatra

sinatra_contribのインストール

sinatra_contribとは、コードを変更した際、Webサーバーを再起動せずに変更点を反映してくれるgemです。
このgemは必須ではないですが、開発がとても効率的になるので、インストールすることをおすすめします。

gem install sinatra-contrib

実装編

まずフォルダ構造、完成コードを掲載します。

フォルダ構造.
.
├── myapp.rb 
├── views
│   ├── index.erb

myapp.rb
require 'sinatra'
require 'sinatra/reloader'
require 'active_record'

ActiveRecord::Base.establish_connection(
  adapter:  "mysql2",
  host:     "localhost",
  username: "root",
  password: "",
  database: "sinatra_practice",
  socket: "/tmp/mysql.sock"
)

class Comment < ActiveRecord::Base
end

get "/" do 
  @comments = Comment.all
  erb :index
end

post "/new" do 
  Comment.create({body: params[:body]})
  redirect '/'
end

post "/delete" do
  puts params
  Comment.find(params[:id]).destroy
  redirect '/'
end
index.erb
<!DOCTYPE html>
<html lang="ja">
<head>
    <mata charset="utf-8">
    <title>ToDo</title>
</head>
<body>
    <h1>ToDoリスト</h1>
    <ul>
        <% @comments.each do |comment| %>
        <li data-id= "<%= comment.id %>">
            <%= comment.body %>
            <form method="post" action="/delete">
              <input type="hidden"  name="id" value="<%= comment.id %>">
              <input type="submit" value="done!">
            </form>
        </li>
        <% end %>
    </ul>
    <form method="post" action="/new">
        <input type= "text" name="body">
        <input type= "submit" value="追加">
    </form>
</body>
</html>

myapp.rbに関して

ruby on Railsを触ったことがある人向けへの解説ですが、普段あまり意識しないことも記載しておきます。

  • require
    • ライブラリの読み込み
      • sinatra/reloaderは環境構築でインストールしたinatra-contribの変更を自動反映してくれる
      • active_recordはDBのデータをRubyオブジェクトとして扱えるようにするもの
        • railsのインストールが必要
  • ActiveRecord::Base.establish_connection
    • DBへの接続情報を指定

まとめ

SinatraはMVCを意識せずミニアプリを作成するにはもってこいだと感じました。
コードが動かない、記載について詳しく解説してほしい等あったらご連絡ください

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

Railsのtext_fieldにCSSをあてる方法

text_fieldにCSSってどうやってあてるんだっけ?

Railsアプリケーション作成時にフロント部分を作成していた時に

example.haml.html
.item__name
  商品名
  .name--input
    = f.text_field :name, placeholder: "40文字まで"

のようなhamlを作成していざscssを記述しようとしたときに
このtext_filedにどうやってCSSをあてるのかが
ふと考えると分からなかったんです。

同じようなnumber_fieldsubmitなどにも使えるやり方なので
是非覚えておいたほうがよいです。

そもそもtext_fieldで作られるHTMLは何なのか?

上の= f.text_field :name, placeholder: "40文字まで"で作られるHTMLを
chromeの検証で確認してみると
<input placeholder="40文字まで" type="text" name="item[name]" id=item_name>
というものが作成されているのがわかるかと思います。

text_fieldとかはRailsがviewを簡潔に記述するために用意してくれているヘルパーメソッドのため
簡単な記述で実際はこういうHTML文の作成もしてくれています。
この作成されたHTMLにあてるようにCSSを記述すればOKです。

今回はSCSSを使い記述しました。

CSSをあててみよう

example.scss
.item__name{
  input[type="text"]{
    width: 100%;
    height: 20px;
    font-size: 14px;
  }
  ::placeholder{
    padding: 5px 5px;
  }
}

と指定して記述すると、text_fieldにCSSをあてることができます。
placholderはCSSの擬似要素のため上記のような記述をする必要があります。

ヘルバーメソッドを使って記述した場合にどのようなHTML文が作成されているのかを確認すると解決できる部分でしたね。

参考先

rails フォームの大きさをcssで変更する

CSS: カスケーディングスタイルシート::placeholder

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

Railsチュートリアルメモ - 第12章

(メモの目次記事はこちら)[https://qiita.com/yokohama4580/items/dedfd5510080273dc2a0]

(公式Railsチュートリアル第12章へのリンク)[https://railstutorial.jp/chapters/password_reset?version=5.1#cha-password_reset]

サマリ

  • パスワードの再設定
    • パスワード再設定メールの送信
    • トークンによる認証とパスワードの再設定

ポイント

  • パスワードリセット機能はUserモデルを拡張して使用するため、MVCのうち新たに作成するのはViewとControllerのみで良い
  • 処理のコントローラーのアクションがいろいろ登場するので、流れを整理しないと混乱する
sessions/new.html.erb
→password_resets_controller#new
→password_resets/new.html.erb
→password_resets_controller#create
→メール送信
→password_resets_controller#edit
→password_resets/edit.html.erb
→password_resets#update
  • @user.authenticated?(:reset, params[:id])でresetトークンとDBに保存されたダイジェストを照合する
  • パスワード再設定用メールを送信する際に、パスワードリセットトークンとダイジェストを生成する。
    • トークンはメール内のリンクに埋め込んでユーザーに送付する
    • ダイジェストはDBに保存し、リンクが開かれた際に両者を照合して本人であることを確認する
  • #edit時点では、メール内のリンクに埋め込んだURLパラメーターからメールアドレスを取得できるが、#updateは画面から呼び出すので、同じ方法でメールアドレスを取得することができない。そのため、edit.html.erbの中の隠しフィールドにメールアドレスを持たせて#updateに引き渡す。
    • <%= hidden_field_tag :email, @user.email %>

感想

  • 11章と内容的にはほぼ同じだったが、その分説明が少なくて少し混乱した
  • herokuでの動作は問題なかったが、ローカル動作時にパスワード再設定用リンクを開こうとすると画面にこのサイトは安全に接続できませんlocalhostから無効な応答が送信されました。と表示され、ログに以下が吐かれてしまった
2020-03-18 02:53:45 +0900: HTTP parse error, malformed request (): #<Puma::HttpParserError: Invalid HTTP format, parsing fails.>
  • 原因はメールリンクがhttpsになっているためで、httpでリンクを開くと画面が正常に表示された
    • localhostにhttpsで繋ぐ場合は少し追加の設定が必要 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでページ毎にsubmitの表記を変えたい

submitボタンの表記を変えたい

submitボタンを実装した場合、デフォルトの日本語表記は下記のような形で表示されます
(色付けなどはCSSで加工済み)

スクリーンショット 2020-03-20 14.09.19.png

'登録する' というのがデフォルトのsubmitボタンとなります。

例えば、作成しているアプリで商品出品するページでは当然、'出品する'という表記にしたいですよね。
しかし、商品を編集して更新する場合は、'出品する'だと違和感があるので
'更新する'と表記をしたいところです。

しかし、出品も更新も同じフォームを流用しているので
分岐分けで記入する必要があるところになります。

単純にsunmitボタンのテキストを変えたい場合なら
<input type="submit" value="出品する">
を使えば表記は変わるわけなのですが、上記のように分岐わけさせたいときにどうするべきか。
出品と更新のページを別々に作って、各々でテキスト表記を変えるという手段もできますが
メンテナンス上、ほぼ同じ記述のコードのファイルが増えてしまうのは避けたいところです。

ja.ymlを利用して分岐させる

ja.ymlといえば、英語表記を日本語化させる指定を記述する場所なのですが
こちらでアクション毎にsubmitの表記を指定させることができます。

まずはi18nの導入が必要なので参考先にあるページを参照に導入を済ませておいてください
[初学者]Railsのi18nによる日本語化対応

config/locales/ja.ymlに記述する

ja.yml
ja:
  helpers:
    submit:
      create: "出品する"
      update: "更新する"

このように記述をするとcreate下のsubmitは出品すると表示され
update下のsubmitは更新すると表示されるようになります。

実際にcreate時はこのように変更がかかります。
スクリーンショット 2020-03-20 14.09.41.png
update画面も同様に記述した通りの変更がかかります。

これでhtmlをcreateとupdateで別々に表記を分ける必要がなくなるので
ファイルを増やすことなく分岐して表示させることができます。

参考先

[初学者]Railsのi18nによる日本語化対応

http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-submit

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

[scss]hamlへの反映方法は2種類あった 備忘録

今までscssのhamlへの反映方法といったら
application.scss@import記述のみと思っていましたが。

実はそんなことをしなくてもコマンド入力で作成したscssには
自動的に反映される記述が入っているんです。
知りませんでした。。。
コメントアウトされ得た以下の3行があるとOKらしい

scss
// Place all the styles related to the reset controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

ですので、scssの反映方法を以下2種類をまとめたいと思います

コマンドで作成する方法

importいらないコマンド存在したー!!!

ターミナル
$ rails g assets 作成したいファイル名

以上です。
あとはscssに記述したものは自動的にhamlに反映されます。

手動でファイルを作成する方法

右クリックでファイル作成をした場合scssにimport記述は自動生成されませんので
application.scssにimportしてhamlにscssが当たるようにしましょう。

やり方
・hamlに反映させたいscssのファイル名をアンダーバー「 _ 」始まりにします。
application.scssにimportします。
・終わり

記述参考は下記

application.scss
@import "home";
@import "いれたいscssファイル名(アンダーバーは入れない)"; ⬅︎終わりは必ずセミコロンを入れる

#assetsの中でもフォルダの異なるものはURLをちゃんと記載しないと反映されない
@import "config/reset";
@import "フォルダ名/ファイル名";

参照記事

Railsドキュメント

終わりに

自分のいた学校は極力コマンドを使用しない学校でしたが
コマンドはコマンドで別の仕様があることを今回知りました。
アウトプットもそうですがインプットも必要。
人との情報交換は本当に大事だなと改めて感じました。

初学者な為、記事に不備やアドバイ等ございましたらご連絡頂けますと幸いです。
最後まで読んできただきありがとうございます。

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

【ruby】シーザー暗号を読み解く

シーザー暗号と呼ばれる、アルファベットをある文字数分ずらすという暗号方式をrubyで解読してみます。

3文字数分ずらし(戻し)暗号を解読してみます。

def to_decode(char)

  char_ary = char.split("") # => ["k","h","o","o","r"]
  changed_char_ary = [] # 空の配列を用意

  char_ary.each do |char|
    changed_char_ary << (char.ord - 3).chr 
   # 3を引いた文字コードを文字列に変換する =>["h","e","l","l","o"]
  end
  puts changed_char_ary.join # => "hello"
end

char = "khoor"
to_decode(char)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】form_withについて簡単にまとめた

はじめに

form_withについて使いかたを簡単にまとめてみました。
今回はRubyonRailsAPIを色々と参照しました。

form_withの機能

フォームを構成するヘルパーメソッド。form_tagform_forと同じような挙動をするが、
現在はform_withを使用することが推奨されている。

基本的な構文は下記のとおり。

= form_with model: @user do |f|
  = f.text_field :name
  = f.submit '登録'

テキストフィールドに名前を入力して「登録」をおすとデータが送信される。
このとき、データはハッシュの階層構造であるparams[:user][:name]という形で送信される。
よってコントローラーでストロングパラメーターを用いる際は下記のようになる。

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

データの送信先については、:urlオプションを追加することによっても指定できるが、
渡されたモデルの状態(①新規②既存)によって自動推定をしてくれる。

①新規作成されたモデルが渡された場合
= form_with model: User.new do |f|
  = f.text_field :name
①で生成されるHTML
<form action="/users" method="post" data-remote="true">
  <input type="text" name="user[name]">
</form>
②既存のモデルが渡された場合
= form_with model: User.first do |f|
  = f.text_field :name
②で生成されるHTML
<form action="/users/1" method="post" data-remote="true">
  <input type="hidden" name="_method" value="patch">
  <input type="text" name="user[name]" value="<the name of the user>">
</form>

①のパターンでは、action="/users"であるのに対して、
②のパターンではaction="/users/1"となっている上に隠しinputフィールドを利用してメソッドをPatchに指定している。
これにより、上記①②のパターンでそれぞれ対応するアクションがcreateになったりupdateになったりする。

ビュー上に表示しないものを送信したいとき

hidden_fieldを使用することに対応可能。

= form_with model: @user do |f|
  = f.hidden_field :age, :value => @user.age #ビューに表示されない
  = f.text_field :name
  = f.submit

上記のように記載をすることで、ビュー上には表示されないがparams[:user][:age]の中に
@user.ageの値を格納した状態でデータを送ることができる。

ビュー上に表示させたいが編集はさせたくないとき

readonly: trueを指定すればOK。

= form_with model: @user do |f|
  = f.text_field :name
  = f.text_field :age, value: @user.age, readonly: true #ビューに表示されるが編集不可
  = f.submit

まとめ

form_withの仕組みが腹落ちしました。
特にモデルの状態の違いから生成されるhtmlが変わり、結果として対応するアクションが自動的に決まる仕組みのあたりは今まで曖昧な理解だったので今回がいい機会になりました。

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

SequelでPG::ConnectionBad: PQconsumeInput()エラーが出るようになって困った話

急に出始めた PG::ConnectionBad: PQconsumeInput() エラー

RailsでActiveRecordを使わずSequelをORMとして使っているのだが、いつからかRails起動直後のRDS(Postgresql)接続時に「PG::ConnectionBad: PQconsumeInput()」エラーが出るようになってしまった。

PG::ConnectionBad: PQconsumeInput() SSL error: decryption failed or bad record mac
PG::ConnectionBad: PQconsumeInput() server closed the connection unexpectedly
PG::ConnectionBad: PQconsumeInput() SSL error: sslv3 alert bad record mac

Sequel側は変えていないのでRDSで何か仕様変更があったのか・・・。RDSのCA証明書の変更の影響かと思ったが、変更する前から当エラーは出てた。

状況

  • Rails起動直後にいくつかのリクエストでこのエラーになる。全てではない。
  • ある程度エラーが出るとその後は出ない。
  • ステージング環境などで8時間くらいアクセスがないとまたでるようになる。
  • なので、本番環境ではRails再起動直後だけこのエラーがでる。
  • 開発環境のRDSではないPostgresqlだと出ない。

このエラーが出たらretryする仕組みも入れたけど、やはりそもそも出ないようにしたい。

connection_validatorを試す

真偽はわからないがpumaのようなマルチスレッドのアプリケーションサーバを使っているとこのエラーが出るらしい。Sequelのコネクションプーリングがスレッドセーフではない?

で、色々ググったところ日本語の情報はさっぱりの中、海外のサイトで以下の設定を入れれば解決するらしい情報を入手。

DB.extension(:connection_validator)
DB.pool.connection_validation_timeout = -1

https://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/connection_validator_rb.html

コネクションプーリング内のコネクションが有効かどうかチェックして、有効でなければ再接続する設定。
DB.pool.connection_validation_timeout はチェックする間隔(秒)で、デフォルト3600秒、−1のときは常にチェック。

connection_validation_timeoutの値を変えて検証

  • -1: エラーが出なくなった!
  • 1以上: 起動直後はエラーでる

検証結果

DB.pool.connection_validation_timeout = -1 なら効果あり。
3600で効果出て欲しかったなあ。

パフォーマンス試験

公式にもあるように、常にチェックだと気になるのがパフォーマンスの劣化。
connection_validatorありなしでApacheBenchでパフォーマンステストしてみた。

connection_validatorなし

$ ab -c 100 -n 10000 "http://xxxxxxxxxx"

Concurrency Level:      100
Time taken for tests:   22.620 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      15650000 bytes
HTML transferred:       11390000 bytes
Requests per second:    442.09 [#/sec] (mean)
Time per request:       226.196 [ms] (mean)
Time per request:       2.262 [ms] (mean, across all concurrent requests)
Transfer rate:          675.66 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.4      0       5
Processing:     5  226 162.3    178    1102
Waiting:        5  226 162.3    178    1102
Total:          5  226 162.3    178    1102

connection_validatorあり (connection_validation_timeout = -1)

$ ab -c 100 -n 10000 "http://xxxxxxxxxx"

Concurrency Level:      100
Time taken for tests:   26.522 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      15650000 bytes
HTML transferred:       11390000 bytes
Requests per second:    377.05 [#/sec] (mean)
Time per request:       265.216 [ms] (mean)
Time per request:       2.652 [ms] (mean, across all concurrent requests)
Transfer rate:          576.26 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       2
Processing:    10  264 157.8    231    1126
Waiting:       10  264 157.8    231    1126
Total:         10  264 157.9    231    1126

(結果は3回試して一番良かった結果)

テスト結果

1リクエストあたり約40ms遅くなった。

結論

超絶負荷のかかる様なサーバではないので、40msのパフォーマンス劣化は許容範囲としてconnection_validatorを採用することにした。
auto_reconnectオプションとかあれば良いのに。

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

Rails deviseの導入〜新規登録、ログイン時のカラムの追加まで

初めに

某プログラミングスクールの卒業生です。
スクールに通う中で学んだことや、つまづいたことを備忘録としてまとめてます。
今回は、deviseを扱う際に必ず調べるであろう、独自カラムの追加方法をdeviseの導入からまとめておきたいと思います。

環境

・Ruby 2.5.7
・Rails 5.2.4.1

deviseの導入

deviseとは、ログインや新規登録機能等を簡単に実装できるgemです。

まず初めに、Gemfileに以下の1行追加して保存します。

Gemfile
gem 'devise'

次にターミナルで下記を実行し、deviseをアプリケーションに読み込ませます。

$ bundle install

最後にターミナルで下記を実行し、deviseの初期設定を行います。

$ rails g devise:install
Running via Spring preloader in process 29980
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

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
===============================================================================

ターミナルに上記のような表示がされれば成功です。
以上でdeviseを使う準備が整いました。

deviseでモデルを作成する

準備が整ったので、いよいよdeviseを使ってみましょう。
今回は、ユーザー登録時の名前の追加と、ログイン時は名前とパスワードでログインできるようにカスタマイズしていきます。

まず初めに、モデルを作成します。
ターミナルで下記のを実行し、モデルを作成します。

$ rails g devise User
Running via Spring preloader in process 30045
      invoke  active_record
      create    db/migrate/20200319222813_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

これで、「User」という名のモデルと、usersテーブル用のマイグレーションファイルが作成されました。

独自のカラムを追加しよう

モデルとマイグレーションファイルの作成ができたので、usersテーブルにカラムを追加しいきましょう。
今回はnameカラムを追加します。
先ほど、作成したマイグレーションファイルに以下の1行を追加します。

20200319222813_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string :name, null: false ←追加

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

      ## Rememberable
      t.datetime :remember_created_at
以下省略

追加したら、ターミナルでマイグレーションを実行しましょう。

$ rails db:migrate

実際にカラムが追加できたかschemaファイルで確認します。

schema.rb
ActiveRecord::Schema.define(version: 2020_03_19_222813) do

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "name", null: false ←追加されている
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

追加されていることが確認できれば成功です。

Viewを追加しよう

カラムの追加がおわったら、次にViewを作成しましょう。
ターミナルで下記を実行しましょう。

$ rails g devise:views
invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

これでdevise用のViewを作成する事ができました。

deviseをカスタマイズしよう

Viewが作成できたので、いよいよdeviseをカスタマイズしていきます。
今回は、ユーザー登録時の名前の追加と、ログイン時は名前とパスワードでログインできるようにカスタマイズしていきたいと思います。

新規登録画面に名前を追加  

deviseの新規登録画面はデフォルトで、以下のようになっています。
今回はここに名前(Name)を追加します。
スクリーンショット 2020-03-20 8.51.09.png

新規登録のViewにNameを追加します。
以下を参考に追加してみてください。
※registrationsが新規登録のviewなので覚えておきましょう

views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

<%# ここから %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true %>
  </div>
<%# ここまで %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

スクリーンショット 2020-03-20 9.06.58.png

上記の追記で名前が追加できた事が確認できます。
これではまだ、名前の登録がデータベースに反映されません。
なので、最後にストロングパラメーターを設定しましょう。

controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ここから
  before_action :configure_permitted_parameters, if: :devise_controller?
  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
  end
# ここまで
end

これでデータベースに名前が登録できるようになりました。
今後、カラムを増やす場合はストロングパラメーターにも忘れずに追加しましょう。

名前とパスワードでログインできるように変更

deviseのログイン画面はデフォルトで、以下のようになっています。
今回はEmailを名前(Name)に変更します。
スクリーンショット 2020-03-20 9.40.21.png

ログインのViewをEmailからNameを変更します。
以下を参考に変更してみてください。
※sessionsがログインのviewなので覚えておきましょう

views/devise/sessions/new.html.erb
<h2>Log in</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>

  <%# ここから %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true %>
  </div>
  <%# ここまで追加 %>

  <%# ここから %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>
  <%# ここまで削除 %>

  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password, autocomplete: "current-password" %>
  </div>

  <% if devise_mapping.rememberable? %>
    <div class="field">
      <%= f.check_box :remember_me %>
      <%= f.label :remember_me %>
    </div>
  <% end %>

  <div class="actions">
    <%= f.submit "Log in" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

スクリーンショット 2020-03-20 9.46.08.png

上記の追記でEmailが名前(Name)が変更できた事が確認できます。
これではまだ、名前でのログインができません。
devise.rbを書き換えて名前でのログインができるように変更します。

config/initializers/devise.rb
# ==> Configuration for any authentication mechanism
  # Configure which keys are used when authenticating a user. The default is
  # just :email. You can configure it to use [:username, :subdomain], so for
  # authenticating a user, both parameters are required. Remember that those
  # parameters are used only when authenticating and not when retrieving from
  # session. If you need permissions, you should implement that in a before filter.
  # You can also supply a hash where the value is a boolean determining whether
  # or not authentication should be aborted when the value is not present.
  # config.authentication_keys = [:email]

devise.rbの中に上記のような記述があるかと思います。
その一番下の行をを変更してください。
※#を外すのを忘れないように注意してください。

config/initializers/devise.rb
# config.authentication_keys = [:email]
↓ 変更
config.authentication_keys = [:name]

これで名前をとパスワードでログインできるようになりました。
以上で新規登録とログインのカスタマイズが終わります。

最後に

備忘録程度のまとめになっているので、もしかしたら分かりにくいかもしれませんがご了承ください。
この投稿が少しでも役に立つ事があれば幸いです。

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

Rails deviseの導入〜新規登録、ログイン時のカラム

初めに

某プログラミングスクールの卒業生です。
スクールに通う中で学んだことや、つまづいたことを備忘録としてまとめてます。
今回は、deviseを扱う際に必ず調べるであろう、独自カラムの追加方法をdeviseの導入からまとめておきたいと思います。

環境

・Ruby 2.5.7
・Rails 5.2.4.1

deviseの導入

deviseとは、ログインや新規登録機能等を簡単に実装できるgemです。

まず初めに、Gemfileに以下の1行追加して保存します。

Gemfile
gem 'devise'

次にターミナルで下記を実行し、deviseをアプリケーションに読み込ませます。

$ bundle install

最後にターミナルで下記を実行し、deviseの初期設定を行います。

$ rails g devise:install
Running via Spring preloader in process 29980
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

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
===============================================================================

ターミナルに上記のような表示がされれば成功です。
以上でdeviseを使う準備が整いました。

deviseでモデルを作成する

準備が整ったので、いよいよdeviseを使ってみましょう。
今回は、ユーザー登録時の名前の追加と、ログイン時は名前とパスワードでログインできるようにカスタマイズしていきます。

まず初めに、モデルを作成します。
ターミナルで下記のを実行し、モデルを作成します。

$ rails g devise User
Running via Spring preloader in process 30045
      invoke  active_record
      create    db/migrate/20200319222813_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

これで、「User」という名のモデルと、usersテーブル用のマイグレーションファイルが作成されました。

独自のカラムを追加しよう

モデルとマイグレーションファイルの作成ができたので、usersテーブルにカラムを追加していきましょう。
今回はnameカラムを追加します。
先ほど、作成したマイグレーションファイルに以下の1行を追加します。

20200319222813_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string :name, null: false ←追加

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

      ## Rememberable
      t.datetime :remember_created_at
以下省略

追加したら、ターミナルでマイグレーションを実行しましょう。

$ rails db:migrate

実際にカラムが追加できたかschemaファイルで確認します。

schema.rb
ActiveRecord::Schema.define(version: 2020_03_19_222813) do

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "name", null: false ←追加されている
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

追加されていることが確認できれば成功です。

Viewを追加しよう

カラムの追加がおわったら、次にViewを作成しましょう。
ターミナルで下記を実行しましょう。

$ rails g devise:views
invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

これでdevise用のViewを作成する事ができました。

deviseをカスタマイズしよう

Viewが作成できたので、いよいよdeviseをカスタマイズしていきます。
今回は、ユーザー登録時の名前の追加と、ログイン時は名前とパスワードでログインできるようにカスタマイズしていきたいと思います。

新規登録画面に名前を追加  

deviseの新規登録画面はデフォルトで、以下のようになっています。
今回はここに名前(Name)を追加します。
スクリーンショット 2020-03-20 8.51.09.png

新規登録のViewにNameを追加します。
以下を参考に追加してみてください。
※registrationsが新規登録のviewなので覚えておきましょう

views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

<%# ここから %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true %>
  </div>
<%# ここまで %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

スクリーンショット 2020-03-20 9.06.58.png

上記の追記で名前が追加できた事が確認できます。
これではまだ、名前の登録がデータベースに反映されません。
なので、最後にストロングパラメーターを設定しましょう。

controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ここから
  before_action :configure_permitted_parameters, if: :devise_controller?
  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
  end
# ここまで
end

これでデータベースに名前が登録できるようになりました。
今後、カラムを増やす場合はストロングパラメーターにも忘れずに追加しましょう。

名前とパスワードでログインできるように変更

deviseのログイン画面はデフォルトで、以下のようになっています。
今回はEmailを名前(Name)に変更します。
スクリーンショット 2020-03-20 9.40.21.png

ログインのViewをEmailからNameを変更します。
以下を参考に変更してみてください。
※sessionsがログインのviewなので覚えておきましょう

views/devise/sessions/new.html.erb
<h2>Log in</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>

  <%# ここから %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true %>
  </div>
  <%# ここまで追加 %>

  <%# ここから %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>
  <%# ここまで削除 %>

  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password, autocomplete: "current-password" %>
  </div>

  <% if devise_mapping.rememberable? %>
    <div class="field">
      <%= f.check_box :remember_me %>
      <%= f.label :remember_me %>
    </div>
  <% end %>

  <div class="actions">
    <%= f.submit "Log in" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

スクリーンショット 2020-03-20 9.46.08.png

上記の追記でEmailが名前(Name)が変更できた事が確認できます。
これではまだ、名前でのログインができません。
devise.rbを書き換えて名前でのログインができるように変更します。

config/initializers/devise.rb
# ==> Configuration for any authentication mechanism
  # Configure which keys are used when authenticating a user. The default is
  # just :email. You can configure it to use [:username, :subdomain], so for
  # authenticating a user, both parameters are required. Remember that those
  # parameters are used only when authenticating and not when retrieving from
  # session. If you need permissions, you should implement that in a before filter.
  # You can also supply a hash where the value is a boolean determining whether
  # or not authentication should be aborted when the value is not present.
  # config.authentication_keys = [:email]

devise.rbの中に上記のような記述があるかと思います。
その一番下の行をを変更してください。
※#を外すのを忘れないように注意してください。

config/initializers/devise.rb
# config.authentication_keys = [:email]
↓ 変更
config.authentication_keys = [:name]

これで名前をとパスワードでログインできるようになりました。
以上で新規登録とログインのカスタマイズが終わります。

最後に

備忘録程度のまとめになっているので、もしかしたら分かりにくいかもしれませんがご了承ください。
この投稿が少しでも役に立つ事があれば幸いです。

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

Rails deviseの導入〜新規登録、ログイン時のカラム追加まで

初めに

某プログラミングスクールの卒業生です。
スクールに通う中で学んだことや、つまづいたことを備忘録としてまとめてます。
今回は、deviseを扱う際に必ず調べるであろう、独自カラムの追加方法をdeviseの導入からまとめておきたいと思います。

環境

・Ruby 2.5.7
・Rails 5.2.4.1

deviseの導入

deviseとは、ログインや新規登録機能等を簡単に実装できるgemです。

まず初めに、Gemfileに以下の1行追加して保存します。

Gemfile
gem 'devise'

次にターミナルで下記を実行し、deviseをアプリケーションに読み込ませます。

$ bundle install

最後にターミナルで下記を実行し、deviseの初期設定を行います。

$ rails g devise:install
Running via Spring preloader in process 29980
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

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
===============================================================================

ターミナルに上記のような表示がされれば成功です。
以上でdeviseを使う準備が整いました。

deviseでモデルを作成する

準備が整ったので、いよいよdeviseを使ってみましょう。
今回は、ユーザー登録時の名前の追加と、ログイン時は名前とパスワードでログインできるようにカスタマイズしていきます。

まず初めに、モデルを作成します。
ターミナルで下記のを実行し、モデルを作成します。

$ rails g devise User
Running via Spring preloader in process 30045
      invoke  active_record
      create    db/migrate/20200319222813_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

これで、「User」という名のモデルと、usersテーブル用のマイグレーションファイルが作成されました。

独自のカラムを追加しよう

モデルとマイグレーションファイルの作成ができたので、usersテーブルにカラムを追加していきましょう。
今回はnameカラムを追加します。
先ほど、作成したマイグレーションファイルに以下の1行を追加します。

20200319222813_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string :name, null: false ←追加

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

      ## Rememberable
      t.datetime :remember_created_at
以下省略

追加したら、ターミナルでマイグレーションを実行しましょう。

$ rails db:migrate

実際にカラムが追加できたかschemaファイルで確認します。

schema.rb
ActiveRecord::Schema.define(version: 2020_03_19_222813) do

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "name", null: false ←追加されている
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

追加されていることが確認できれば成功です。

Viewを追加しよう

カラムの追加がおわったら、次にViewを作成しましょう。
ターミナルで下記を実行しましょう。

$ rails g devise:views
invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

これでdevise用のViewを作成する事ができました。

deviseをカスタマイズしよう

Viewが作成できたので、いよいよdeviseをカスタマイズしていきます。
今回は、ユーザー登録時の名前の追加と、ログイン時は名前とパスワードでログインできるようにカスタマイズしていきたいと思います。

新規登録画面に名前を追加  

deviseの新規登録画面はデフォルトで、以下のようになっています。
今回はここに名前(Name)を追加します。
スクリーンショット 2020-03-20 8.51.09.png

新規登録のViewにNameを追加します。
以下を参考に追加してみてください。
※registrationsが新規登録のviewなので覚えておきましょう

views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

<%# ここから %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true %>
  </div>
<%# ここまで %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

スクリーンショット 2020-03-20 9.06.58.png

上記の追記で名前が追加できた事が確認できます。
これではまだ、名前の登録がデータベースに反映されません。
なので、最後にストロングパラメーターを設定しましょう。

controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ここから
  before_action :configure_permitted_parameters, if: :devise_controller?
  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
  end
# ここまで
end

これでデータベースに名前が登録できるようになりました。
今後、カラムを増やす場合はストロングパラメーターにも忘れずに追加しましょう。

名前とパスワードでログインできるように変更

deviseのログイン画面はデフォルトで、以下のようになっています。
今回はEmailを名前(Name)に変更します。
スクリーンショット 2020-03-20 9.40.21.png

ログインのViewをEmailからNameを変更します。
以下を参考に変更してみてください。
※sessionsがログインのviewなので覚えておきましょう

views/devise/sessions/new.html.erb
<h2>Log in</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>

  <%# ここから %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true %>
  </div>
  <%# ここまで追加 %>

  <%# ここから %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>
  <%# ここまで削除 %>

  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password, autocomplete: "current-password" %>
  </div>

  <% if devise_mapping.rememberable? %>
    <div class="field">
      <%= f.check_box :remember_me %>
      <%= f.label :remember_me %>
    </div>
  <% end %>

  <div class="actions">
    <%= f.submit "Log in" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

スクリーンショット 2020-03-20 9.46.08.png

上記の追記でEmailが名前(Name)が変更できた事が確認できます。
これではまだ、名前でのログインができません。
devise.rbを書き換えて名前でのログインができるように変更します。

config/initializers/devise.rb
# ==> Configuration for any authentication mechanism
  # Configure which keys are used when authenticating a user. The default is
  # just :email. You can configure it to use [:username, :subdomain], so for
  # authenticating a user, both parameters are required. Remember that those
  # parameters are used only when authenticating and not when retrieving from
  # session. If you need permissions, you should implement that in a before filter.
  # You can also supply a hash where the value is a boolean determining whether
  # or not authentication should be aborted when the value is not present.
  # config.authentication_keys = [:email]

devise.rbの中に上記のような記述があるかと思います。
その一番下の行をを変更してください。
※#を外すのを忘れないように注意してください。

config/initializers/devise.rb
# config.authentication_keys = [:email]
↓ 変更
config.authentication_keys = [:name]

これで名前をとパスワードでログインできるようになりました。
以上で新規登録とログインのカスタマイズが終わります。

最後に

備忘録程度のまとめになっているので、もしかしたら分かりにくいかもしれませんがご了承ください。
この投稿が少しでも役に立つ事があれば幸いです。

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

Kinx 実現技術 - Yacc/Bison

コンパイラ・コンパイラの話

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。作ったものの紹介だけではなく実現のために使った技術を紹介していくのも誰の役に立つかもしれないしね。その道の人には当たり前でも、そうでない人にも興味をもって貰えるかもしれないので。ソースを具体的にさらすのは(アドホックにやってる部分もあるので)多少恥ずかしいが、どうせ OSS にしてるし、まぁいいか。

最初のテーマは構文解析。なお、以下のような感じで記事をプランしてみる。

  • コンパイル時編
    • 構文解析 ... Bison を使っている。kmyacc に変えるかもしれない。(本記事)
    • Switch-Case ... 色々条件を判断。結構複雑。
  • 実行時編
    • VM(Virtual Machine) ... gcc ではダイレクト・スレッディング。
    • Garbage Collection ... ザ・マーク・アンド・スイープ。静的スコープの扱い。
    • JIT ... ネイティブの機械語コードにコンパイルするために採用した方法。
    • Fiber ... 思い付きで実装したら割と動いたのでその方法。

Kinx での構文解析

Yacc/Bison とは

Kinx での構文解析は今現在 GNU Bison を使っている。いわゆる LALR(1) の Yacc 系統のパーサ・ジェネレータ。コンパイラを作る時のジェネレータであるため、俗に コンパイラ・コンパイラ と呼ばれている。GNU Bison 自体は GPLv2 だが、その出力(パーサ自体)は例外条項によって自分でライセンスを付けることができる。

元々コンパイラを書く場合、手書きの再帰下降構文解析で実装するのが好きだったのだが、あとから文法を理解しやすい、変えやすいという点で Yacc にしよう、と思ったので使った。最初は miniyacc という簡易 Yacc を使っていたのだが、エラーリカバリ処理がない という致命的な弱点のため現在は GNU Bison を使うように修正。ただ、Windows で Bison を使うのは面倒なのでここだけ Linux で修正する、という作業が非常に面倒くさいことになっている。それもあって、kmyacc に乗り換えようかなー、というのが現時点のステータス。

ちなみに、再帰下降構文解析(再帰下降パーサ)とは、BNF で書いた時の構文ルールを関数呼び出しの形で表したもの。結構書いてて面白い。例えば、expr → ... → factor という流れで、factor には '(' expr ')' が定義されているとき、factor 関数の中で '(' を認識したら再帰的に expr 関数を呼ぶことで構文解析していく。トップダウン的に下方向に向かって解析し、途中再帰的に上に戻ってくるので再帰下降と呼ばれている。詳しくは Wikipedia も参照してみるといいかも。

構文解析・字句解析

構文定義ファイルは src/kinx.y にあり、構文自体は BNF という記法で書く。尚、Yacc(Bison) の動きに関しては、Rubyソースコード完全解説 にある 第9章 速習yacc がわかりやすくて良いと思ったので紹介しておきます。

今回作ったパーサに関して、特徴的なところをピックアップしてみよう。

字句解析部分

コンパイラを作る場合、実は構文解析の前に 字句解析 というフェーズが入る。だいたい以下のような流れで進む。

字句解析
   ↓
構文解析
   ↓
意味解析

字句解析は yacc と対になって lex というツール(Bison の場合 flex)を使うのも標準的なのだが、私は lex は使わない。字句解析は手書きしている。src/lexer.c がソース。いろいろなステータスを細かく制御するのに都合がいいし、手書きでもそれほど面倒な処理ではないので、今回も手書きした。このほうが都合が良いのは、例えば、using のような構文は、字句解析レベルで実施しており、入力ソースを動的に入れ替えるようになっている。

一般的に構文解析では、主に 見た目 だけ扱う。見た目さえ正しければよい。内容が妥当かどうかは次の意味解析のフェーズで行う。

正規表現リテラル

あと、Factor のところに正規表現リテラルが来るのだが、字句解析レベルで /= を DIVEQ と認識してしまっているので、DIVEQ が来た時に /= で始まった正規表現リテラルと判断するようにしている。状態遷移的に DIVEQ として扱われる場所と異なるのでこれはコンフリクトしない。正規表現の最初の文字が = であることをきちんと認識すればよいという感じで扱った。

kmyacc について

kmyacc は森公一郎氏(故人)が作った素晴らしいパーサ・ジェネレータ。非常に昔からお世話になっているツールの一つ。Yacc を使おうというときはたいがいこれを使っていた。なぜ今回使っていなかったのか、それはビルド環境に yacc 含めたかったのでライセンスの緩い miniyacc にしたから(MIT License)。現時点では Bison に変えてしまったので kmyacc で全然かまわない。正直、森氏は後世に名を遺すべき人であって、kmyacc も遺しておくべき作品だと思う。彼が亡くなったとき(2015年)は、それはもう日本のプログラミング業界でひと騒動あったのも記憶に新しい。

森氏は LSI-C の作者でもあり、試食版というフリーの LSI-C にも(かなり)昔お世話になった。出力アセンブラが美しいことで有名だ。実際、当時は C では遅いのでサブルーチンはアセンブラで書く ということをやっており、これは今でいう Java は遅いのでライブラリは JNI で書く と同じことだと思えばよい。時代が変わってもやってることは変わんないな。扱うレイヤが変わっただけだ。

その時、当時使っていた Turbo C のアセンブラ出力と LSI-C のアセンブラ出力を見比べて 確かになんて美しいんだ、と感動した記憶もある。どう違うかというと、LSI-C は極限までレジスタを割り当てるので、ほとんどスタックを汚さない。Turbo C は関数呼び出しの引数は基本スタック渡しで汚いなー、と。アセンブラでサブルーチンを作る際に LSI-C の出力コードは大いに参考になった。

話は尽きないので、昔話はここでおしまいにしよう。

つまりは、森氏の名前と kmyacc という作品を後世に遺すべく、今後 Bison から kmyacc に鞍替えする計画を立てるかもしれない、ということだ。

おわりに

あまり構文解析処理自体には触れていないが、ソースコードを見てみると何となく理解できるかもしれない。もちろん、バグがあれば教えて下さい。お願いします。

最後はいつもの以下の定型フォーマットで締め括り。

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

【Rails】小数点と3桁ずつ区切るカンマ

1234.50

 "#{(product.price).to_f}"
=> 1234.5

 "#{(product.price).to_s(:delimited)}"
=> 1,234.5
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

衆議院の質問主意書データをWebページから集計する

国会に提出された質問主意書データをRubyを使って集計する。

集計に使った元データが必要な方は、@ts_3156までご連絡ください。データの各種加工もお引き受けいたします。

集計結果は以下の通り。

国会議員ごとのランキング

1位 初鹿 明博 20回
2位 櫻井 周 17回
3位 松原 仁 16回
4位 丸山 穂高 16回
5位 城井 崇 8回
6位 早稲田 夕季 8回
7位 下地 幹郎 6回
8位 中谷 一馬 5回
9位 阿部 知子 4回
10位 江田 憲司 3回
11位 山井 和則 3回
12位 森山 浩行 2回
13位 岡本 充功 2回
14位 大河原 雅子 2回
15位 柿沢 未途 1回
16位 山崎 誠 1回
17位 中島 克仁 1回
18位 山内 康一 1回
19位 稲富 修二 1回
20位 青山 大人 1回
21位 関 健一郎 1回
22位 赤嶺 政賢 1回
23位 黒岩 宇洋 1回
24位 亀井 亜紀子 1回
25位 大西 健介 1回
26位 階 猛 1回
27位 奥野 総一郎 1回

会派ごとのランキング

1位 立憲民主・国民・社保・無所属フォーラム 82回
2位 無所属 42回
3位 日本共産党 1回

以下、集計に使ったRubyコード

Gemfile
gem 'anemone'
require 'anemone'

def scrape(url)
  Anemone.crawl(url, depth_limit: 0) do |anemone|
    anemone.on_every_page do |page|
      return page
    end
  end
end

url = 'http://www.shugiin.go.jp/internet/itdb_shitsumon.nsf/html/shitsumon/kaiji201_l.htm'
progress_links = []
question_links = []
answer_links = []

scrape(url).links.each do |link|
  str = link.to_s

  if str.match?(/[^ab]201\d{3}\.htm/)
    progress_links << link
  elsif str.match?(/a201\d{3}\.htm/)
    question_links << link
  elsif str.match?(/b201\d{3}\.htm/)
    answer_links << link
  end
end

questions = []

progress_links.each do |link|
  page = scrape(link.to_s)
  html = Nokogiri::HTML(page.body)
  table = html.xpath('//*[@id="mainlayout"]/table')
  question = {}

  table.xpath('tr').each do |row|
    komoku = row.xpath("td[@headers='KOMOKU']").text
    naiyo = row.xpath("td[@headers='NAIYO']").text

    next if komoku == ''
    question[komoku] = naiyo
  end

  questions << question

  sleep 10
end

提出者名で順位を集計する場合

ranking = Hash.new(0)
questions.each do |question|
  key = question['提出者名']
  # key = question['会派名']
  ranking[key] += 1
end; nil

ranking.sort_by { |k, v| -v }.each.with_index do |(key, count), i|
  puts "#{i + 1}#{key.chomp('君')} #{count}回"
end

集計に使った元データが必要な方は、@ts_3156までご連絡ください。データの各種加工もお引き受けいたします。

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

RailsエンジニアがDDDやクリーンアーキテクチャに触れるためにとりあえずHanamiを始めてみる

Introduction

これまで Rails をそれなりにやってきて、ちょっとしたWebアプリならそれなりサクッと作れるようにはなりましたが、 Fat Controller だの Fat Model と言われるようにどこかで臨界点が来て、従来のMVCアーキテクチャとは違う別の設計を模索してみたい欲が出てきました :flushed:

そのとっかりとして、巷で話題の クリーンアーキテクチャ に触れるために、 Rails の対抗馬として名乗りを挙げた Ruby 製フレームワーク Hanami を始めてみようかと思いました :fire: :fire: :fire:

Install

まずは Hanami を立ち上げてみます。

$ mkdir hanami-tutorial
$ cd hanami-tutorial
$ bundle init

Gemfile を書き換えます。

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

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

gem "hanami"

ライブラリをインストールして Rails と同様にサーバーを立ち上げます。

$ bundle
$ bundle exec hanami new .
$ bundle exec hanami server

導入完了!と思いきや、2020年3月20日現在、エラーになりました :cry:

Bundler could not find compatible versions for gem "dry-types":
  In snapshot (Gemfile.lock):
    dry-types (= 0.12.3)

  In Gemfile:
    hanami (~> 1.3) was resolved to 1.3.3, which depends on
      hanami-validations (>= 1.3, < 3) was resolved to 1.3.6, which depends on
        dry-validation (~> 0.11, < 0.12) was resolved to 0.11.2, which depends on
          dry-types (~> 0.12.0)

    hanami-model (~> 1.3) was resolved to 1.3.2, which depends on
      dry-types (~> 0.11.0)

Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.

指示に従って、もう一度サーバーを立ち上げます。

$ bundle update
$ bundle exec hanami server

http://localhost:2300/ を開くと...

image.png

:yum:

Static Page

Rails チュートリアルと同様に、まずは静的なページを作ってみます。

ルーティングは Rails 感があります。

apps/web/config/routes.rb
root to: 'home#index'

アクションは Rails と違い独立したクラスを作ります。

apps/web/controllers/home/index.rb
module Web
  module Controllers
    module Home
      class Index
        include Web::Action

        def call(params)
        end
      end
    end
  end
end

ビューも Rails と違います。 Rails における View は Hanami においては ViewTemplate に分かれていて、 Rails の Helper などの UI に関わるロジックは View で、 HTML テンプレートは Template が担当します。

apps/web/views/home/index.rb
module Web
  module Views
    module Home
      class Index
        include Web::View
      end
    end
  end
end
apps/web/templates/home/index.html.erb
<h1>Bookshelf</h1>

http://localhost:2300/ を開くとページが差し替わったはずです。
これで自由にページを作れるようになりました :smile:

Template Engine

Hanami のデフォルトのテンプレートエンジンは Rails と同様に Erb ですが、 Slim や Haml で書きたいですよね。
Slim をインストールして、先ほど作ったテンプレートを書き換えます。

Gemfile
gem 'slim'
$ bundle
$ mv apps/web/templates/home/index.html.erb apps/web/templates/home/index.html.slim
apps/web/templates/home/index.html.slim
h1 Bookshelf

これで Slim は導入できました :thumbsup:

Pry

Ruby 2.7 以下だとデバッグのために Pry を導入したいですね。これも簡単。

Gemfile
gem 'pry'
$ bundle
$ bundle exec hanami console
[1] pry(main)> 

Ridgepole

意見は分かれますが、自分はプロトタイピング重視で、 Rails のマイグレーションではなく Ridgepole を使いたいです。

Gemfile
gem 'ridgepole'
$ bundle

Rails の database.yml に相当するものが無いので、便宜上新たに作ります。

config/database.yml
# ridgepole を使うために用意

development:
  adapter: sqlite3
  database: db/hanami_tutorial_development.sqlite

Ridgepole が使う Schemafile を生成します。まだテーブルを定義していないので空ファイルが出力されます。

$ bundle exec ridgepole -c config/database.yml -e > Schemafile

テーブルを作ってみます。

Schemafile
create_table :users, force: :cascade do |t|
  t.string :email, null: false
  t.timestamps null: false
  t.index :email, unique: true
end
$ bundle exec ridgepole -c config/database.yml -a
Apply `Schemafile`
-- create_table("users", {})
   -> 0.0052s
-- add_index("users", ["email"], {:unique=>true})
   -> 0.0035s

テーブルが作られたのでモデルを作ります。 Rails におけるモデルは Hanami においては RepositoryEntity に分かれます。クリーンアーキテクチャが見えてきましたね :point_up:

lib/hanami_tutorial/repositories/user_repository.rb
class UserRepository < Hanami::Repository
end
lib/hanami_tutorial/entities/user.rb
class User < Hanami::Entity
end

コンソールで動作確認してみます。

[1] pry(main)> UserRepository.new.create(email: 'test@example.com')
[2] pry(main)> UserRepository.new.find(1)
=> #<User:0x0000000000000000 ...
[3] pry(main)> UserRepository.new.users.where(email: 'test@example.com')
=> #<ROM::Relation::Composite name=users dataset= ...

Hanami は ActiveRecord ではなく Rom という ORM を使っていますが、 Rails エンジニアなら雰囲気で使えそうな気がしますよね? :grin:

Job Queue

Rails の ActiveJob に相当する非同期処理モジュールが Hanami にはありませんが、自分の経験上 Job Queue ミドルウェアを移行することはそんなに無いので、ここでは Sidekiq を入れてみましょう。

Gemfile
gem 'sidekiq'
config/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: ENV['REDIS_URL'] }
end

Sidekiq.configure_client do |config|
  config.redis = { url: ENV['REDIS_URL'] }
end

ワーカーを作ります。引数で与えられた秒数だけ待機して foo! と叫ぶだけのやつです。

lib/hanami_tutorial/workers/sleepy_echo_worker.rb
class SleepyEchoWorker
  include Sidekiq::Worker

  def perform(time)
    sleep time
    puts 'foo!'
  end
end

Sidekiq を立ち上げます。

$ REDIS_URL=redis://localhost:6379 bundle exec sidekiq -r ./config/boot.rb 

コンソールでワーカーを呼び出します。

[1] pry(main)> SleepyEchoWorker.perform_async(3)

Sidekiq を立ち上げたターミナルで、3秒間待機した後に foo! とログに出ることが確認できましたね :sunglasses:

Conclusion

Rails エンジニアが Hanami でWebアプリを開発するための初期導入についてまとめてみました。今後 Hanami で開発する際にハマったことがあればここに追記していこうかと思います。

ありがとうございました :bow:

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