20200525のRailsに関する記事は26件です。

正規表現の基礎

概要

irbを使って正規表現について見ていきます。


irb

irb(main):001:0> name = "taro"
=> "taro"
irb(main):002:0> name.sub(/taro/,"kotaro")
=> "kotaro"

subメソッドを使ってtaroという文字をkotaroに置き換えています。
第1引数に置き換えたい文字列を指定し、第2引数に変換後の文字列を記述します。


irb(main):004:0> name.match(/taro/)
=> #<MatchData "taro">
irb(main):005:0> name.match(/bob/)
=> nil

matchメソッドを使って引数に指定した文字列が含まれているかをチェックしています。
含まれていた場合は指定した文字列がMatchDataオブジェクトとして返り値で返ってきます。
含まれていなかった場合はnilが返ってきます。

irb(main):006:0> array = name.match(/taro/)
=> #<MatchData "taro">
irb(main):007:0> array[0]
=> "taro"

MatchDataオブジェクトは配列なので、上記のようにすると値を取得することができます。

irb(main):008:0> phoneNumber = "080-1234-5678"
=> "080-1234-5678"
irb(main):009:0> phoneNumber.gsub(/-/,"")
=> "08012345678"

電話番号のハイフンを取り除きたい場合ですが、subメソッドを使ってしまうと最初のハイフンしか置換されないので、gsubメソッドを使いましょう。gとはグローバルマッチで、指定した文字列が複数含まれている場合全て置換してくれます。

irb(main):010:0> myPassword = "Taro0123"
=> "Taro0123"
irb(main):012:0> myPassword.match(/[a-z\d]{8,10}/i)
=> #<MatchData "Taro0123">
irb(main):013:0> myPassword.match(/[a-c\d]/i)
=> #<MatchData "a">
irb(main):014:0> myPassword.match(/[a-c\d]{8,}/i)
=> nil

・[a-z]は、a~zの文字がいずれか1個にマッチ
・\dは、数字にマッチ
・{8,10}は、直前の文字列が少なくとも8回、多くても10回出現するものにマッチ
・iは、大文字と小文字を区別しないで検索する
という意味です。

irb(main):015:0> myAddress = "yey@gmail.jp"
=> "yey@gmail.jp"
irb(main):016:0> myAddress.match(/@.+/)
=> #<MatchData "@gmail.jp">
irb(main):017:0> 

メールアドレスのドメインを取得する場合
.は、どの1文字にもマッチ
+は、直前の文字の1回以上の繰り返しにマッチ
という意味を持ちます。
ですので@.+ とすると
@から始まり、全ての文字にマッチ、それを繰り返す。ということになるので
@以降のドメインが取得できます。

この記事を読んでいただきありがとうございました。

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

[Rails]テーブルのカラム追加、型変更、型確認方法、カラムの型の種類

こんな時

あ。追加でカラム欲しいな。
あ。型変更したいな。
カラムの型を簡単にさっと見たいな。
型っていっぱいあったよな、どれがなんだっけ。

記載事項

1.カラム追加方法
2.カラムの型変更方法
3.ターミナルでの型確認方法
4.カラムの型の種類(毎回ここにRails見に行ってるので、Qiitaに持ってきます。)

1.カラム追加方法

ターミナル
rails g migration Addカラム名Toテーブル名 カラム名:データ型

# postsテーブルにstart_timeカラムをtime型で追加
rails g migration AddStartTimeToPosts start_time:time

# マイグレーションファイルが作成される
# create    db/migrate/~~~_add_start_time_to_posts.rb

ファイルを編集。
他にもカラム追加したかったので追加。
null: falseもつけて、空のデータは保存不可にします。

~~~_add_start_time_to_posts.rb
class AddStartTimeToPosts < ActiveRecord::Migration[5.2]
  def change
    add_column :posts, :start_time, :time, null: false
    add_column :posts, :end_time, :time, null: false
    add_column :posts, :golf_place, :string
    add_column :posts, :min_member, :integer, null: false
    add_column :posts, :max_member, :integer, null: false
  end
end

rails db:migrateして終了!!

ターミナル
rails db:migrate

2.カラムの型変更方法

ターミナル
rails g migration change_data_カラム名_to_テーブル名

# postsテーブルのdateカラムの型を変更
rails g migration change_data_date_to_posts

# マイグレーションファイルが出来る
# db/migrate/~~~_change_data_date_to_posts.rb

マイグレーションファイルを編集します。

~~~_change_data_date_to_posts.rb
# change_column :posts, :カラム名, :型
# dateカラムをdate型に変更したかったのです。

class ChangeDataDateToPosts < ActiveRecord::Migration[5.2]
  def change
    change_column :posts, :date, :date
  end
end

rails db:migrateして終了!!

ターミナル
rails db:migrate

3.カラムの型確認方法

ターミナルでコンソールを使います。

ターミナル
rails c
ターミナル
# モデル名.columns_hash['カラム名'].type

pry(main)> Post.columns_hash['date'].type
=> :date

dateカラムはdate型と確認出来ましたね。

4.カラムの型一覧

最後にカラムの型一覧です。

[カラムの型]
- データ方 説明
- string 文字列
- text 長い文字列
- integer 整数
- float 浮動小数
- decimal 精度の高い小数
- datetime 日時
- timestamp より細かい日時
- time 時間
- date 日付
- binary バイナリデータ
- boolean Boolean型

元ソース Rails

ありがとうございました!!

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

Failed to find Chrome binary について

前提

本日学んだことを書いていきます。
Docker
Ruby on Rails
Ruby

本題

Capybara でのテスト実行時に以下のようなエラーが出てしまい、ハマりました。。。

Failure/Error: visit login_path

          Webdrivers::BrowserNotFound:
            Failed to find Chrome binary.

Chrome binary が見つからない?
Chrome がないのが原因?
Docker環境内のためChromeが入っていないのかなと思いました。

解決方法

capybara.rb
#変更前
Capybara.register_driver :chrome_headless do |app|
  options = ::Selenium::WebDriver::Chrome::Options.new

  options.add_argument('--headless')
  options.add_argument('--no-sandbox')
  options.add_argument('--disable-dev-shm-usage')
  options.add_argument('--window-size=1400,1400')

  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

Capybara.javascript_driver = :chrome_headless

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end

  config.before(:each, type: :system, js: true) do
    driven_by :chrome_headless
  end
end
capybara.rb
#変更後
Capybara.register_driver :chrome_headless do |app|
  options = ::Selenium::WebDriver::Chrome::Options.new

  options.add_argument('--headless')
  options.add_argument('--no-sandbox')
  options.add_argument('--disable-dev-shm-usage')
  options.add_argument('--window-size=1400,1400')

  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

Capybara.javascript_driver = :chrome_headless

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end

  config.before(:each, type: :system, js: true) do
    if ENV["SELENIUM_DRIVER_URL"].present?
      driven_by :selenium, using: :chrome, options: {
        browser: :remote,
        url: ENV.fetch("SELENIUM_DRIVER_URL"),
        desired_capabilities: :chrome
      }
    else
      driven_by :selenium_chrome_headless
    end
  end
end
docker-compose.yml
#変更前
version: '3'
services:
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app
    ports:
      - 3000:3000
    depends_on:
      - db
    tty: true
    stdin_open: true
  db:
    image: mysql:5.7
    volumes:
      - db-volume:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
volumes:
  db-volume:
docker-compose.yml
#変更後
version: '3'
services:
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app
    ports:
      - 3000:3000
    depends_on:
      - db
    tty: true
    stdin_open: true
    environment: #以下追記
    - "SELENIUM_DRIVER_URL=http://selenium_chrome:4444/wd/hub"
  selenium_chrome:
    image: selenium/standalone-chrome-debug
    logging:
      driver: none
  db:
    image: mysql:5.7
    volumes:
      - db-volume:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
volumes:
  db-volume:

上記設定で解決しました。

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

Railsの基本理念

Railsの生みの親DHHが書いた基本理念をまとめました

https://rubyonrails.org/doctrine/#optimize-for-programmer-happiness
https://postd.cc/rails-doctrine/

1. プログラマの幸福度を最適化

DHHがRailsを作った理由は「自分が笑顔になるため」

論理的ではなくむしろ病的だが、自分が幸福になるために作ってきた

Railsのコミュニティは幸福のために集まり、幸福の最適化によってRailsは形成されてきた

2. 設定より規約 (Convention over Configuration、CoC)

Railsの生産性に関する初期のモットーは「無駄な個性をやめれば、平凡な決断で苦労することなく、重要な場面で仕事のスピードが増す」データベースの主キーがidか、postIdか、posts_idか、pidか議論するのは無駄設定から規約に移行することで、無駄な熟考を避け生産性を高めることができる

加えて、規約は初心者のハードルを下げてくれるRailsの規約の中には初心者が知らなくてもいいものがたくさんある彼らにはその無知ゆえのメリットがある全てを知らずともすばらしいアプリケーションを作ることができるフレームワークがただの分厚い教科書であればそれは不可能

そうすると、「全て既存のテンプレートで作れる」と思いがちだが、たいていは5%や1%でも独自の要素を持つ価値がある

規約からいつ逸脱するかは難しい逸脱したいという衝動のほとんどは熟考されておらず、レールを外れて行くことのコストは過小評価されている

  • コメント

あまり気にしたことなかったですが、Ruby on Rails の名前は「規約 (Rail)に沿ったRuby」って意味なんでしょうねhttps://jp.quora.com/Ruby-Rails-no-namae-no-yurai-wo-oshie-te-kuda-sai

3. メニューはおまかせで

プログラミングにとって、他の人に任せることの利点は、設定より規約から得られるものと似ているが、より高いレベルにある。CoCが個々のフレームワークをどのように使うのがベストなのかを考えるのに対し、おまかせはどのフレームワークをどのように組み合わせて使うのかを考える。これは、利用可能なツールを個々の選択肢として提示するという崇高なプログラミングの伝統に反するしかし、その伝統が個々のプログラマに決定する特権(と重荷)を与えてきた「仕事に最適な道具を使え」という言葉は議論の余地がないほど初歩的なことだが、「最高の道具」を選ぶには、「最高」を自信を持って判断できる土台が必要であり、これは思った以上に難しい。

そこで私たちは、Railsでは、全員にとって良いツールボックスのために、箱の中の各ツールを選ぶというプログラマーの個人的な特権を1つ減らすことにした。その結果、多くの利益を得ることができた。

1.みんなが使っていれば安全

ほとんどの人が同じデフォルトでRailsを使っているとき、経験を共有できる。共通のベースがあると、人に教えたり助けたりするのが格段に楽になる。それはより強いコミュニティの感覚を育む。

2.共通の基本的なツールボックスを人々が進化させる

フルスタックフレームワークとして、Railsは動的な部分も数多くありますし、それぞれが単独でどのように機能するのかと同じくらいに、それらがどのように組み合わさって機能するのかというのが重要になる。ソフトウェアの不具合はそれぞれのコンポーネントが原因ではなく、それらの相互作用の中で発生する。構成された複数のコンポーネントから、共通の不具合を緩和しようとみんなで取り組み、同じ状態で失敗するならば、不具合の回数を減らすことができる。

3.交換は可能だが、必須ではない

Railsは“おまかせ“スタックである一方で、他のフレームワークやライブラリと取り替えることもできる。しかし、必須ではない。つまり、たまに発生する相違に適した、明確で個人的なパレットをのちに開発することもできる。経験もあり腕のあるプログラマでさえ、メニューの中全てのものを気に入らないことは十分にある(もし、全てに満足している人がいるならば、それはRailsでまだ行き詰まっていないのでしょう)。彼らは勤勉にも別のものに交換し、それからみんなが監督し共有している他のスタックにいってみて、楽しんでいる。

4. パラダイムは1つではない

「Railsは多くの異なる考え方からできている」以外何も分からなかった…

5. 美しいコードを称える

私たちは、コンピュータや他のプログラマーに理解されるためだけにコードを書くのではなく、美しさの温かな光を浴びるためにコードを書きます。美しいコードはそれ自体が価値であり、精力的に追求されるべきものです。だからといって、美しいコードが常に他の事に勝るというわけではありませんが、優先順位には必ずあるべきです。

class Project < ApplicationRecord
    belongs_to :account
    has_many :participants, class_name: 'Person'
    validates :name, presence: true
end

