- 投稿日:2020-05-25T23:54:48+09:00
正規表現の基礎
概要
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/) => nilmatchメソッドを使って引数に指定した文字列が含まれているかをチェックしています。
含まれていた場合は指定した文字列が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回以上の繰り返しにマッチ
という意味を持ちます。
ですので@.+ とすると
@から始まり、全ての文字にマッチ、それを繰り返す。ということになるので
@以降のドメインが取得できます。この記事を読んでいただきありがとうございました。
- 投稿日:2020-05-25T22:32:12+09:00
[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.rbclass 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 endrails db:migrateして終了!!
ターミナルrails db:migrate2.カラムの型変更方法
ターミナル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 endrails db:migrateして終了!!
ターミナルrails db:migrate3.カラムの型確認方法
ターミナルでコンソールを使います。
ターミナルrails cターミナル# モデル名.columns_hash['カラム名'].type pry(main)> Post.columns_hash['date'].type => :datedateカラムはdate型と確認出来ましたね。
4.カラムの型一覧
最後にカラムの型一覧です。
[カラムの型]
- データ方 説明
- string 文字列
- text 長い文字列
- integer 整数
- float 浮動小数
- decimal 精度の高い小数
- datetime 日時
- timestamp より細かい日時
- time 時間
- date 日付
- binary バイナリデータ
- boolean Boolean型元ソース Rails
ありがとうございました!!
- 投稿日:2020-05-25T22:21:30+09:00
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 endcapybara.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 enddocker-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:上記設定で解決しました。
- 投稿日:2020-05-25T21:20:20+09:00
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がうまくいく余地があることは間違いありません。
大きなテントを持つということは、すべての人に万能であろうとすることではありません。すべての人を歓迎し、自分の飲み物を持ってくることを許可するということだ。他の人にも参加してもらうことで、私たちの魂や価値観を失う必要はないし、新しいおいしい飲み物の混ぜ方も学べるかもしれない。これはタダではできません。歓迎するためには努力が必要です。特に、あなたの目標が、コミュニティの既存メンバーと同じような人をさらに集めることだけが目的でない場合は、なおさらです。参入障壁を下げることは、常に真剣に取り組むべき仕事です。ドキュメントのスペルミスを修正するだけで始めた次の人が、いつ次の素晴らしい機能を実装することになるかわかりません。しかし、あなたが微笑んで、どんな小さな貢献にも感謝することで、モチベーションを高めることができるかもしれません。
- 投稿日:2020-05-25T20:50:20+09:00
100日後に1人前になる新人エンジニア(5日目)
100日後に1人前になる新人エンジニア(5日目)
どうもこんばんは早くも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日
- 投稿日:2020-05-25T20:48:40+09:00
ユーザー登録時のエラーメッセージ、サクセスメッセージの表示の仕方
はじめに
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.rbdef create @user = User.new(user_params) if @user.save flash[:success] = "Welcome to the Sample App!" #成功したときのメッセージの指定 redirect_to @user else render 'new' end endflash
サクセスメッセージはページにリダイレクトした直後だけ表示するようにします。そのためには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)についてもしっかり理解していきたいと思います。
- 投稿日:2020-05-25T20:16:38+09:00
【Rails 6】active_strageの画像の保存タイミングが変更された
今回はRails6から画像の保存のタイミングが変更したようなので調べてみました。
Rails バージョンの確認
$ rails -v Rails 6.0.2.1ActiveStrageのインストール
$ rails active_storage:install $ rails db:migrate画像添付の準備
最終成果物を載せます。
routeにpreviewを追加します。
route.rbRails.application.routes.draw do resources :users do collection do post :preview patch :preview end end endモデルにhas_one_attachedで画像を扱えるようにします。
user.rbclass User < ApplicationRecord has_one_attached :image end次にcontrollerです。
今回はプレビュー機能をつけたいのでpreviewアクションを追加。user_controller.rbclass 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での画像の保存のタイミング
早速画像をアップロードしていきます。
テスト.jpegをアップロードして
Create User
ボタンを押すと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を試みます。
同じようにプレビュー.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/33303rails5では
@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)できました!!
まとめ
ActiveStrageの保存のタイミングに変更があるなんて知りませんでした。
保存せずに画像を触りたいならrecord.attachment_changes['<attributename>'].attachable.readでバイナリ読み出してBase64!!(Base64でなくてもいいけど)
他に何かいい方法あれば教えてください。
- 投稿日:2020-05-25T19:30:26+09:00
ログイン、サインアップ画面のビュー変更
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
- 投稿日:2020-05-25T18:29:32+09:00
『kaminari』配列に対してページネーションを作成する方法
はじめに
今回は、配列に対してページネーションを作成しビューで表示するまでのサンプルコードを紹介します。
コントローラー
お気に入りした商品をユーザーのマイページにて表示するためにコントローラーでこのようなコードを書きました。実はこれ、アソシエーション組めばもっと簡単にできるので以下の記事を参考にしてみてください!
https://qiita.com/yummy888/items/22db2f8b79b5be148b69def 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) enduser_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まとめ
自分で色々実装できるようになると、より簡単な実装方法の模索を放棄してしまってると感じるので、そこは改善していきたいですね、、、ちょっとできるように、なっただけで付け上がっちゃいかんのう
でも、ページネーション作成する場合だったらどちらにしてもコントローラーで色々やらないといけないのかな??ビューでparams[:page]とかできないよな?多分
次この機能実装するときはまた違った実装の方法を試してみますか
参考記事
- 投稿日:2020-05-25T18:25:28+09:00
PAY.JPで登録したクレジットカードで商品購入機能を実装する
はじめに
個人アプリにて、クレジットカード決済を行うため、PAY.JPを導入しました。
機能実装に関して、備忘録として記載しています。前回の記事
PAY.JPでクレジットカードの登録・削除機能を実装する前提条件
- Rails 5.2.4.2
- Ruby 2.5.1
- devise使用
- haml使用
- VSCode使用
前回までにカード情報の登録を行うためCardモデル及びCardコントローラーを作成しました。
今回は新たに購入用モデル及びコントローラーを作成して購入処理を行っていきます。手順
- モデルの作成
- テーブルの作成
- コントローラーの作成
モデルの作成
今回は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 endECサイトを作成したので、カート機能などを実装している関係で、インスタンスメソッドなど定義しておりますが、今回は購入機能にフォーカスしているため、割愛します。
なので、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 endcardを外部キーとして設置しています。
コントローラーの作成
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 endnewアクションにおいては、購入確認ページの形態をとっています。
ユーザーが登録していないと、そもそも購入確認ページに訪問できない仕様になっております。
かつ、ユーザーがマイページでカードを登録できるように仕様です。購入確認ページで初めてカード登録を行う仕様にするのであれば、前回の記事で作成した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
- 投稿日:2020-05-25T17:20:28+09:00
PAY.JPでクレジットカードの登録・削除機能を実装する
はじめに
個人アプリにて、クレジットカード決済を行うため、PAY.JPを導入しました。
導入において、少しつまずいた部分もあったので、備忘録として記載しています。前提条件
- Rails 5.2.4.2
- Ruby 2.5.1
- devise使用
- haml使用
- VSCode使用
手順
- PAY.JPの登録
- アプリにPAY.JPを導入・下準備
- モデルの作成
- マイグレーションファイル
- コントローラーの作成
- gonの導入
- JSファイルの作成
PAY.JPの登録
以下のURLより登録します。
PAY.JP登録が完了すると、上記のような画面に移ります。(使用したため、売上がたっております。)
最初はテストモードになっております。
実取引を行うためには,申請を行い、ライブモードに切り替える必要があります。
今回は個人アプリでの使用であり、商用目的ではないので、テストモードを使用しています。自身のアプリにはこのテスト秘密鍵とテスト公開鍵を使用していきます。
アプリにPAY.JPを導入・下準備
Gemfileに以下を追記
gem 'payjp'ここから、カード情報の入力フォームを作成準備をしていくのですが、やり方としては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キーなどセキュリティ的に外部に公開してはいけない値の管理をシンプルに行うことができる機能です。
当該ファイルを編集するには少し準備が必要です。
まずは、ターミナルからVSCodeを起動できるよう設定を行います。
VSCodeで、「Command + Shift + P」を同時に押してコマンドパレットを開きます。
続いて、「shell」と入力しましょう。
メニューに、「PATH内に'code'コマンドをインストールします」という項目が表示されるので、それをクリックします。
この操作を行うことで、ターミナルから「code」と打つことでVSCodeを起動できるようになりました。続いて以下にてファイルの中身を編集していきます。
$ pwd # 自身のアプリディレクトリにいることを確認 $ EDITOR='code --wait' rails credentials:editここに以下のように記述していきます。
ネストしていることに注意してください。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"}次回は、登録できたカードでの決済機能を記載します。
- 投稿日:2020-05-25T15:46:24+09:00
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>実際にブラウザで表示するとどうなるか!?
Reactから「contentEditableはあなたの責任で使いなさいよ」という警告メッセージが出ていました。
はーい、がんばりまーす。
って言うほど頑張れないので、メッセージ出さない方法ないかなぁ?
と調べた結果、react-contenteditableというものを見つけたので、使ってみたいと思います。react-contenteditableの追加
以下のとおり、yarnで追加できました。
shellbundle exec yarn add react-contenteditable
react-contenteditableを使ってみる。
インストールができたので、ソースコードに追加してみます。
対象は、先ほどのSticky.jsです。ソースコードの変更
app/javascript/components/Sticky.jsimport 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 動作確認
おぉ!編集できる!!
ならば、この値を取得して保存する流れを作ってみます。CotentEditableで作成した要素から値を取得する。
reactで編集可能な要素の値を取得するには、onChangeを使ってstateに値を保存するなどの方法が考えられますが、ここではrefを使った方法を使ってみます。
ContentEditableコンポーネントでは、innerRefというプロパティを使うことで実現します。具体的には以下のようなソースコードとなります。(変更箇所に1〜5の番号をふってあります。)
app/javascript/components/Sticky.jsimport 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; }試してみます。
タイトルを編集(hogegeってしたり、updatedってしたり)して、"save"を押してみると。
おぉ、ログが出た。
実際にDBに保存させてみる。
編集した値が取得できることが分かったので、実際にDBに反映する流れを作ってみます。
シーケンス図で考える。
Railsで作ってるAPIにて
APIの処理イメージ@task = Task.find(params[:task][:id]); @task.update(params[:task]);みたいな感じになれば良いわけですが。
誰がこのAPIを呼ぶ?
みたいなことを考えねばなりません。で、以前作っているWhiteBoard.jsですでにAPI呼び出しを行っているので、
こいつにまとめてしまおう。
ちょっと複雑になるので、シーケンス図を起こしてみました。
図にするとわかりやすいですね。
最終的に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); endViewを作る
次に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.rbnamespace :api do put 'tasks/update' endとりあえずテスト
ブラウザでいちいち動作確認するのめんどいので、テストコードを追加しときます。
エラー系は、とりあえず後で。。test/controllers/api/tasks_controller_test.rbtest "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.jsimport 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.rbtest "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まとめ
- Reactでレンダリングした要素でcontentEditable=trueを使いたい場合は、react-contenteditableを使うと良い。
- しかし、今回の場合はinputタグでスタイル指定してそれっぽく見せたほうが良かったのではないかと。
- ContentEditableで描画した部分も普通にCapybaraでテストできる。
- 投稿日:2020-05-25T15:44:38+09:00
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)を使用します。controllerdef new @user = User.new endview<%= 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 %>最後に
まだまだ覚えないといけない書き方がたくさんありますが、少しずつ覚えていきます。
- 投稿日:2020-05-25T15:38:48+09:00
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"という感じです。
- 投稿日:2020-05-25T12:56:23+09:00
has_secure_passwordとは
簡単に言うと?
安全性の高いパスワード機能を簡単に実装できるメソッド。
間違ったパスワードを入力させないように、二回パスワードを入力させるフォーム画面を作成するときに使える。何ができる?
has_secure_passwordを使うと以下の機能が使える様になる。
- ハッシュ化したパスワードをデータベース内のpassword_digestという属性に保存できるようになる。
- 2つのペアの仮想的な属性(passwordとpassword_confirmation)が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される 。
- 認証のために使うauthenticateメソッドが使えるようになる。authenticateメソッドとは、引数で受けとった文字列がオブジェクト内のpassword_digestと一致するか調べるためのメソッド。一致した時はそのオブジェクト自身を返し、不一致だとfalseを返す。
どうやって使う?
- テーブルのカラムに
password_digest
を導入する- Gemfileの
bcrypt
をコメントアウトしてbundle install
(Genfile内にコメントとして既に記述されているのでコメントアウトする)- モデルに
has_secure_password
を追加するpassword
とpassword_confirmation
を使ってフォーム画面を作成する。$ bin/rails g model user name:string password_digest:stringapp/model/user.rbclass User < ApplicationRecord has_secure_password endgem 'bcrypt', '~> 3.1.7' $ bundle installapp/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のすすめ
- 投稿日:2020-05-25T12:32:15+09:00
【gem:devise の日本語化】
gem:devise日本語化の流れ
(deviseインストール後の流れ)
gemのインストール
・gem 'devise-i18n'
・gem 'devise-i18n-views'コマンドで日本語翻訳ファイルを生成
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.ymlja: 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: "アカウントの凍結解除方法を再送する"これで日本語化ができると思います!
おわりに
自分メモように書いてみました!
よかったら参考にしてみてください。
- 投稿日:2020-05-25T12:06:52+09:00
【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 => false8時台 < 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 => trueDateTimeと同じ挙動をします。
クラスを見てみる
[31] pry(main)> Date.yesterday.class => Date [32] pry(main)> DateTime.yesterday.class => Dateあ、DateTimeで作ってもインスタンスのクラスはDateになるんですね。
つまり、Date.yesterday
とDateTime.yesterday
は同じもののようです。結論
- DateTimeで日付を指定すると、インスタンスはDateクラスになる。
- DateクラスのインスタンスはDateTime/Timeクラスになった時点でタイムゾーンを持つ。
- Dateクラスの状態ではタイムゾーンは+0000なので、DateTime/Timeクラス(Railsだと
ActiveSupport::TimeWithZone
クラス)と比較するときは注意する。
- 投稿日:2020-05-25T12:06:52+09:00
【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 => false8時台 < 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 => trueDateTimeと同じ挙動をします。
クラスを見てみる
[31] pry(main)> Date.yesterday.class => Date [32] pry(main)> DateTime.yesterday.class => Dateあ、DateTimeで作ってもインスタンスのクラスはDateになるんですね。
つまり、Date.yesterday
とDateTime.yesterday
は同じもののようです。結論
- DateTimeで日付を指定すると、インスタンスはDateクラスになる。
- Dateクラスはタイムゾーンの情報を持っていないため、+0000になる。
- DateクラスはDateTime/Timeクラスになった時点でタイムゾーンを持つ。
- DateクラスとDateTime/Timeクラス(Railsだと
ActiveSupport::TimeWithZone
クラス)を比較するときはDateクラスをDateTimeクラスへ変換する必要がある。
- 投稿日:2020-05-25T12:06:52+09:00
【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 => false8時台 < 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 => trueDateTimeと同じ挙動をします。
クラスを見てみる
[31] pry(main)> Date.yesterday.class => Date [32] pry(main)> DateTime.yesterday.class => Dateあ、DateTimeで作ってもインスタンスのクラスはDateになるんですね。
つまり、Date.yesterday
とDateTime.yesterday
は同じもののようです。結論
- DateTimeで日付を指定すると、インスタンスはDateクラスになる。
- Dateクラスはタイムゾーンの情報を持っていないため、+0000になる。
- DateクラスはDateTime/Timeクラスになった時点でタイムゾーンを持つ。
- DateクラスとDateTime/Timeクラス(Railsだと
ActiveSupport::TimeWithZone
クラス)を比較するときはDateクラスをDateTimeクラスへ変換する必要がある。
- 投稿日:2020-05-25T11:51:46+09:00
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" >> ~/.gemrcRailsインストール
今回、バージョンはチュートリアルのままとしたが、いずれバージョンアップもやっておかないと。。。
$ gem install rails -v 5.1.6rails 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.comHi (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.compush時に毎回パスワードを聞かれないための方法
- GitHubのCode画面の右側"Clone or download"ボタンをクリック
- 右上の"Use SSH"をクリックし、表示されたURLをコピー
- ターミナルから以下コマンドを実行
$ 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 endpg gemを本番環境にインストールしないようにオプションを追加してインストール
$ bundle install --without productionHerokuをインストール
$ 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 "アプリケーション名"とりあえずこれでアプリケーションの作成に入っていける環境が構築できたかと思います。
- 投稿日:2020-05-25T10:56:56+09:00
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: migraterooms 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.rbgeocoded_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では部屋のカバー写真をマップ上に表示させるこれでマップは表示されるはず。(下の図を参照、場所は適当にマレーシアにしてます笑)
そして。。。表示成功!勉強しながらやっていますが、レベルアップしたような達成感はあります。(ズームアウトすると訳のわからん熱帯雨林みたいなところにある、ちょうど写真には合うけど笑)
- 投稿日:2020-05-25T10:36:23+09:00
ローカル環境でserverが起動しない時の対処法 PG::ConnectionBad
- 投稿日:2020-05-25T08:17:09+09:00
Railsでよく使うRubyのメソッド
はじめに
Ruby on Railsをメインに扱うWeb受託開発企業に転職し、約4ヶ月が経過しました。
初心者ながらにプロジェクトにアサインして頂き苦し楽しい毎日を過ごしております。
開発していく中でよく使うなーと思うRubyのメソッドをまとめていきます!メソッド一覧
if
説明不要の条件分岐メソッド。
true,falseで評価し、実行する処理を分岐させる。if 条件式 ifに対するtrue処理 elsif 条件式 elsifに対するtrue処理 else falseの処理 endeach
配列の要素を繰り返しの形で取得するメソッド。
RailsではDBのレコードを順に取得する際に利用しています。[1, 2, 3].each do |i| p i end 出力 1 2 3map
こちらも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参考
- 投稿日:2020-05-25T02:01:16+09:00
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 hereRailsコマンドを入力すると警告がたくさん出て鬱陶しいな
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
- 投稿日:2020-05-25T01:05:46+09:00
[rails]Rspecで単体テストを行おう!
テストには、単体テストと統合テストの2種類がある。
-単体テスト
1つのプログラムに対して、正常に動くかのテスト。
(例)モデルクラスごと-統合テスト
1蓮の処理に関するテスト。
(例)ユーザーの新規登録用画面から値を入力、送信して、データベースにレコードが追加されるまでの流れ「Rspec」と「factory_bot」の導入
「Rspec」はテスト行うためのjem、
「factory_bot」はテストを行う際に一時的に情報を作成してくれる
お助けツールです。そのため、まとめてインストールしましょう。
①jemno
インストールGemfilegroup :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固有のスペック記述のためのヘルパここに、必要なディレクトリ・ファイルを追加していきます。
③必要なファイルの作成
ここでは、usersモデルに対してのバリデーションテストを
行なっていきます。◆テスト用ファイル
spec/内のファイルは、app/以下のテスト対象のrbファイルに対して1
対1で対応するかたちで配置します。app/models/user.rbに対するスペックは
spec/models/user_spec.rbとなります。◆テストのためのデータ用ファイル(factory_bot)
spec/factoriesにファクトリを配置することで、
簡単にテスト用のデータを利用できます。④ファイルの記述
名前空間を省略できるように設定する。
spec/rails_helper.rbRSpec.configure do |config| + config.include FactoryGirl::Syntax::Methods end.rspec--format documentation --require spec_helperspec/models/user_spec.rbspec/factories/users.rbFactoryBot.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