これは DSL のように見えますが、実際にはただのクラス定義で、シンボルとオプションを取る 3 つのクラスメソッドを呼び出しています。派手なものは何もありません。しかし、確かにきれいで、シンプルです。この数少ない宣言から、膨大な量のパワーと柔軟性を得ることができます。

美しさの一部は、これらの呼び出しが「設定より規約」を尊重していることから来ています。belongs_to :accountを呼び出すとき、外部キーはaccount_idと呼ばれ、それがProjectsのテーブルにあることを前提としています。Personのクラス名をparticipantsのアソシエーションの役割に指定する必要がある場合、そのクラス名の定義のみが必要です。そこから外部キーと他の設定が得られます。

class CreateAccounts < ActiveRecord::Migration
  def change
    create_table :accounts do |t|
      t.integer :queenbee_id
      t.timestamps
    end
  end
end

これがフレームワークの力の本質です。プログラマは特定の規約(例えば、#changeを実装したActiveRecord::Migrationのサブクラス)に従ってクラスを宣言します。そうするとフレームワークは、それをひと通り全部調べて、これが呼び出すメソッドだと認識します。

これにより、プログラマは書くべきコードが非常に少なくなります。マイグレーションの場合、rails db:migrateを呼び出してDBをアップグレードして新しいテーブルを追加するだけでなく、rails db:rollbackでこのテーブルを削除することもできます。これは、プログラマーがこれらのことを実現するために、ライブラリを使ってワークフローをつなぎ合わせるのとは大きく異なります。

6. 鋭いナイフを提供する

Rubyは機能の中に鋭いナイフをたくさん含んでいます。それは偶然ではなく、設計上のものです。最も有名なのはモンキーパッチです。Railsで提供されるナイフはRubyで提供されるものほど鋭くはありませんが、それでも切れ味は十分です。私は、すべてのプログラマには、権利とまではいかないまでも、完全に有能なRubyとRailsのプログラマになる道があると信じています。そして、有能というのは、状況に応じて、いつ、どのように、引き出しの中のさまざまな、時に危険なツールを使うべきかを知ることができる知識を持っているという意味です。

7. 統合システムを尊重する

Railsは様々な文脈で利用できますが、一番は統合システムを作ることです。 壮大なモノリス!問題全体に対処するシステム全体です。つまり、RailsはフロントエンドのJavaScriptから、DBまで、すべてに関わっているということです。これは非常に広い範囲ですが、一人の人間が理解するのに非現実的ほど広い範囲ではありません。Railsは特に、ジェネラリストの個人がこれらの完全なシステムを作れるようにすることを目指しています。個人に力を与えることに焦点を当てているのが、統合されたシステムなのです。1つの統合システムとしての使いやすさと分かりやすさを備え、個別にチューニングされ分散されたアプリケーションの力を得ることができます

  • コメント

あまりマイクロサービスには賛成じゃなさそうですね

8.安定性より進歩

Railsのようにシステムが10年以上前から存在している場合、自然と固定化していく傾向にあります。過去の挙動に依存していた人にとって、どんな変更でも問題になる可能性があるのは当然です。しかし、あまりにも保守的になっていると、反対側に何があるか見えなくなります。進化と成長のためには現状を壊し変更する必要があります。進化することで、これからの数十年Railsが生存と繁栄をすることができます。

Railsのバージョンも上がっていっていますが、Rubyの新しいバージョンを素早く採用することで、Rubyの発展のために先頭を切って貢献するべきです。

進歩とは、結局変化を推し進めようとする人とその意欲にかかっていることがほとんどです。Rails CoreやRails Committersのようなグループに無期限の席がないのはこのためです。どちらのグループも、フレームワークの進歩に積極的に取り組んでいる人たちのためのものです。同様に、コミュニティの新しいメンバーを歓迎し、奨励し続けることが非常に重要な理由です。私たちは、より良い進歩を遂げるために、新鮮な血と新鮮なアイデアを必要としています。

9. テントを押し上げる

Railsには異論のあるアイデアがたくさん取り入れられているので、もしあらゆる考え方に対して完全に従うよう常に皆さんに求めていたら、Railsは思想的な隠者が集まるグループとしてすぐに孤立してしまうでしょう。ですので、それはありません!意見の相違が必要です。方言が必要です。思想や人の多様性が必要なのです。このアイデアのるつぼの中にこそ最高の共有物があるのです。私がしばしば重大な不満を表明してきたテスト用DSLであるRSpecの継続的な成功は、完璧な証拠です。私はなぜこれではいけないと思うのか、顔が真っ青になるまでわめき散らすことができますが、それでもRSpecはまだ花開き、繁栄することができます。その点の方が遥かに重要なのです

APIとしてのRailsの登場についても同じことが言えます。私の個人的な焦点とこだわりは、ビューを含む統合システムにありますが、クライアントやサーバを先行して配布したいと考えている人には、Railsがうまくいく余地があることは間違いありません。

大きなテントを持つということは、すべての人に万能であろうとすることではありません。すべての人を歓迎し、自分の飲み物を持ってくることを許可するということだ。他の人にも参加してもらうことで、私たちの魂や価値観を失う必要はないし、新しいおいしい飲み物の混ぜ方も学べるかもしれない。これはタダではできません。歓迎するためには努力が必要です。特に、あなたの目標が、コミュニティの既存メンバーと同じような人をさらに集めることだけが目的でない場合は、なおさらです。参入障壁を下げることは、常に真剣に取り組むべき仕事です。ドキュメントのスペルミスを修正するだけで始めた次の人が、いつ次の素晴らしい機能を実装することになるかわかりません。しかし、あなたが微笑んで、どんな小さな貢献にも感謝することで、モチベーションを高めることができるかもしれません。

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

100日後に1人前になる新人エンジニア(5日目)

100日後に1人前になる新人エンジニア(5日目)

100日後に1人前になる新人エンジニア(4日目)

どうもこんばんは早くも5日目になりました。
今日のお題はRailsのroutesに関してです。
調べてみると結構いろんなルールがありましたのでまとめてみたいと思います。

Rails のルーティング

Railsルーター

Railsのルーターは受け取ったURLを認識し、適切なコントローラ内アクションに割り当てるよ。

GET /patients/17                  

こんなURLでリクエストがあったとき

get '/patients/:id', to: 'patients#show' 

patientsコントローラのshowアクションに割り当てられられています。
17の部分は :idに対応しているよ。
to: 以降は 'コントローラー#アクション' って感じで対応付けがされています。

ResourceベースのRouting

関連するいろんなリクエストを1つのアクションにまとめたやつです。

resources :users

こんな感じでroutesに一行書くだけで以下のrouteが追加されます

HTTP パス コントローラ#アクション
GET /users users#index
GET /users/new users#new
POST /users users#create
GET /users/:id users#show
GET /users/:id/edit users#edit
PATCH/PUT /uses/:id users#update
DELETE /users/:id users#destroy

コントローラの名前空間とrouting

名前空間によってグループ化することができるよ。

namespace :admin do
  resources :users
end
HTTP パス コントローラ#アクション
GET /admin/users admin/users#index
GET /admin/users/new admin/users#new
POST /admin/users admin/users#create
GET /admin/users/:id admin/users#show
GET /admin/users/:id/edit admin/users#edit
PATCH/PUT /admin/uses/:id admin/users#update
DELETE /admin/users/:id admin/users#destroy

こうすることで/adminから始まるグループでルーティングを作成することができるんだね。

ちなみにresourcesでいくつかのroutesを選択して使用したい場合は

resources :articles do
  resources :comments, only: [:index, :new, :create]
end

こんな感じで only:以降に使用したいroutesを指定すれば良い。
これで必要のないroutesを指定することもなさそうだね。

名前付きrouting

:asオプションを使うと、どんなルーティングにも名前を指定できます。

get 'exit', to: 'sessions#destroy', as: :logout

上のルーティングではlogout_pathとlogout_urlがアプリケーションの名前付きルーティングヘルパーとして作成されます。logout_pathを呼び出すと/exitが返されます。
という様に:asで名前付きroutingを作成できます。

resourcesの時にも名前付きroutingは作成されています。

基本的にはurlの指定は名前付きrouteで指定することが多そうなので、
この方法に慣れることは結構重要だなと最近感じております。

全部書くと無限にありそうなので今日はここら辺でおしまい。
わからなくなったらRailsガイドをみて都度確認する癖をつけたい。

1人前のエンジニアになるまであと95日

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

ユーザー登録時のエラーメッセージ、サクセスメッセージの表示の仕方

はじめに

RailsでWebサービスを作成の際にサインんアップ時のフラッシュメッセージの表示の仕方を自分の得た知識の整理のために記事を作成しました。
基本はRailsチュートリアルで得た知識になります。

前提条件

  • Railsを使用したWebサービスを作成途中で、すでに基本的なログイン機能を自分でdeviceを使用せず作ったとします。
  • バリデーション実装済み

controller

def create
    @user = User.new(user_params) 
    if @user.save   #@userの保存に成功
      redirect_to @user  #ユーザー詳細にリダイレクト
    else
      render 'new'  #失敗時は新規登録へ戻る
    end
  end

  private

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

新規登録に失敗したときのエラーメッセージ

新規登録の際にパスワードが空白などのバリデーションに引っかかった場合に新規登録画面に戻ってエラーメッセージを表示します。

new.html.erb
 <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>

上記のコードではform_forメソッドの中にrenderを挿入しsharedフォルダにまとめてあるerror_messagesのファイを持ってきています。
エラーメッセージなどを挿入する際には、個別のviewにコードを記述しても結果としては変わりません。しかし、複数のviewで使用されるパーシャルはsharedディレクトリを作成してそこで管理し、そして必要なところにrenderで挿入するのがRailsでは慣習となっています。

では、error_messagesのファイルをみてみましょう。

_error_messages.html.erb
<% if @user.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

このコードでは様々なメソッドが使用されています。一つずつみていきましょう。

<% if @user.errors.any? %>

この行ですが@user.errorsに対してany?メソッドを使用しています。
any?メソッドは読んで字の如く、指定した対象一つでもあれば、論理値でtrueを返します。
そのため、ここはif文の条件式で「@userになんか不備ある?」ということを聞いています。
そして、何か不備があった場合はその下の処理が発動して、先ほどのnew.html.erbに挿入されます。

The form contains <%= pluralize(@user.errors.count, "error") %>.

次にこのコードです。
これはエラーの数を数えて表示しますというコードです。
ここで見るからに難しそうな単語のpluralizeというメソッドがありますが全然難しくありません。
これは勝手に数えた数に応じて単数形と複数形をわけて出力してくれるメソッド(英語限定)です。
helperオブジェクトを使用して例にあげると

>> helper.pluralize(1, "error")
=> "1 error"
>> helper.pluralize(5, "error")
=> "5 errors"

上記ではhelperオブジェクトに対して使用したpluralizeメソッドが第一引数で整数値をとった場合、その整数値に合わせて第二引数の英単語を複数形にして出力します。
このメソッドのすごい所は可算名詞だけでなく不可算名詞にも対応しているので何かを数えて表示したい際に、不自然な形になることを防げます。
改めて今回のコードをみます。

The form contains <%= pluralize(@user.errors.count, "error") %>.

pluralizeメソッドの第一引数には@user.errors.countがあります。ここではcountメソッドで@user.errorsの数を数えます。そして、第二引数で指定した単位で単数形、複数形判断して出力します。
結果、The form contains 3 errorsとかの表記になります。

<% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>

そして、このeach文で実際に起きているエラーの内容を表示します。

<% @user.errors.full_messages.each do |msg| %>

この文の中のerrors.full_messagesという部分はオブジェクトに対してエラーがないか調べ、もしあった場合は何が悪いのか元々Railsに備え付けられているエラーメッセージを使用して出力します。(日本語で出力するやり方もあるのですが調べてません。)
そして、その出力したエラーメッセージをmsgというブロックに入れて<li><%= msg %></li>で出力します。

新規登録成功時のサクセスメッセージ

サクセスメッセージを表示する際はコントローラーを少し弄ります。

users_controller.rb
def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = "Welcome to the Sample App!" #成功したときのメッセージの指定
      redirect_to @user
    else
      render 'new'
    end
  end

flash

サクセスメッセージはページにリダイレクトした直後だけ表示するようにします。そのためにはflashという特殊な変数を使用します。そして上記のコントローラーではそのflash変数のキーがsuccess(Railsの慣習で成功時を表す)の時は"Welcome to the Sample App!"をバリューとして返すように設定しています。
そしてapplication.html.erbでflash変数にeachメソッドを使用してサクセスメッセージを表示します。
追加するコードは下記の通り、

applocation.html.erb
<!DOCTYPE html>
<html>
.
.
.
<% flash.each do |message_type, message| %>
  <div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
.
.
.
</body>
</html>

上記のコードはちょっとややこしい上に周りくどい書き方をしているのであとでさらに簡略化します。

<% flash.each do |message_type, message| %>

この時点で、flash変数の中にはsuccessというキーと"Welcome to the Sample App!"というバリューがあるのでeachメソッドで取り出してmessage_typeというキーのブロックとmessageというバリューのブロックに入れます。
そして下記で出力します。

<div class="alert alert-<%= message_type %>"><%= message %></div>

class="alert alert-<%= message_type %>"というBootstrapで使用するクラスがありますがここに先ほどのキーを入れるとclass="alert alert-success"となりBootstrapがカッコよくしてくれます。

これでも正常に表示はされるのですが読みにくいのでcontent_tagメソッドを使用して一行にします。

application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<% flash.each do |message_type, message| %>
     <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
<% end %>
.
.
.
</body>
</html>

こっちの方が読みやすいですね!

感想

基本的なサクセス、エラーメッセージの付け方は理解できたので、エラーメッセージを日本語化したり、deviseを使用した時の方法(個人的にこっちの方が重要なんじゃないかとこの記事書きながらずっと考えてたw)についてもしっかり理解していきたいと思います。

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

【Rails 6】active_strageの画像の保存タイミングが変更された

今回はRails6から画像の保存のタイミングが変更したようなので調べてみました。

Rails バージョンの確認

$ rails -v
Rails 6.0.2.1

ActiveStrageのインストール

$ rails active_storage:install
$ rails db:migrate

画像添付の準備

最終成果物を載せます。

routeにpreviewを追加します。

route.rb
Rails.application.routes.draw do
  resources :users do
    collection do
      post :preview
      patch :preview
    end
  end
end

モデルにhas_one_attachedで画像を扱えるようにします。

user.rb
class User < ApplicationRecord
  has_one_attached :image
end

次にcontrollerです。
今回はプレビュー機能をつけたいのでpreviewアクションを追加。

user_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    @users = User.all
  end

  # GET /users/1
  # GET /users/1.json
  def show
  end

  # GET /users/new
  def new
    @user = User.new
  end

  # GET /users/1/edit
  def edit
  end
.
.
.
.
  def preview
    @user = User.new(user_params)
    image_binary = ''
    if @user.image.attached?
      image_binary = @user.attachment_changes['image'].attachable.read
    else
      saved_user = User.find_by(id: params[:id])
      if saved_user.present? && saved_user.image.attached?
        image_binary = saved_user.image.blob.download
      end
    end
    @image_enconded_by_base64 = Base64.strict_encode64(image_binary)

    render template: 'users/_preview', layout: 'application'
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def user_params
      params.require(:user).permit(:name, :image)
    end
end

最後にhtmlです。

new.html.erb
<h1>New User</h1>

<%= render 'form', user: @user %>

<%= link_to 'Back', users_path %>
_form.html.erb
<%= form_with(model: user, local: true) do |form| %>
  <% if user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
        <% user.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
    <%= form.label :image %>
    <%= form.file_field :image %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @user.name %>
  <% if @user.image.attached? %>
    <%= image_tag @user.image %>
  <% end %>
</p>

<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>
_preview.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @user.name %>
  <% if action_name == 'preview' %>
    <img src="data:image/png;base64,<%= @image_enconded_by_base64 %>" />
  <% else %>
    <%= image_tag @member.image %>
  <% end %>
</p>

これで準備ができました。

Rails6での画像の保存のタイミング

早速画像をアップロードしていきます。

スクリーンショット 2020-05-21 18.04.06.png

テスト.jpegをアップロードしてCreate Userボタンを押すと

スクリーンショット 2020-05-21 18.18.57.png

createは成功しています。
ここでアップロードした画像を見に行くと

irb(main):005:0> @user.image.attachment
  ActiveStorage::Attachment Load (1.5ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 1], ["record_type", "User"], ["name", "image"], ["LIMIT", 1]]
=> #<ActiveStorage::Attachment id: 1, name: "image", record_type: "User", record_id: 1, blob_id: 1, created_at: "2020-05-21 09:04:12">

画像が存在するのがわかりました。

アップロードでは保存できていない

次に新しくpreviewを試みます。

スクリーンショット 2020-05-21 18.20.20.png

同じようにプレビュー.jpgをアップロードしてpreviewボタンを押して画像の確認をします。

すると

> @user.image.attachment
NameError: uninitialized constant #<Class:0x00007fd450065780>::Analyzable
from /usr/local/bundle/gems/activestorage-6.0.3.1/app/models/active_storage/blob.rb:26:in `<class:Blob>'

ない!!

> params[:user][:image]
=> #<ActionDispatch::Http::UploadedFile:0x000055892ec2b598
 @content_type="image/jpeg",
 @headers=
  "Content-Disposition: form-data; name=\"user[image]\"; filename=\"\xE3\x83\x95\xE3\x82\x9A\xE3\x83\xAC\xE3\x83\x92\xE3\x82\x99\xE3\x83\xA5\xE3\x83\xBC.jpg\"\r\nContent-Type: image/jpeg\r\n",
 @original_filename="プレビュー.jpg",
 @tempfile=#<File:/tmp/RackMultipart20200522-1-ha9tz1.jpg>>

パラメータを確認。
画像をアップロードしただけでは保存されないのか!

下記を参考にすると
https://github.com/rails/rails/pull/33303

rails5では

@user.image = params[:image]

attributeに入れたタイミングですね!

まさかrails6になってこんな変更があるなんて!!
このままだとプレビュー時(画像をインスタンスに保存せず)に画像をViewに渡せない。

どうにかいい方法が無いか調べていると

https://github.com/rails/rails/pull/33303

https://stackoverflow.com/questions/57564796/reading-from-active-storage-attachment-before-save

あった!!

record.attachment_changes['<attributename>'].attachable

試す!

> @user.attachment_changes['image'].attachable
=> #<ActionDispatch::Http::UploadedFile:0x000055892ec2b598
 @content_type="image/jpeg",
 @headers=
  "Content-Disposition: form-data; name=\"user[image]\"; filename=\"\xE3\x83\x95\xE3\x82\x9A\xE3\x83\xAC\xE3\x83\x92\xE3\x82\x99\xE3\x83\xA5\xE3\x83\xBC.jpg\"\r\nContent-Type: image/jpeg\r\n",
 @original_filename="プレビュー.jpg",
 @tempfile=#<File:/tmp/RackMultipart20200522-1-ha9tz1.jpg>>

さらに

@user.attachment_changes['image'].attachable.read

↑でバイナリデータを取ってくることができたのでBase64でエンコードして渡すと

@image_enconded_by_base64 = Base64.strict_encode64(@user.attachment_changes['image'].attachable.read)

スクリーンショット 2020-05-22 10.48.00.png

できました!!

まとめ

ActiveStrageの保存のタイミングに変更があるなんて知りませんでした。
保存せずに画像を触りたいなら

record.attachment_changes['<attributename>'].attachable.read

でバイナリ読み出してBase64!!(Base64でなくてもいいけど)

他に何かいい方法あれば教えてください。

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

ログイン、サインアップ画面のビュー変更

1.devise用のビューファイルを作成
 deviseでログイン機能を実装すると、ログイン・サインアップ画面は自動的に生成されるがビューファイルとしては生成されない。これは、deviseのGem内に存在するビューファイルを読み込んでいるため。

ビューファイルに変更を加えるためには、deviseのコマンドを利用してビューファイルを生成する必要がある。

ターミナルで
 rails g devise:views
 新規作成されるファイル
 app/views/devise以下のディレクトリにあるビューファイル各種

2.ログイン・サインアップ画面のビューファイルを編集する
 サインアップ画面
 app/views/devise/registrations/new.html.erb
 ログイン画面
 app/views/devise/sessions/new.html.erb

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

『kaminari』配列に対してページネーションを作成する方法

はじめに

今回は、配列に対してページネーションを作成しビューで表示するまでのサンプルコードを紹介します。

コントローラー

お気に入りした商品をユーザーのマイページにて表示するためにコントローラーでこのようなコードを書きました。実はこれ、アソシエーション組めばもっと簡単にできるので以下の記事を参考にしてみてください!
https://qiita.com/yummy888/items/22db2f8b79b5be148b69

 def index
    items = []
    likes = Like.where(user_id: params[:item_id])
    if likes.present?
      likes.each { |like| items << Item.find(like.item_id)}
    end
    @items = Kaminari.paginate_array(items).page(params[:page]).per(15)
  end

user_idになんでparams[:item_id]渡してるの?と思ったかもしれませんが、root.rbにてitemsにlikesをネストしているため、このようなパラメーターの渡し方になっています。(パラメーターでは:item_idとなっているところに,current_userをpathで渡している)本当だったらusesにitemsをネストして、さらにlikesをネストすればもっと綺麗なパラメーターの渡し方ができると思います。

ビュー

hamlでやってます。erbだったらeach文のendの真下にページネーションおく感じですね

 - @items.each do |item|
   = link_to item_path(item), class: "user__liked__items" do
     .user__liked__items__image
       = image_tag "#{item.images[0].image.url}", height:"50px", width: "50px", class: "user__liked__item__image"
       -if item.buyer_id.present? 
         .items__image__sold
           .items__image__sold__inner
              SOLD
       .user__liked__item__details
         .user__liked__item__name
           = item.name
         .user__liked__item__price
           %span
             ¥
             = item.price.to_s(:delimited)
           %i.fas.fa-chevron-right
  = paginate @items

まとめ

自分で色々実装できるようになると、より簡単な実装方法の模索を放棄してしまってると感じるので、そこは改善していきたいですね、、、ちょっとできるように、なっただけで付け上がっちゃいかんのう:santa:

でも、ページネーション作成する場合だったらどちらにしてもコントローラーで色々やらないといけないのかな??ビューでparams[:page]とかできないよな?多分

次この機能実装するときはまた違った実装の方法を試してみますか:frowning2:

参考記事

https://blog.konboi.com/post/2013/03/31/224939/

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

PAY.JPで登録したクレジットカードで商品購入機能を実装する

はじめに

個人アプリにて、クレジットカード決済を行うため、PAY.JPを導入しました。
機能実装に関して、備忘録として記載しています。

前回の記事
PAY.JPでクレジットカードの登録・削除機能を実装する

前提条件

  • Rails 5.2.4.2
  • Ruby 2.5.1
  • devise使用
  • haml使用
  • VSCode使用

前回までにカード情報の登録を行うためCardモデル及びCardコントローラーを作成しました。
今回は新たに購入用モデル及びコントローラーを作成して購入処理を行っていきます。

手順

  1. モデルの作成
  2. テーブルの作成
  3. コントローラーの作成

モデルの作成

今回はOrderモデルを作成していきます。

class Order < ApplicationRecord
  belongs_to :user
  has_many :products, through: :order_details
  has_many :order_details, dependent: :destroy
  belongs_to :card
  belongs_to :address

  enum postage: {burden: 0, free: 1}
  enum status: {支払済み: 0, 配送準備中: 1, 配送済み: 2}

  def add_items(cart)
    cart.line_items.target.each do |item|
      item.cart_id = nil
      line_items << item
    end
  end
end

ECサイトを作成したので、カート機能などを実装している関係で、インスタンスメソッドなど定義しておりますが、今回は購入機能にフォーカスしているため、割愛します。
なので、belongs_to :cardの1行が使いたい部分になります。

テーブルの作成

マイグレーションファイルを以下のようにします。

class CreateOrders < ActiveRecord::Migration[5.2]
  def change
    create_table :orders do |t|
      t.references :user, foreign_key: true
      t.references :address, foreign_key: true
      t.references :card, foreign_key: true
      t.references :product, foreign_key: true
      t.integer :quantity, null: false
      t.integer :status, default: 0, null: false
      t.integer :postage, default: 0, null: false
      t.integer :price, null: false
      t.timestamps
    end
  end
end

cardを外部キーとして設置しています。

コントローラーの作成

Orderコントローラーを作成していきます。
今回はnewとcreateの2つのアクションを使用します。

class OrdersController < ApplicationController
  before_action :set_cart
  before_action :user_signed_in
  before_action :set_user
  before_action :set_card
  before_action :set_address

  require "payjp"

  #注文入力画面
  def new
    @line_items = current_cart.line_items
    @cart = current_cart
    if @cart.line_items.empty?
      redirect_to current_cart, notice: "カートは空です"
      return
    end
    if @card.present?
      customer = Payjp::Customer.retrieve(@card.customer_id)
      default_card_information = customer.cards.retrieve(@card.card_id)
      @card_info = customer.cards.retrieve(@card.card_id)
      @exp_month = default_card_information.exp_month.to_s
      @exp_year = default_card_information.exp_year.to_s.slice(2,3)
      customer_card = customer.cards.retrieve(@card.card_id)
      @card_brand = customer_card.brand
      case @card_brand
      when "Visa"
        @card_src = "icon_visa.png"
      when "JCB"
        @card_src = "icon_jcb.png"
      when "MasterCard"
        @card_src = "icon_mastercard.png"
      when "American Express"
        @card_src = "icon_amex.png"
      when "Diners Club"
        @card_src = "icon_diners.png"
      when "Discover"
        @card_src = "icon_discover.png"
      end
      @order = Order.new
    end
  end

  #注文の登録
  def create
    unless user_signed_in?
      redirect_to cart_path(@current_cart), notice: "ログインしてください"
      return
    end
    @purchaseByCard = Payjp::Charge.create(
    amount: @cart.total_price,
    customer: @card.customer_id,
    currency: 'jpy',
    card: params['payjpToken']
    )
    @order = Order.new(order_params)
    @order.add_items(current_cart)
    if @purchaseByCard.save && @order.save!
      OrderDetail.create_items(@order, @cart.line_items)
      flash[:notice] = '注文が完了しました。マイページにて注文履歴の確認ができます。'
      redirect_to root_path
    else
      flash[:alert] = "注文の登録ができませんでした"
      redirect_to action: :new
    end
  end

  private
  def order_params
    params.permit(:user_id, :address_id, :card_id, :quantity, :price)
  end

  def set_user
    @user = current_user
  end

  def set_cart
    @cart = current_cart
  end

  def set_card
    @card = Card.find_by(user_id: current_user.id)
  end

  def set_address
    @address = Address.find_by(user_id: current_user.id)
  end

  def user_signed_in
    unless user_signed_in?
      redirect_to cart_path(@cart.id), alert: "レジに進むにはログインが必要です"
    end
  end
end

newアクションにおいては、購入確認ページの形態をとっています。
ユーザーが登録していないと、そもそも購入確認ページに訪問できない仕様になっております。
かつ、ユーザーがマイページでカードを登録できるように仕様です。

購入確認ページで初めてカード登録を行う仕様にするのであれば、前回の記事で作成したCardコントローラーのnewアクションのインスタンス変数を持ってこればいいかもですね。

createアクションでは以下の内容が必要です。
見易いように内容抽出しております。

def create
  @purchaseByCard = Payjp::Charge.create(
  amount: @cart.total_price,
  customer: @card.customer_id,
  currency: 'jpy',
  card: params['payjpToken']
  )
  if @purchaseByCard.save
    flash[:notice] = '注文が完了しました。'
    redirect_to root_path
  else
    flash[:alert] = "注文の登録ができませんでした"
    redirect_to action: :new
  end
end

上記で購入が完了すると以下のようにPAY.JPにも売上が登録されています。
alt

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

PAY.JPでクレジットカードの登録・削除機能を実装する

はじめに

個人アプリにて、クレジットカード決済を行うため、PAY.JPを導入しました。
導入において、少しつまずいた部分もあったので、備忘録として記載しています。

前提条件

  • Rails 5.2.4.2
  • Ruby 2.5.1
  • devise使用
  • haml使用
  • VSCode使用

手順

  1. PAY.JPの登録
  2. アプリにPAY.JPを導入・下準備
  3. モデルの作成
  4. マイグレーションファイル
  5. コントローラーの作成
  6. gonの導入
  7. JSファイルの作成

PAY.JPの登録

以下のURLより登録します。
PAY.JP

41b709e395800ed89184beb6bbfee74f.png

登録が完了すると、上記のような画面に移ります。(使用したため、売上がたっております。)
最初はテストモードになっております。
実取引を行うためには,申請を行い、ライブモードに切り替える必要があります。
今回は個人アプリでの使用であり、商用目的ではないので、テストモードを使用しています。

メニューバーにある"API"の内容を使います。
996ed64b5c8b7f2c66a991720dc78f83.png

自身のアプリにはこのテスト秘密鍵とテスト公開鍵を使用していきます。

アプリにPAY.JPを導入・下準備

Gemfileに以下を追記

gem 'payjp'

ここから、カード情報の入力フォームを作成準備をしていくのですが、やり方としては2つあります。

カード情報のトークン化について

  1. チェックアウト
  2. カスタム

チェックアウトでは

<form action="/pay" method="post">
  <script src="https://checkout.pay.jp/" class="payjp-button" data-key=""></script>
</form>

上記を記述することでPAYJP側が用意したフォームを使用できます。

今回は自分はフォームを自身でカスタムしたかったので、以下のようにやっていきました。

application.html.hamlのhead部分に以下を追記します。

 %script{src: "https://js.pay.jp", type: "text/javascript"}

公式では上記に加えて、公開鍵の記述もしておりますが、自分は別で記載しました(後述)。

続いて、PAYJPの公開鍵と秘密鍵をcredentials.yml.encに記述していきます。

※Rails5.2系から導入されたEncrypted Credentialsという機能を利用します。これは、APIキーなどセキュリティ的に外部に公開してはいけない値の管理をシンプルに行うことができる機能です。

alt

当該ファイルを編集するには少し準備が必要です。
まずは、ターミナルからVSCodeを起動できるよう設定を行います。
VSCodeで、「Command + Shift + P」を同時に押してコマンドパレットを開きます。
続いて、「shell」と入力しましょう。
メニューに、「PATH内に'code'コマンドをインストールします」という項目が表示されるので、それをクリックします。
この操作を行うことで、ターミナルから「code」と打つことでVSCodeを起動できるようになりました。

続いて以下にてファイルの中身を編集していきます。

$ pwd
# 自身のアプリディレクトリにいることを確認
$ EDITOR='code --wait' rails credentials:edit

しばらくすると、以下のようにファイルが開きます。
alt

ここに以下のように記述していきます。
ネストしていることに注意してください。

payjp:
  PAYJP_SECRET_KEY: sk_test_...
  PAYJP_PUBLIC_KEY: pk_test_...

ご自身の鍵を記述して、保存してからファイルを閉じると
”New credentials encrypted and saved.”
とターミナルに出力されるので完了です。

なお、保存できているかどうかは以下コマンドで確認できます。

$ rails c
$ Rails.application.credentials[:payjp][:PAYJP_SECRET_KEY]
$ Rails.application.credentials[:payjp][:PAYJP_PUBLIC_KEY]

モデルの作成

今回はCardモデルと命名してやっていきます。
以下のように作成しました。

class Card < ApplicationRecord
  belongs_to :user
  has_one :order, dependent: :nullify

  require 'payjp'
  Payjp.api_key = Rails.application.credentials.dig(:payjp, :PAYJP_SECRET_KEY)

  def self.create_card_to_payjp(params)
    # トークンを作成 
    token = Payjp::Token.create({
      card: {
        number:     params['number'],
        cvc:        params['cvc'],
        exp_month:  params['valid_month'],
        exp_year:   params['valid_year']
      }},
      {'X-Payjp-Direct-Token-Generate': 'true'} 
    )
    # 上記で作成したトークンをもとに顧客情報を作成
    Payjp::Customer.create(card: token.id)
  end

個人アプリではECサイトを作成していたため、アソシエーションはUserとOrderとしています。

ここでカード情報のトークン化を行っております。

テーブルの作成

以下の内容でテーブルを作成しました。

class CreateCards < ActiveRecord::Migration[5.2]
  def change
    create_table :cards do |t|
      t.string :customer_id, null: false
      t.string :card_id, null: false
      t.references :user, null: false, foreign_key: true
      t.timestamps
    end
  end
end

自分は最初知らなかったですが、customer_idとcard_idはマストでカラムとして作成する必要があります。
テーブルには以下のようにレコードが登録されていきます。

コントローラーの作成

今回は4つのアクションで以下のようにコントローラーを構成しました。

class CardsController < ApplicationController
  before_action :set_card, only: [:new, :show, :destroy]
  before_action :set_payjpSecretKey, except: :new
  before_action :set_cart
  before_action :set_user

  require "payjp"

  def new
    redirect_to action: :show, id: current_user.id if @card.present?
    @card = Card.new 
    gon.payjpPublicKey = Rails.application.credentials[:payjp][:PAYJP_PUBLIC_KEY]
  end

  def create
    render action: :new if params['payjpToken'].blank?
    customer = Payjp::Customer.create(
      card: params['payjpToken']
    )
    @card = Card.new(
      card_id: customer.default_card,
      user_id: current_user.id,
      customer_id: customer.id
    )
    if @card.save
      flash[:notice] = 'クレジットカードの登録が完了しました'
      redirect_to action: :show, id: current_user.id
    else
      flash[:alert] = 'クレジットカード登録に失敗しました'
      redirect_to action: :new
    end
  end

   def show
    redirect_to action: :new if @card.blank?
    customer = Payjp::Customer.retrieve(@card.customer_id)
    default_card_information = customer.cards.retrieve(@card.card_id)
    @card_info = customer.cards.retrieve(@card.card_id)
    @exp_month = default_card_information.exp_month.to_s
    @exp_year = default_card_information.exp_year.to_s.slice(2,3)
    customer_card = customer.cards.retrieve(@card.card_id)
    @card_brand = customer_card.brand
    case @card_brand
    when "Visa"
      @card_src = "icon_visa.png"
    when "JCB"
      @card_src = "icon_jcb.png"
    when "MasterCard"
      @card_src = "icon_mastercard.png"
    when "American Express"
      @card_src = "icon_amex.png"
    when "Diners Club"
      @card_src = "icon_diners.png"
    when "Discover"
      @card_src = "icon_discover.png"
    end
  end

  def destroy
    customer = Payjp::Customer.retrieve(@card.customer_id)
    @card.destroy
    customer.delete
    flash[:notice] = 'クレジットカードが削除されました'
    redirect_to controller: :users, action: :show, id: current_user.id
  end

  private
  def set_card
    @card = Card.where(user_id: current_user.id).first
  end

  def set_payjpSecretKey
    Payjp.api_key = Rails.application.credentials[:payjp][:PAYJP_SECRET_KEY]
  end

  def set_cart
    @cart = current_cart
  end

  def set_user
    @user = current_user
  end
end

また、PAYJPの公開鍵を後述するJSファイルにベタ書きすることを避けるため、"gon"というGemを導入しました。
JSファイルでrubyの変数を使用したい時に使うという認識です。

gonの導入

Gemfileに以下を追記

gem 'gon'

application.html.hamlのhead部分に以下を追記します。

= include_gon(init: true)

これで準備はOKです。

JSファイルの作成

ここから、コントローラーのnewアクションで呼び出すnew.html.hamlと連動するJSファイルを作成します。
まずはnew.html.hamlを記載します。
※フォームに入力するカード番号等についてですが、以下の公式ページのものを使用してください。
APIで使用するテストカードはこちらです。

.cardNew
  .title 
    クレジットカード情報入力
  .cardForm
    = form_with model: @card, id: "form" do |f|
      .cardForm__number
        = f.label :カード番号, class: "cardForm__number__title"
        %span.must_check 必須
      .cardForm__field
        = f.text_field :card_number, id: "card_number", placeholder: "半角数字のみ", class: "form-group__input", maxlength: 16
      .cardForm__image
        = image_tag(image_path('cards/icon_visa.png'), class: 'visa', width: 58, height: 28)
        = image_tag(image_path('cards/icon_mastercard.png'), class: 'master', width: 47, height: 36)
        = image_tag(image_path('cards/icon_jcb.png'), class: 'jcb', width: 40, height: 30)
        = image_tag(image_path('cards/icon_amex.png'), class: 'amex', width: 40, height: 30)
        = image_tag(image_path('cards/icon_diners.png'), class: 'diners', width: 45, height: 32)
        = image_tag(image_path('cards/icon_discover.png'), class: 'discover', width: 47, height: 30)
      .cardForm__expirationdate
        .cardForm__expirationdate__details
          = f.label :有効期限
          %span.must_check 必須
          %br
        .cardForm__expirationdate__choice
          .cardForm__expirationdate__choice__month
            = f.select :expiration_month, options_for_select(["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]), {}, {id: "exp_month", name: "exp_month", type: "text"}
            = f.label :, class: "cardForm__expirationdate__choice__month__label"
          .cardForm__expirationdate__choice__year
            = f.select :expiration_year, options_for_select((2020..2030)), {}, {id: "exp_year", name: "exp_year", type: "text"}
            = f.label :, class: "cardForm__expirationdate__choice__year__label"
      .cardForm__securitycode
        .cardForm__securitycode__details
          .cardForm__securitycode__details__title 
            = f.label :セキュリティコード, class: "label"
            %span.must_check 必須
          .cardForm__securitycode__details__field
            = f.text_field :cvc,  id: "cvc", class: "cvc", placeholder: "カード背面3~4桁の番号", maxlength: "4"
          .cardForm__securitycode__details__hatena
            = link_to "カード背面の番号とは?", "#", class: "cardForm__securitycode__details__hatena__link"
      #card_token
        = f.submit "登録する", id: "token_submit", url: cards_path, method: :post

続いて、上記viewファイルに対応するJSファイルを作成します。

$(document).on('turbolinks:load', function() {
  $(function() {
    Payjp.setPublicKey(gon.payjpPublicKey);
    $("#token_submit").on('click', function(e){
      e.preventDefault();
      let card = {
          number: $('#card_number').val(),
          cvc:$('#cvc').val(),
          exp_month: $('#exp_month').val(),
          exp_year: $('#exp_year').val()
      };

      Payjp.createToken(card, function(status, response) {
        if (response.error) {
          $("#token_submit").prop('disabled', false);
          alert("カード情報が正しくありません。");
        }
        else {
          $("#card_number").removeAttr("name");
          $("#cvc").removeAttr("name");
          $("#exp_month").removeAttr("name");
          $("#exp_year").removeAttr("name");

          let token = response.id;
          $("#form").append(`<input type="hidden" name="payjpToken" value=${token}>`);
          $("#form").get(0).submit();
          alert("登録が完了しました");
        }
      });
    });
  });
});

viewファイルで登録ボタン( = f.submit "登録する", id: "token_submit")を押すと、JSが発火します。
トークン作成は Payjp.createToken というメソッドで行います。

上記までで、カードの登録までは完了です。

最後に削除機能について簡単に記載しておきます。
自分はshowアクションで呼び出すshow.html.hamlに削除ボタンを以下のように設置しました。

.payment
  .payment__content
    .payment__content__title
      カード情報
    .payment__content__box
      .payment__content__box__cardImage
        = image_tag "cards/#{@card_src}", width: 40, height: 28
      .payment__content__box__details
        .payment__content__box__details__cardNumber
          = "カード番号:**** **** **** " + @card_info.last4
        .payment__content____boxdetails__cardMMYY
          = "有効期限:" + @exp_month + " / " + @exp_year
  .payment__cardDelete
    = button_to "削除する", card_path(@card.id), {method: :delete, id: 'charge-form', name: "inputForm", class: "payment__cardDelete__deleteBtn"}

次回は、登録できたカードでの決済機能を記載します。

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

Rails6+Reactで付箋アプリっぽいページを作ってみた。5(react-contenteditable導入編)

記事について

前回まででUIをそれっぽくしてみましたが、そろそろ付箋の中身を書き換えたくなってきました。
ということで、やってみます。

関連する記事

今までの記事です。
その1(環境構築〜モデル作成編)
その2(API作成編)
その3(UI作成編1)
その4(UI作成編2)
その5(react-contenteditable導入編)
おまけ(モデルのテスト編)

divのまま編集してみたい。

またしても手段を目的として面倒臭いことになるパターンです。
これまで、タスクのタイトルや説明などをdivタグで作ってきてしまったので、そのまま編集可能にできたら修正が少なくなって楽かもしれない?

divタグを編集可能にするにはcontentEditable=trueという属性を使います。
参考(MDN Web Docs - contentEditable)

なんだ、簡単そうだ。
早速、以前作ったSticky.jsのタイトル表示部分で試してみます。

app/javascript/components/Sticky.js(変更箇所のみ)
// contentEditable="true"を追加
<div className="TaskTitle" contentEditable="true">{this.props.task.title}</div>

実際にブラウザで表示するとどうなるか!?

おやおや???
スクリーンショット 2020-05-23 20.26.49.png

Reactから「contentEditableはあなたの責任で使いなさいよ」という警告メッセージが出ていました。

はーい、がんばりまーす。
って言うほど頑張れないので、メッセージ出さない方法ないかなぁ?
と調べた結果、react-contenteditableというものを見つけたので、使ってみたいと思います。

react-contenteditableの追加

以下のとおり、yarnで追加できました。

shell
bundle exec yarn add react-contenteditable

react-contenteditableを使ってみる。

インストールができたので、ソースコードに追加してみます。
対象は、先ほどのSticky.jsです。

ソースコードの変更

app/javascript/components/Sticky.js
import React from "react"
import PropTypes from "prop-types"

// ContentEditableコンポーネントをimportします。
import ContentEditable from "react-contenteditable"

class Sticky extends React.Component {
  // (中略)
  // レンダラです。
  render () {
    return (
          { /* 先ほどのdivをContentEditableに変更します。 */ }
          { /* 表示内容はhtml=で指定します。 */ }
          <ContentEditable className="TaskTitle" html={this.props.task.title} />
          { /* 以後、色々省略 */ }

    );
  }
}

export default Sticky

コメントしてあるとおり、最初のimport文でContentEditableをインポートし、render()内で編集可能にしたい要素をContentEditableに置き換えます。

なお、propsとして指定できるのは、以下のもののようです。

お名前 ご説明
innerRef refに指定する値(あとで値の取得等に使うためのものです。) Object または Function
html 要素の値です。名前の通りhtmlが利用できます。必須要素です。(null値はエラーになる。。) String
disabled trueにすると編集不可にできます。 Boolean
onChange innerHTMLの内容が変更されたときに呼び出されるハンドラを指定します。 Function
onBlur フォーカスが離れた時のハンドラです。 Function
onKeyUp キーを離した際のハンドラ Function
onKeyDown キーを押した際のハンドラ Function
className そのまんまクラス名です。 String
style スタイルも指定できるんですね。 Object

動作確認

ソースコードを直したら、早速反映されたか確認してみます。
スクリーンショット 2020-05-24 10.33.18.png

おぉ!編集できる!!
ならば、この値を取得して保存する流れを作ってみます。

CotentEditableで作成した要素から値を取得する。

reactで編集可能な要素の値を取得するには、onChangeを使ってstateに値を保存するなどの方法が考えられますが、ここではrefを使った方法を使ってみます。
ContentEditableコンポーネントでは、innerRefというプロパティを使うことで実現します。

具体的には以下のようなソースコードとなります。(変更箇所に1〜5の番号をふってあります。)

app/javascript/components/Sticky.js
import React from "react"
import PropTypes from "prop-types"

// ContentEditableコンポーネントをimportします。
import ContentEditable from "react-contenteditable"

class Sticky extends React.Component {
  // コンストラクタです。
  constructor(props) {
    // おまじないです。
    super(props);

    /*
    ((中略))
    */

    // createRefで要素参照用のインスタンス変数を作ります。 -- 1
    this.taskTitle = React.createRef();

    // 保存ボタンクリック時のハンドラをバインド -- 2
    this.onSaveButtonClick = this.onSaveButtonClick.bind(this);

  }

  /*
   ((さらに中略))
  */

  // 保存ボタンクリックイベントハンドラ -- 3
  onSaveButtonClick(event){
    // 以下のように"current.textContent"で要素の値が取得できます。
    // ここでは、とりあえず取得した値をconsole.logに吐いてみます。
    console.log(this.taskTitle.current.textContent);
  }

  // レンダラです。
  render () {
    return (
          { /* innerRef={this.taskTitle}という記述を追加しました。 -- 4 */ }
          <ContentEditable className="TaskTitle" html={this.props.task.title} innerRef={this.taskTitle} />
          { /* 保存ボタンとして使う要素を追加します。 -- 5 */ }
          <div className="TaskFooter">
            <div className="TaskUpdateButton" onClick={this.onUpdateButtonClick} >save</div>
          </div>
          { /* 以後、色々省略 */ }

    );
  }
}

表示する要素が増えてしまったので、ついでに、スタイルシートも追加しておきます。

app/assets/stylesheets/white_board.scss
// 付箋のフッター
div.TaskFooter {
  grid-row: 8;
  grid-column: 1 / 3;
  display: flex;
  justify-content: space-between;
}

// 保存ボタン
div.TaskUpdateButton {
  color: #0000FF;
  font-weigt: bold;
  font-size: 10px;
  text-align: right;
}

試してみます。
スクリーンショット 2020-05-24 15.13.44.png
タイトルを編集(hogegeってしたり、updatedってしたり)して、"save"を押してみると。
おぉ、ログが出た。
スクリーンショット 2020-05-24 15.04.09.png

実際にDBに保存させてみる。

編集した値が取得できることが分かったので、実際にDBに反映する流れを作ってみます。

シーケンス図で考える。

Railsで作ってるAPIにて

APIの処理イメージ
@task = Task.find(params[:task][:id]);
@task.update(params[:task]);

みたいな感じになれば良いわけですが。
誰がこのAPIを呼ぶ?
みたいなことを考えねばなりません。

で、以前作っているWhiteBoard.jsですでにAPI呼び出しを行っているので、
こいつにまとめてしまおう。
ちょっと複雑になるので、シーケンス図を起こしてみました。
スクリーンショット 2020-05-24 16.28.50.png

図にするとわかりやすいですね。
最終的にWhiteBoardのonTaskSave()が呼ばれるようにpropsを引き継いでいけば実装できそうです。

APIを用意してみる。

コントローラの準備

まずは、コントローラの準備をします。

app/controllers/api/tasks_controller.rb
  # タスク情報更新処理
  def update
    # エラーメッセージリストを初期化
    @errmsgs = [];

    # 無害化したパラメータを取得
    updparam = update_params();

    begin
      # タスク情報を取得
      @task = Task.find(updparam[:id]);

      # アップデート実行
      if ! @task.update(updparam) then
        # エラー時はエラーメッセージリストにエラーメッセージを追加しておいて
        @task.errors.each do | key |
          @task.errors[key].each do | message |
            @errmsgs.push(key.to_s + ":" + message);
          end
        end
        # エラー表示用レンダラーを指定します。
        render :show_error
      end

    rescue => ex
      # こちらも、例外情報をメッセージリストに追加して
      @errmsgs.push(ex.to_s);
      # エラー表示用レンダラーを指定します。
      render :show_error
    end

    # 更新がうまくいったら、json形式で更新結果をお知らせするので、
    # app/views/api/tasks/update.json.jbuilderを用意しておきます。
    # Railsのデフォルト動作では、コントローラと同じ名前のviewを表示しようとするので、"render :update"などと書く必要はありません。
    # 必要はありませんが、知らないと分からないですよね。。

  end

  private
    # update時パラメータの取得
    def update_params()
      return params.require(:task).permit(:id, :title, :description, :due_date, :user_id);
    end

Viewを作る

次にviewを用意します。

app/views/api/tasks/update.json.jbuilder
# 更新後のデータを返せば良いかなぁ。と。
json.task do
  json.id(@task.id);
  json.title(@task.title);
  json.description(@task.description);
  json.due_date(@task.due_date.strftime("%Y-%m-%d"));
  json.user_id(@task.user.id);
end

エラー時はこんな感じでいいかしらね。

app/views/api/tasks/show_error.json.jbuilder
# エラーメッセージをJSON形式で返します。
json.errors @errmsgs do | msg |
  json.message(msg);
end

ルーティングを追加する。

コントローラにアクションを追加したので、ルーティングを追加します。
updateだから、putで。

config/route.rb
namespace :api do
  put 'tasks/update'
end

とりあえずテスト

ブラウザでいちいち動作確認するのめんどいので、テストコードを追加しときます。
エラー系は、とりあえず後で。。

test/controllers/api/tasks_controller_test.rb
  test "should success to update" do
    task2 = tasks(:task2);
    due_date = Date.new(1894,2,11);

    put(api_tasks_update_url(:json), params: { task: { id: task2.id, title: "title-updated", description: "description-updated", due_date: due_date }});

    assert_response :success

    json_data = ActiveSupport::JSON.decode(@response.body);
    assert_equal(task2.id, json_data['task']['id']);
    assert_equal("title-updated", json_data['task']['title']);
    assert_equal("description-updated", json_data['task']['description']);
    assert_equal(due_date, Date.parse(json_data['task']['due_date']));
  end

フロントエンド側に処理を追加していく

Rails側の修正

Reactで作ったコンポーネントから、追加したアクション(今回はtasks/update)を呼び出してもらうため、react_compnent()呼び出し時の引数を修正します。

app/views/white_board/main.html.erb
<%
# update_task_url: api_tasks_update_url(:json)
# を追加しました。
%>
<%= react_component('WhiteBoard', { 
  title: 'You can let others do your task', 
  user_tasks_url: api_users_user_task_list_url(:json), 
  switch_user_url: api_tasks_switch_user_url(:json),
  update_task_url: api_tasks_update_url(:json),
  secure_token: form_authenticity_token
}) %>

WhiteBoard.jsの修正

ここには、updateを呼び出すための処理の追加と、自身の"onTaskSave()"をコールバックしてもらうための処理を追加します。

app/javascript/components/WhiteBoard.js
import React from "react"
import PropTypes from "prop-types"

// 自作コンポーネントはこのように呼び出せます。
import UserBox from "./UserBox"

// WhiteBoardコンポーネントの定義
class WhiteBoard extends React.Component {
  // コンストラクタ
  constructor(props) {
    // おまじない
    super(props);

    // いくつか省略

    // イベントハンドラのバインド
    this.onTaskSave = this.onTaskSave.bind(this); // タスク更新時の処理
  }

  // またまた省略

  // タスク更新イベント処理
  onTaskSave(task) {
    // タスク更新処理(tasks/update)を呼び出します。
    fetch(this.props.update_task_url, {
      method: "PUT",
      headers: {
        "Content-Type": "application/json; charset=utf-8",
        "X-CSRF-Token": this.props.secure_token
      },
      body: JSON.stringify(task)
    })
    .then(response => response.json())
    .then(json => {
      /* 実は返されたデータの使い道を見失った。。 */
      console.log(JSON.stringify(json));
    })
    .catch(error_response => console.log(error_response));
  }

  // レンダリング
  // UserBoxのpropsにonTaskSaveを追加しました。
  render () {
    return (
      <React.Fragment>
        <div id="WhiteBoardFlame">
          <div id="WhiteBoardTitle">{this.props.title}</div>
          <div id="AddUserButton" onClick={this.onAddUserClick} >+Add User</div>
          { this.state.show_add_user && <UserForm onAddButtonClick={this.ExecuteAddUser} onCancelButtonClick={this.CancelAddUser} /> }
          <div id="WhiteBoard">
            { ! this.state.loading && this.state.users.map((user) => <UserBox user={user} key={user.id} dropHandlerRegister={this.dropHandlerRegister} onTaskDrop={this.onTaskDrop} onTaskSave={this.onTaskSave} /> )}
          </div>
        </div>
      </React.Fragment>
    );
  }
}

UserBox.jsの修正

ここにも、onTaskSaveというメソッドを追加してあげます。

app/javascript/components/UserBox.js
// ユーザ毎の箱を表示します。
class UserBox extends React.Component {

  // コンストラクタです。
  constructor(props) {
    // おまじない
    super(props);

    // 色々省略
    // イベントハンドラのバインド
    this.onTaskSave = this.onTaskSave.bind(this); // <-- onTaskSaveメソッドのバインドを追加します。

  }

  // タスク更新処理
  onTaskSave(task) {
    // stateに保持しているタスクデータを更新
    var tasks = this.state.tasks;
    tasks[task.id] = task;

    // さらにコールバック(この中でDB反映が行われます。)
    this.props.onTaskSave(task);

    // stateを更新
    this.setState({tasks: tasks});

  }

  // レンダリング
  render () {
    return (
      <React.Fragment>
        <div id={"user-" + this.props.user.id} className="UserBox" onDrop={this.onDrop} onDragOver={this.preventDefault} >
          <div className="UserName">{this.props.user.name}</div>
          <div className="TaskArea">
            {
              Object.keys(this.state.tasks).map(
               (key) => <Sticky 
                          user_id={this.props.user.id} 
                          task={ this.state.tasks[key] } 
                          key={ key }
                          onTaskSave={this.onTaskSave} /> 
              ) 
            }
          </div>
        </div>
      </React.Fragment>
    );
  }

Sticky.jsの修正

やっとここまできましたー。
最初に書いたonSaveButtonClickの中身をちゃんと書いてみます。

app/javascript/components/Sticky.js
  // 保存ボタンクリックイベントハンドラ
  onSaveButtonClick(event){
    // 以下のように"current.textContent"で取得した値を使って、
    // UserBoxのonTaskSave()を呼び出して(コールバックして)あげます。
    var task = this.props.task;
    task.title = this.taskTitle.current.textContent;
    this.props.onTaskSave(task);

    // title以外の値も同じように更新できます。

  }

テストじゃぁ!!

ここでも、しつこくテストを追加していきます。
UI側は、systemテストでやるのが楽です。

test/system/whiteboards_test.rb
  test "sticky is able to update" do
    # fixutreで登録したデータを取得しておきます。
    task1 = tasks(:task1);

    # divのidを設定します。
    task1_id = "task-" + task1.id.to_s;

    # white_board/mainを開く。
    visit white_board_main_url;

    # task1の要素を取得します。
    div_task1 = find(id: task1_id);

    # title要素を取得します。
    div_title1 = div_task1.find("div", class: "TaskTitle");

    # title要素の中身を書き換えます。(text=でできるかと思ったら、setでした。)
    div_title1.set("task1_updated!!");

    # saveボタンを押しちゃいます。
    div_task1.find("div", class: "TaskUpdateButton").click();

    # 表示されている値が更新されていますように。
    assert_equal("task1_updated!!", div_title1.text);

    # DBに反映されていますように!!
    task1_updated = Task.find(task1.id);
    assert_equal("task1_updated!!", task1_updated.title); 

  end

まとめ

  1. Reactでレンダリングした要素でcontentEditable=trueを使いたい場合は、react-contenteditableを使うと良い。
  2. しかし、今回の場合はinputタグでスタイル指定してそれっぽく見せたほうが良かったのではないかと。
  3. ContentEditableで描画した部分も普通にCapybaraでテストできる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsのformについて

最初に

formの使い方が曖昧だったので勉強致しました。
至る所で紹介されていることで、個人的なアウトプットですので、ご了承ください。

formの使い分けについて

formは3種類が存在していますが、使い分けが曖昧でした。

・form_tag
モデルがない時に使用します。
使用例で言うと検索フォームです。

・form_for
データのモデルがある時に使用します。
使用例で言うと投稿フォームです。

・form_with
記述次第で「form_tag」と「form_for」の両方の使い方ができます。
現在のバージョンで推奨されている書き方です。

form_tagの基本構文

form_tagを使うときは基本的にアクション属性とHTTPメソッドを指定します。

<div>
  <%= form_tag (action: :index, method: :post) do %>
    <input type="text" id="name">
    <input type="submit">
  <% end %>
</div>

form_forの基本構文

form_forはインスタンス変数を引数として渡します。
さらにHTMLのinputタグは使わず、フォームのビルドヘルパー(form.hoge)を使用します。

controller
def new
  @user = User.new
end
view
<%= form_for(@user) do |form| %>
  <%= form.text_field :name %>
  <%= form.submit %>
<% end %>

form_withの基本構文

・form_tagの様な書き方

<%= form_with url: users_path do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

・form_forの様な書き方
form_withはデフォルトでAjax

<%= form_with model: @user, local: true do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

最後に

まだまだ覚えないといけない書き方がたくさんありますが、少しずつ覚えていきます。

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

Rails Console内で関数を定義したい

ちょっとしたことなんですが。

pryにコードをコピペするためにしておきたい設定#方法1:一つの式にしてしまう

括弧で括ると改行は無視されるようになります。
無視されるというか、閉じるまで式の途中とみなされます。

よく使うパターンとしては、Consoleが立ち上がってる間だけの関数を定義したいとき。
これはメソッドの一部を軽くテストしたいな〜ってときにやってます。

Console内で

[1] pry(main)> (
[1] pry(main)*   def hoge
[1] pry(main)*     return 'hoge' # 処理内容
[1] pry(main)*   end  
[1] pry(main)* )  
=> :hoge


[2] pry(main)> hoge # 呼び出し
=> "hoge"

という感じです。

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

has_secure_passwordとは

簡単に言うと?

安全性の高いパスワード機能を簡単に実装できるメソッド。
間違ったパスワードを入力させないように、二回パスワードを入力させるフォーム画面を作成するときに使える。

何ができる?

has_secure_passwordを使うと以下の機能が使える様になる。

  • ハッシュ化したパスワードをデータベース内のpassword_digestという属性に保存できるようになる。
  • 2つのペアの仮想的な属性(passwordとpassword_confirmation)が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される 。
  • 認証のために使うauthenticateメソッドが使えるようになる。authenticateメソッドとは、引数で受けとった文字列がオブジェクト内のpassword_digestと一致するか調べるためのメソッド。一致した時はそのオブジェクト自身を返し、不一致だとfalseを返す。

どうやって使う?

  1. テーブルのカラムにpassword_digestを導入する
  2. Gemfileのbcryptをコメントアウトしてbundle install(Genfile内にコメントとして既に記述されているのでコメントアウトする)
  3. モデルにhas_secure_passwordを追加する
  4. passwordpassword_confirmationを使ってフォーム画面を作成する。
$ bin/rails g model user name:string password_digest:string
app/model/user.rb
class User < ApplicationRecord

  has_secure_password

end
gem 'bcrypt', '~> 3.1.7'

$ bundle install
app/views/users/form.html.slim
= form_with model: user, local: true do |f|
  .form-group
    = f.label name, '名前'
    = f.text_field :name, class: 'form-control'
  .form-group
    = f.label :password, 'パスワード'
    = f.password_field :password, class: 'form-control'
  .form-gorup
    = f.label :password_cocfirmation, 'パスワード(確認)'
    = f.password_field :password_cocfirmation, class: 'form-control'
  = f.submit '登録する', class: 'btn btn-primary'
引用

has_secure_passwordのvalidationsをカスタマイズする
Railsのhas_secure_passwordとは
BCryptのすすめ

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

【gem:devise の日本語化】

gem:devise日本語化の流れ

(deviseインストール後の流れ)

  1. gemのインストール
    ・gem 'devise-i18n'
    ・gem 'devise-i18n-views'

  2. コマンドで日本語翻訳ファイルを生成

  3. devise.views.ja.yml を編集

1. gemのインストール

gem 'devise-i18n'
gem 'devise-i18n-views'

gemfileに記述して“$ bundle install”します

2. コマンドで日本語翻訳ファイルを生成

$ rails g devise:views:locale ja

コマンドを実行すると日本語翻訳ファイルであるconfig/localesdevise.views.ja.yml
が生成されます。

3. devise.views.ja.yml を編集

生成されたconfig/localesdevise.views.ja.ymlを編集していきます。

config/localesdevise.views.ja.yml
ja:
  activerecord:
    errors:
      models:
        user:
          attributes:
            email:
              taken: "は既に使用されています。"
              blank: "が入力されていません。"
              too_short: "は%{count}文字以上に設定して下さい。"
              too_long: "は%{count}文字以下に設定して下さい。"
              invalid: "は有効でありません。"
            password:
              taken: "は既に使用されています。"
              blank: "が入力されていません。"
              too_short: "は%{count}文字以上に設定して下さい。"
              too_long: "は%{count}文字以下に設定して下さい。"
              invalid: "は有効でありません。"
              confirmation: "が内容とあっていません。"
    attributes:
      user:
        current_password: "現在のパスワード"
        name: 名前
        email: "メールアドレス"
        password: "パスワード"
        password_confirmation: "確認用パスワード"
        remember_me: "次回から自動的にログイン"
    models:
      user: "ユーザ"
  devise:
    confirmations:
      new:
        resend_confirmation_instructions: "アカウント確認メール再送"
    mailer:
      confirmation_instructions:
        action: "アカウント確認"
        greeting: "ようこそ、%{recipient}さん!"
        instruction: "次のリンクでメールアドレスの確認が完了します:"
      reset_password_instructions:
        action: "パスワード変更"
        greeting: "こんにちは、%{recipient}さん!"
        instruction: "誰かがパスワードの再設定を希望しました。次のリンクでパスワードの再設定が出来ます。"
        instruction_2: "あなたが希望したのではないのなら、このメールは無視してください。"
        instruction_3: "上のリンクにアクセスして新しいパスワードを設定するまで、パスワードは変更されません。"
      unlock_instructions:
        action: "アカウントのロック解除"
        greeting: "こんにちは、%{recipient}さん!"
        instruction: "アカウントのロックを解除するには下のリンクをクリックしてください。"
        message: "ログイン失敗が繰り返されたため、アカウントはロックされています。"
    passwords:
      edit:
        change_my_password: "パスワードを変更する"
        change_your_password: "パスワードを変更"
        confirm_new_password: "確認用新しいパスワード"
        new_password: "新しいパスワード"
      new:
        forgot_your_password: "パスワードを忘れましたか?"
        send_me_reset_password_instructions: "パスワードの再設定方法を送信する"
    registrations:
      edit:
        are_you_sure: "本当に良いですか?"
        cancel_my_account: "アカウント削除"
        currently_waiting_confirmation_for_email: "%{email} の確認待ち"
        leave_blank_if_you_don_t_want_to_change_it: "空欄のままなら変更しません"
        title: "%{resource}編集"
        unhappy: "気に入りません"
        update: "更新"
        we_need_your_current_password_to_confirm_your_changes: "変更を反映するには現在のパスワードを入力してください"
      new:
        sign_up: "アカウント登録"
    sessions:
      new:
        sign_in: "ログイン"
    shared:
      links:
        back: "戻る"
        didn_t_receive_confirmation_instructions: "アカウント確認のメールを受け取っていませんか?"
        didn_t_receive_unlock_instructions: "アカウントの凍結解除方法のメールを受け取っていませんか?"
        forgot_your_password: "パスワードを忘れましたか?"
        sign_in: "ログイン"
        sign_in_with_provider: "%{provider}でログイン"
        sign_up: "アカウント登録"
    unlocks:
      new:
        resend_unlock_instructions: "アカウントの凍結解除方法を再送する"

これで日本語化ができると思います!

おわりに

自分メモように書いてみました!
よかったら参考にしてみてください。

参照
https://remonote.jp/rails-devise-i18n-locale-ja

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

【Rails】DateとTimeの比較をする際の注意点

ことの起こりは深夜0時も回った頃。RSpecを走らせたところ、実装と関係のないところが落ちる。

調べてみたところ、どうやろここで本当はreturnしないといけないのにreturnしていないようです。

    return if last_tweet.tweeted_at > DateTime.yesterday

というわけで、デバッグしてみました。

デバッグ

[2] pry(#<RegisteredTag>)> DateTime.yesterday
=> Thu, 21 May 2020

うんうん、合ってる。

[3] pry(#<RegisteredTag>)> DateTime.yesterday.to_time
=> 2020-05-21 00:00:00 +0900

よかろう。

[4] pry(#<RegisteredTag>)> last_tweet.tweeted_at.to_time
=> 2020-05-21 00:30:02 +0900

そうだよね、では比較してみよう。

[6] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday.to_time
=> true

比較したらそうなるはずなんだ。では、元のコードに戻してみよう。

[5] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday
=> false

ファファファ????

ということで、改めて確認してみます。

日付の比較

[33] pry(main)> DateTime.yesterday
=> Thu, 21 May 2020
[34] pry(main)> after_9
=> Thu, 21 May 2020 09:21:38 +0900
[35] pry(main)> DateTime.yesterday < after_9
=> true
[37] pry(main)> before_9
=> Thu, 21 May 2020 08:31:48 +0900
[38] pry(main)> DateTime.yesterday < before_9
=> false

8時台 < DateTime.yesterday < 9時台
ということで、DateTime.yesterdayにはタイムゾーンが適用されていないようです。

Date.yesterdayでもやってみた

[22] pry(main)> Date.yesterday
=> Thu, 21 May 2020
[23] pry(main)> Date.yesterday < before_9
=> false
[24] pry(main)> Date.yesterday < after_9
=> true

DateTimeと同じ挙動をします。

クラスを見てみる

[31] pry(main)> Date.yesterday.class
=> Date
[32] pry(main)> DateTime.yesterday.class
=> Date

あ、DateTimeで作ってもインスタンスのクラスはDateになるんですね。
つまり、Date.yesterdayDateTime.yesterdayは同じもののようです。

結論

  • DateTimeで日付を指定すると、インスタンスはDateクラスになる。
  • DateクラスのインスタンスはDateTime/Timeクラスになった時点でタイムゾーンを持つ。
  • Dateクラスの状態ではタイムゾーンは+0000なので、DateTime/Timeクラス(RailsだとActiveSupport::TimeWithZoneクラス)と比較するときは注意する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】DateTimeで比較をする際の注意点

ことの起こりは深夜0時も回った頃。RSpecを走らせたところ、実装と関係のないところが落ちる。

調べてみたところ、どうやろここで本当はreturnしないといけないのにreturnしていないようです。

    return if last_tweet.tweeted_at > DateTime.yesterday

というわけで、デバッグしてみました。

デバッグ

[2] pry(#<RegisteredTag>)> DateTime.yesterday
=> Thu, 21 May 2020

うんうん、合ってる。

[3] pry(#<RegisteredTag>)> DateTime.yesterday.to_time
=> 2020-05-21 00:00:00 +0900

よかろう。

[4] pry(#<RegisteredTag>)> last_tweet.tweeted_at.to_time
=> 2020-05-21 00:30:02 +0900

そうだよね、では比較してみよう。

[6] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday.to_time
=> true

比較したらそうなるはずなんだ。では、元のコードに戻してみよう。

[5] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday
=> false

ファファファ????

ということで、改めて確認してみます。

日付の比較

[33] pry(main)> DateTime.yesterday
=> Thu, 21 May 2020
[34] pry(main)> after_9
=> Thu, 21 May 2020 09:21:38 +0900
[35] pry(main)> DateTime.yesterday < after_9
=> true
[37] pry(main)> before_9
=> Thu, 21 May 2020 08:31:48 +0900
[38] pry(main)> DateTime.yesterday < before_9
=> false

8時台 < DateTime.yesterday < 9時台
ということで、DateTime.yesterdayにはタイムゾーンが適用されていないようです。

Date.yesterdayでもやってみた

[22] pry(main)> Date.yesterday
=> Thu, 21 May 2020
[23] pry(main)> Date.yesterday < before_9
=> false
[24] pry(main)> Date.yesterday < after_9
=> true

DateTimeと同じ挙動をします。

クラスを見てみる

[31] pry(main)> Date.yesterday.class
=> Date
[32] pry(main)> DateTime.yesterday.class
=> Date

あ、DateTimeで作ってもインスタンスのクラスはDateになるんですね。
つまり、Date.yesterdayDateTime.yesterdayは同じもののようです。

結論

  • DateTimeで日付を指定すると、インスタンスはDateクラスになる。
  • Dateクラスはタイムゾーンの情報を持っていないため、+0000になる。
  • DateクラスはDateTime/Timeクラスになった時点でタイムゾーンを持つ。
  • DateクラスとDateTime/Timeクラス(RailsだとActiveSupport::TimeWithZoneクラス)を比較するときはDateクラスをDateTimeクラスへ変換する必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】DateTimeで日付と時間を比較をする際の注意点

ことの起こりは深夜0時も回った頃。RSpecを走らせたところ、実装と関係のないところが落ちる。

調べてみたところ、どうやろ本当はreturnしないといけないところがreturnしていないようです。
してほしい挙動としては、「最後にツイートした日時が昨日より最近のときにreturnする」というもの。

    return if last_tweet.tweeted_at > DateTime.yesterday

というわけで、調べてみました。

環境

Rails 5.2.4.2
Ruby 2.6.6

デバッグ

[2] pry(#<RegisteredTag>)> DateTime.yesterday
=> Thu, 21 May 2020

うんうん、合ってる。

[3] pry(#<RegisteredTag>)> DateTime.yesterday.to_time
=> 2020-05-21 00:00:00 +0900

よかろう。

[4] pry(#<RegisteredTag>)> last_tweet.tweeted_at.to_time
=> 2020-05-21 00:30:02 +0900

そうだよね、では比較してみよう。

[6] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday.to_time
=> true

比較したらそうなるはずなんだ。では、元のコードに戻してみよう。

[5] pry(#<RegisteredTag>)> last_tweet.tweeted_at > DateTime.yesterday
=> false

ファファファ????

ということで、改めて確認してみます。

日付の比較

[33] pry(main)> DateTime.yesterday
=> Thu, 21 May 2020
[34] pry(main)> after_9
=> Thu, 21 May 2020 09:21:38 +0900
[35] pry(main)> DateTime.yesterday < after_9
=> true
[37] pry(main)> before_9
=> Thu, 21 May 2020 08:31:48 +0900
[38] pry(main)> DateTime.yesterday < before_9
=> false

8時台 < DateTime.yesterday < 9時台
ということで、DateTime.yesterdayにはタイムゾーンが適用されていないようです。

Date.yesterdayでもやってみた

[22] pry(main)> Date.yesterday
=> Thu, 21 May 2020
[23] pry(main)> Date.yesterday < before_9
=> false
[24] pry(main)> Date.yesterday < after_9
=> true

DateTimeと同じ挙動をします。

クラスを見てみる

[31] pry(main)> Date.yesterday.class
=> Date
[32] pry(main)> DateTime.yesterday.class
=> Date

あ、DateTimeで作ってもインスタンスのクラスはDateになるんですね。
つまり、Date.yesterdayDateTime.yesterdayは同じもののようです。

結論

  • DateTimeで日付を指定すると、インスタンスはDateクラスになる。
  • Dateクラスはタイムゾーンの情報を持っていないため、+0000になる。
  • DateクラスはDateTime/Timeクラスになった時点でタイムゾーンを持つ。
  • DateクラスとDateTime/Timeクラス(RailsだとActiveSupport::TimeWithZoneクラス)を比較するときはDateクラスをDateTimeクラスへ変換する必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアルのセットアップ関連だけ要約

Railsチュートリアルをようやく一周しました。
次の段階として、Railsチュートリアルを参考にしながらオリジナルのWebサービスを作ってみるために、セットアップ関連だけまとめています。
開発環境はCloud9です。

Rails環境の構築

便利コマンド

普段からドキュメントをインストールしないように指定するコマンド(らしい)。
参考にさせていただきました。
Railsチュートリアル第1章の「$ printf "install: --no-document \nupdate: --no-document\n" >> ~/.gemrc」を分解

$ printf "install: --no-document \nupdate:  --no-document\n" >> ~/.gemrc

Railsインストール

今回、バージョンはチュートリアルのままとしたが、いずれバージョンアップもやっておかないと。。。

$ gem install rails -v 5.1.6

rails newの実行

$ rails _5.1.6_ new "タイトル"

GitHub環境の構築

gitの生成

$ cd ~/envronment/"タイトル"
$ git init
$ git add -A
$ git commit -m "Initialize repository" # コメントは定番のもの

リポジトリの作成(GitHub)

GitHub上で 'New repository'してリポジトリを作成後、以下コマンドを実行(URLはリポジトリ作成画面に記載)

$ git remote add origin "URL"
$ git push -u origin master

# uオプションをつけて、origin masterを上流ブランチとして設定
# 次回以降、git pushのみでorigin masterへのpushが可能

リポジトリを非公開としたい場合は、Settingの一番下'Danger Zone'内にあるMake privateをクリック

SSH 公開鍵の設定(GitHub)

$ cd ~/.ssh          # 鍵を入れるフォルダに移動
$ ssh-keygen -t rsa  # 鍵を生成
$ cat id_rsa.pub     # 鍵の中身を表示して手動でコピー

# cloud9ではpbcopyが使用できなかったので、手動でコピーしています。

https://github.com/settings/ssh で公開鍵を設定
以下コマンドで接続を確認し、以下コメントが出れば接続完了。

$ ssh -T git@github.com

Hi (account名)! You've successfully authenticated, but GitHub does not provide shell access.

参考にさせていただきました。
GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~

警告がでた場合の対処方法

以下のような警告が出る場合があります(僕は出ました)。
Warning: Permanently added the RSA host key for IP address 'xxx.xxx.xxx.x' to the list of known hosts.

pushができなくなるわけではないので問題はなさそうですが、Worningがでているのは気持ち悪い。。。
以下コマンドでknown_hostsの内容が更新されてWorningがでなくなります。

$ ssh-keygen -R "IPアドレス"

# 上記で直らない場合は、以下コマンドも試してみてください

$ ssh-keygen -R github.com
push時に毎回パスワードを聞かれないための方法
  1. GitHubのCode画面の右側"Clone or download"ボタンをクリック
  2. 右上の"Use SSH"をクリックし、表示されたURLをコピー
  3. ターミナルから以下コマンドを実行
$ git remote set-url origin "URL"

# 確認のため以下を実行
$ git remote -v

#以下のように、git@github.comから始まっていれば成功!
origin  git@github.com:reiji012-pg/Tabetter.git (fetch)
origin  git@github.com:reiji012-pg/Tabetter.git (push)

Herokuのセットアップ

ここもひとまずチュートリアルのままで。
本番環境のみにPostgreSQLのgemをインストール

Gemfile.
group :production do
  gem 'pg', '0.20.0'
end

開発環境とテスト環境にSQLiteのgemをインストール

Gemfile.
group :development, :test do
  gem 'sqlite3', '1.3.13'
  gem 'byebug',  '9.0.6', platform: :mri
end

pg gemを本番環境にインストールしないようにオプションを追加してインストール

$ bundle install --without production

Herokuをインストール

$ source <(curl -sL https://cdn.learnenough.com/heroku_install)

インストールを確認後、ログインからデプロイまで。

$ heroku --version
$ heroku login --interactive
$ heroku keys:add
$ heroku create
$ git push heroku master

以下コマンドでアプリケーションの名称を変更可能

$ heroku rename "アプリケーション名"

とりあえずこれでアプリケーションの作成に入っていける環境が構築できたかと思います。

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

AIRBNBのようにGOOGLEMAPをWEBに追加

追加背景

今自分で習いながらAIRBNBのような民泊シェアリングエコノミーサイトを作っているので、その中でもGOOGLEMAPの位置表示機能を追加する手順をシェアできればかと思います。今回は部屋の掲載ページに載せるので主にROOM関連のファイルで編集していきます。

GEM追加

Gemfile.
gem 'geocoder', '~> 1.4'

bundle install

DB migration

rails g migration AddFieldsToRoom latitude:float longitude: float (掲載した部屋のページに表示したいのでこの場合ROOMのDBに追加)
rails db: migrate

rooms table

Column Type Options
home_type string null: false
room_type string null: false
accommodate integer null: false
bed_room integer null: false
bath_room integer null: false
listing_name string null: false
summary text null: false
address text null: false
is_tv boolean null: false
is_kitchen boolean null: false
is_aircon boolean null: false
is_heating boolean null: false
is_internet boolean null: false
price integer null: false
active boolean null: false
latitude float null: false
longitude float null: false
user references foreign_key: true

Association

  • belongs_to: user
  • has_many: photos

Roomモデルファイル編集

room.rb
  geocoded_by :address
  after_validation :geocode, if: :address_changed?

ROOMのDBのADDRESSから位置付けをする。あとはアドレスがアップデートされる度にGEOCODEがVALIDATIONをかけ、位置経緯を自動的に決める。

Roomビューファイル編集

rooms/show.html.rb
    <!-- GOOGLE MAP -->
    <div class="row">

      <div id="map" style="width: 100%; height: 400px"></div>

      <script src="https://maps.googleapis.com/maps/api/js"></script>
      <script>
          function initialize() {
            var location = {lat: <%= @room.latitude %>, lng: <%= @room.longitude %>};
            var map = new google.maps.Map(document.getElementById('map'), {
              center: location,
              zoom: 14
            });

            var marker = new google.maps.Marker({
              position: location,
              map: map
            });

            var infoWindow = new google.maps.InfoWindow({
              content: '<div id="content"><%= image_tag @room.cover_photo(:medium) %></div>'
            });
            infoWindow.open(map, marker);
          }

          google.maps.event.addDomListener(window, 'load', initialize);
      </script>

最後の一行はGOOGLEMAP APIのマップ生成を行うためのJquery
https://techacademy.jp/magazine/5638#sec2 (DOMについて忘れたかけてるのでもう一度復習)

Markerはマップ上にピンマークされるように指定。
infowindowでは部屋のカバー写真をマップ上に表示させる

これでマップは表示されるはず。(下の図を参照、場所は適当にマレーシアにしてます笑)
image.png

DBではちゃんと経緯を生成されている!
image.png

そして。。。表示成功!勉強しながらやっていますが、レベルアップしたような達成感はあります。(ズームアウトすると訳のわからん熱帯雨林みたいなところにある、ちょうど写真には合うけど笑)
image.png

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

ローカル環境でserverが起動しない時の対処法 PG::ConnectionBad

結論

DBを起動させていないから

スクリーンショット 2020-05-25 9.48.44.png

解決法

terminal
brew services start postgresql    

※postgresqlのところはそれぞれ使っているDBを指定

terminal
bin/rails s 

開発環境

Homebrew 2.2.17
ruby 2.5.1p57
Rails 5.2.4.3
psql (PostgreSQL) 12.3

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

Railsでよく使うRubyのメソッド

はじめに

Ruby on Railsをメインに扱うWeb受託開発企業に転職し、約4ヶ月が経過しました。
初心者ながらにプロジェクトにアサインして頂き苦し楽しい毎日を過ごしております。
開発していく中でよく使うなーと思うRubyのメソッドをまとめていきます!

メソッド一覧

if

説明不要の条件分岐メソッド。
true,falseで評価し、実行する処理を分岐させる。

if 条件式
  ifに対するtrue処理
elsif 条件式
  elsifに対するtrue処理
else
  falseの処理
end

each

配列の要素を繰り返しの形で取得するメソッド。
RailsではDBのレコードを順に取得する際に利用しています。

[1, 2, 3].each do |i|
  p i
end
出力
1
2
3

map

こちらもeachメソッドと似ていますが、
DBのレコードを取得する際にカラム指定する際に利用しています。

例えば以下のテーブルがあった場合

Usersテーブル
id |name        | age  | sex  | 
1  | テスト 一郎 | 20  | 男   |
2  | テスト 花子 | 18  | 女   |
3  | テスト 二郎 | 15  | 男   |

このようにカラムを指定して取り出せます。

Users.map(&:age)

出力
[20, 18, 15]

カラム指定でテーブルのid等を取得して、
関連付けされたテーブルを参照したりするのに便利です。
また、Whereメソッドと組み合わせ使用したりもよくします。

where

DBからレコードを取り出す際にカラムとデータを指定して検索する際に使用します。
mapと同様のテーブルが存在した場合、以下の通りに取得できます。

Users.where(age: 15)

出力
id |name        | age  | sex  |
3  | テスト 二郎 | 15  | 男   |

first

複数レコードが存在するDBの最初のレコードを取得します。
mapと同様のテーブルが存在した場合、以下の通りに取得できます。

Users.first

出力
id |name        | age  | sex  | 
1  | テスト 一郎 | 20  | 男   |

present?,blank?,nil?

present?:値が存在すればtrue,存在しなければfalse。配列を評価する際に[]の場合nilとなりfalseとなる。
blank?:値といえるものがない場合、真となる。
nil?:変数の値がnilまたは、値なしの場合、真となります。

三項演算子

ifによる条件分岐が1度しかない場合によく利用します。
1行で条件分岐することができ、個人的にはスマートだと思います。

条件式? trueの処理 : falseの処理

count

DBのレコードをカウントしたり、ページネーションに利用したりします。
mapと同様のテーブルが存在した場合、以下の通りに取得できます。

Users.count

出力
3

参考

Ruby 2.7.0 リファレンスマニュアル
【Ruby入門】真偽判定present?の使い方をわかりやすく解説

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

rails cでカラム確認する方法

User.column_names

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

Rubyのバージョンを変えたらbundle installができなくなった

事件発生

ある日のことだった

$ bundle install

を入力すると

Traceback (most recent call last):
~/.rbenv/versions/2.5.0/bin/ruby: invalid option -:  (-h will show valid options) (RuntimeError)

ええええ何だこれ!
他のコマンドも受け付けてくれない、何をしでかしたんだ…

これまでを振り返ってみよう

自分が致命的な何かをしてしまったはず…

Ruby : 2.7 で開発していた

(自分の環境)/vendor/bundle/ruby/2.7.0/gems/active_hash-3.1.0/lib/associations/associations.rb:22: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
(自分の環境)/vendor/bundle/ruby/2.7.0/gems/activerecord-6.0.3.1/lib/active_record/associations.rb:1657: warning: The called method `belongs_to' is defined here

Railsコマンドを入力すると警告がたくさん出て鬱陶しいな
gemの警告は大事だけどまあいっか

環境変数を追加して対処

export RUBYOPT='-W:no-deprecated -W:no-experimental'

.zshrcに上を追加したら警告が消えたよストレスフリーだね

Rubyのバージョンを下げて開発しなければならなくなった

別バージョンでの開発が不可欠になったのでバージョンを下げました(Ruby:2.5)

Traceback (most recent call last):
~/.rbenv/versions/2.5.0/bin/ruby: invalid option -:  (-h will show valid options) (RuntimeError)

今までは別のバージョンにしてもこんなこと起きなかったのにな…

結論

export RUBYOPT='-W:no-deprecated -W:no-experimental'

環境変数からこれを削除したら解決しました。
gemのバージョンが上がって警告が出なくなったら忘れずに削除しないとね。

参考

https://qiita.com/mojihige/items/ce3282bec0c58f8ba637
https://qiita.com/hirocueki2/items/010c777d2125ee120a8e

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

[rails]Rspecで単体テストを行おう!

テストには、単体テストと統合テストの2種類がある。

-単体テスト
 1つのプログラムに対して、正常に動くかのテスト。
 (例)モデルクラスごと

-統合テスト
 1蓮の処理に関するテスト。
 (例)ユーザーの新規登録用画面から値を入力、送信して、データベースにレコードが追加されるまでの流れ

「Rspec」と「factory_bot」の導入

「Rspec」はテスト行うためのjem、
「factory_bot」はテストを行う際に一時的に情報を作成してくれる
お助けツールです。

そのため、まとめてインストールしましょう。

①jemno
インストール

Gemfile
group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
end
ターミナル
bundle install

②RSpecの最低限必要なファイル/ディレクトリ構成をRailsにインストール

ターミナル
$ rails g rspec:install
#>      create  .rspec                  # RSpecの設定ファイル
#>      create  spec                    # スペックを格納する
#>      create  spec/spec_helper.rb     # スペック記述のためのヘルパ
#>      create  spec/rails_helper.rb    # Rails固有のスペック記述のためのヘルパ

specフォルダ.png

ここに、必要なディレクトリ・ファイルを追加していきます。

③必要なファイルの作成
ここでは、usersモデルに対してのバリデーションテストを
行なっていきます。

◆テスト用ファイル
spec/内のファイルは、app/以下のテスト対象のrbファイルに対して1
対1で対応するかたちで配置します。

app/models/user.rbに対するスペックは
spec/models/user_spec.rbとなります。

spec-file.png

◆テストのためのデータ用ファイル(factory_bot)
spec/factoriesにファクトリを配置することで、
簡単にテスト用のデータを利用できます。

factories.png

④ファイルの記述

名前空間を省略できるように設定する。

spec/rails_helper.rb
RSpec.configure do |config|
+  config.include FactoryGirl::Syntax::Methods
end
.rspec
--format documentation
--require spec_helper
spec/models/user_spec.rb
spec/factories/users.rb
FactoryBot.define do
  factory :user do
    nickname               {"taro"}
    email                  {"kkk@gmail.com"}
    password               {"00000000"}
    password_confirmation  {"00000000"}
  end
end

⑤テストの実行

ターミナル
bundle exec rspec spec/models/user_spec.rb
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む