- 投稿日:2020-02-22T22:16:23+09:00
【Rails】herokuでS3に画像をアップロード(fog)
Railsチュートリアル第13章でheroku → S3デプロイまで苦戦したためご参考先とともにメモ。
本記事に表現・内容の誤り等あればご指摘頂けると幸いです(^_^;)エラー
・heroku→S3へ画像投稿ができない(マイクロポストテキストはできる)
・(投稿成功後)画像のリサイズで小さくならない※画像がheroku内かS3どうかは、マイクロポストでアップした画像を右クリックし、
画像アドレスをコピー
でリンク先が「 ~s3.amazonaws~ 」になっていればok。対処・間違っていたこと等
・IAM→S3作成ユーザー(≠これまでのEC2作業ユーザ)
・バケットポリシー変更(✔︎新ポリシーのみブロック≠全ブロック)
・uploaderのlimitサイズ変更で確認(そもそも投稿元のサイズが縦長で大きく見えただけだった)※ 投稿後にS3を確認すると、
uploads
フォルダが作成されている< 流れ >
- S3(バケット)作成
- EC2作業ユーザとは別に新規IAMユーザ作成(Fullアクセス権限付与、バケットからAccessキー & Secretキー取得のため)
- パプリックアクセス一時的に解除 & ポリシー付与
- 再度パブリックアクセス(新規条件のブロックに✔︎)
- herokuでS3アクセスキー等追加
S3パブリック設定
パブリックアクセスをすべてブロック
オフ新しいアクセスコントロールリスト (ACL) を介して許可されたバケットとオブジェクトへのパブリックアクセスをブロックする
オフ任意のアクセスコントロールリスト (ACL) を介して許可されたバケットとオブジェクトへのパブリックアクセスをブロックする
オフ新規のパブリックバケットポリシーまたはアクセスポイントポリシーを介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする
オン任意のパブリックバケットポリシーまたはアクセスポイントポリシーを介したバケットとオブジェクトへのパブリックアクセスとクロスアカウントアクセスをブロックする
オフ
各種作成設定ファイル
carrier_wave
config/initializers/carrier_wave.rbif Rails.env.production? CarrierWave.configure do |config| config.fog_provider = 'fog/aws' #=> 追加 config.fog_credentials = { # Amazon S3用の設定 :provider => 'AWS', :region => ENV['S3_REGION'], # 例(東京): 'ap-northeast-1' :aws_access_key_id => ENV['S3_ACCESS_KEY'], :aws_secret_access_key => ENV['S3_SECRET_KEY'] } config.fog_directory = ENV['S3_BUCKET'] # キャッシュの保存期間 config.fog_attributes = { 'Cache-Control' => "max-age=#{365.day.to_i}" } end # 日本語ファイル名の設定 CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/ end
store_dir
はデフォルトの保存先(S3で確認すると出てくる)app/uploaders/picture_uploader.rbclass PictureUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick process resize_to_limit: [300, 300] # Choose what kind of storage to use for this uploader: if Rails.env.production? storage :fog else storage :file #=> ローカルストレージ end # storage :fog # アップロードファイルの保存先ディレクトリは上書き可能 # 下記はデフォルトの保存先(S3で確認すると出てくる) def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end # Add a white list of extensions which are allowed to be uploaded. # For images you might use something like this: def extension_whitelist %w(jpg jpeg gif png) end end
GemfileGemfilegroup :production do gem 'pg', '0.20.0' gem 'fog-aws'#=> fogから変更 endその他
heroku関係
※
""
は無くてもok(heroku configで確認できる)$ heroku config #=> 状態確認 $ heroku logs #=> ログ確認 $ heroku config:set S3_ACCESS_KEY="CSVダウンロードしたAccessキー" $ heroku config:set S3_SECRET_KEY="ダウンロードしたSecretキー" $ heroku config:set S3_BUCKET_KEY="S3のBucketの名前" $ heroku config:set S3_REGION="ap-northeast-1"ご参考先
【公式】AWS Config 開発者ガイド
S3のバケットポリシーでハマったので、S3へのアクセスを許可するPrincipalの設定を整理する
【Rails】S3へ『CarrierWave+fog』を使って画像アップロードする方法
HerokuでS3に画像をアップロードした話[Rails][S3][CarrierWave][fog]
Rails5 heroku にS3を使ってアップロードしたときに発生するエラー 403 Access denied の対処法
- 投稿日:2020-02-22T22:16:23+09:00
【AWS】herokuでS3に画像をアップロード(Rails)
Railsチュートリアル第13章でheroku → S3デプロイまで苦戦したためご参考先とともにメモ。
本記事に表現・内容の誤り等あればご指摘頂けると幸いです(^_^;)エラー
・heroku→S3へ画像投稿ができない(マイクロポストテキストはできる)
・(投稿成功後)画像のリサイズで小さくならない※画像がheroku内かS3どうかは、マイクロポストでアップした画像を右クリックし、
画像アドレスをコピー
でリンク先が「 ~s3.amazonaws~ 」になっていればok。対処・間違っていたこと等
・IAM→S3作成ユーザー(≠これまでのEC2作業ユーザ)
・バケットポリシー変更(✔︎新ポリシーのみブロック≠全ブロック)
・uploaderのlimitサイズ変更で確認(そもそも投稿元のサイズが縦長で大きく見えただけだった)※ 投稿後にS3を確認すると、
uploads
フォルダが作成されている< 流れ >
- S3(バケット)作成
- EC2作業ユーザとは別に新規IAMユーザ作成(Fullアクセス権限付与、バケットからAccessキー & Secretキー取得のため)
- S3へ行きアクセス権限からパプリックアクセスを一時的に解除 & ポリシー付与
- 再度パブリックアクセス(新規条件のブロックに✔︎)
- herokuでS3アクセスキー等追加
S3パブリック設定
パブリックアクセスをすべてブロック
オフ新しいアクセスコントロールリスト (ACL) を介して許可されたバケットとオブジェクトへのパブリックアクセスをブロックする
オフ任意のアクセスコントロールリスト (ACL) を介して許可されたバケットとオブジェクトへのパブリックアクセスをブロックする
オフ新規のパブリックバケットポリシーまたはアクセスポイントポリシーを介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする
オン任意のパブリックバケットポリシーまたはアクセスポイントポリシーを介したバケットとオブジェクトへのパブリックアクセスとクロスアカウントアクセスをブロックする
オフ
ポリシー
既にEC2で開発している場合、S3用で作った新規ユーザではIDが出るわけではないので注意(元のものでok)
※ 入力に
""
や「」
は不要{ "Version": "2012-10-17", "Statement": [ { "Sid": "statement1", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::AWSユーザのID(12桁の数字):user/新規に作ったS3用ユーザ名" }, "Action": "*", "Resource": "arn:aws:s3:::バケット名/*" } ] }各種作成設定ファイル
carrier_wave
config/initializers/carrier_wave.rbif Rails.env.production? CarrierWave.configure do |config| config.fog_provider = 'fog/aws' #=> 追加 config.fog_credentials = { # Amazon S3用の設定 :provider => 'AWS', :region => ENV['S3_REGION'], # 例(東京): 'ap-northeast-1' :aws_access_key_id => ENV['S3_ACCESS_KEY'], :aws_secret_access_key => ENV['S3_SECRET_KEY'] } config.fog_directory = ENV['S3_BUCKET'] # キャッシュの保存期間 config.fog_attributes = { 'Cache-Control' => "max-age=#{365.day.to_i}" } end # 日本語ファイル名の設定 CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/ end
store_dir
はデフォルトの保存先(S3で確認すると出てくる)app/uploaders/picture_uploader.rbclass PictureUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick process resize_to_limit: [300, 300] # Choose what kind of storage to use for this uploader: if Rails.env.production? storage :fog else storage :file #=> ローカルストレージ end # storage :fog # アップロードファイルの保存先ディレクトリは上書き可能 # 下記はデフォルトの保存先(S3で確認すると出てくる) def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end # Add a white list of extensions which are allowed to be uploaded. # For images you might use something like this: def extension_whitelist %w(jpg jpeg gif png) end end
GemfileGemfilegroup :production do gem 'pg', '0.20.0' gem 'fog-aws'#=> fogから変更 endその他
heroku関係
※
""
は無くてもok(heroku configで確認できる)$ heroku config #=> 状態確認 $ heroku logs #=> ログ確認 $ heroku config:set S3_ACCESS_KEY="CSVダウンロードしたAccessキー" $ heroku config:set S3_SECRET_KEY="CSVダウンロードしたSecretキー" $ heroku config:set S3_BUCKET_KEY="S3のBucketの名前" $ heroku config:set S3_REGION="ap-northeast-1"ご参考先
【公式】AWS Config 開発者ガイド
S3のバケットポリシーでハマったので、S3へのアクセスを許可するPrincipalの設定を整理する
【Rails】S3へ『CarrierWave+fog』を使って画像アップロードする方法
HerokuでS3に画像をアップロードした話[Rails][S3][CarrierWave][fog]
Rails5 heroku にS3を使ってアップロードしたときに発生するエラー 403 Access denied の対処法
- 投稿日:2020-02-22T21:45:27+09:00
asset_path について
便利と思ったので備忘録。
asset_path について
画面上にimageを表示する際、
'/assets/titel.png'
と記述する事も多いですが、asset_pathを使えば、
= image_tag asset_path("title.png")
↑で
app/assets/images/title.pngを参照してくれます。
(プロダクション環境でも参照可能)他にも参考に
application.js というファイルが assets/javascripts に存在する場合
'/assets/application.js'
が、=>asset_path('application.js')
てな感じ。image.png というファイルが assets/images に存在する場合
'/assets/image.png'
が、=>asset_path('image.png')
てな感じ。config.assets.digest = true な環境で application.css というファイルが assets/stylesheets に存在する場合
'/assets/application817776380c2b21405e00e88cbdd215e1.css'
が、=>asset_path('application.css')
てな感じ。
- 投稿日:2020-02-22T21:11:10+09:00
[Rails] ActiveRecordでデータを更新したときに、各カラムが更新されたかどうか知りたかったんだ
はじめに
タイトル通りです。
下のアプリはチャットルームに「タイトル」と「ルームの説明」、「カテゴリ」を設定できるんだけれども、
「タイトル」が変更された時だけトークルームに通知が来るようにしたかった。
変更前のパラメーターを取得するか、変更されたかどうかを判定するメソッドがあったら嬉しいなーと思って探した。
実装
どうやらActiveRecordさんには「カラム名_changed?」っていうヘルパーメソッドがあるらしいと聞いて...
room_controllerdef update if @room.update(room_update_params) if @room.title_changed? # ココ。title_cahged?がtrueであれば自動メッセージを送るようにしたつもり。 Message.create(room_id: @room.id, user_id: current_user.id, content: "*自動投稿* タイトルを「#{@room.title}」に編集しました") end redirect_to room_path(@room.id) else render :edit end endこんな感じで実装してみたのだけれども失敗した。
Githubのrailsを見てみると、なんか更新されているらしい。
つまり
カラム名_changed?は、ActiveRecordのコールバックを使って処理を分けることができていたが、
save前の場合は、
オブジェクト.will_save_change_to_カラム名save後の場合は、
オブジェクト.saved_change_to_{カラム名}?という風に書き変える必要があるみたい。
再度実装
room_controller.rbdef update if @room.update(room_update_params) if @room.title_updated? Message.create(room_id: @room.id, user_id: current_user.id, content: "*自動投稿* タイトルを「#{@room.title}」に編集しました") end redirect_to room_path(@room.id) else render :edit end endコントローラーで「saved_change_to」やら「will_save_change_to」やらを呼び出すとうまくいかなかったので、
モデル側に「更新されたかどうか判別するインスタンスメソッド」を作りました。room.rbdef title_updated? if self.saved_change_to_title? return true else return false end endこれでタイトル編集時のみメッセージが投稿されるようになりました。
おわりに
今回初めてrailsのソースコードを見ましたが、もっと見ていくととても勉強になりそうです。
暇を見つけてもっと読んでみようと思いました。おわり。
- 投稿日:2020-02-22T20:19:22+09:00
Docker-based deployments to heroku of Rails App(postgres) 2020.02
Overview
ここに書くこと
- Dockerコンテナ上で動作するRailsアプリケーションに関する話です
- Railsアプリケーションをherokuにデプロイする話です
- dbはpostgreSQLを使います
Prerequisite
- 以下の開発環境が出来上がっている
- railsサーバー, dbサーバー用のdockerコンテナ(postgreSQL)のみ
- docker-composeでコンテナ同士が協調している
- ローカルでrailsアプリが正常動作している
rails s
+ ブラウザにてWebページの確認ができるSystem
- Docker
- ver:
19.03.5, build 633a0ea
- Docker-compose
- ver:
1.25.2, build 698e2846
- heroku CLI
- ver:
7.36.3 darwin-x64 node-v12.13.0
Table of Contents
- Conclusion
-a
オプションが必要- dbコンテナはherokuのアドオンを使う
- 参考にさせていただいたページ
Main
1. Conclusion
まず、結論です。以下の順にコマンドを打てば良いです。
docker-based-deployments-to-heroku(rails-app)# webサービスイメージをherokuにpush heroku container:push web -a myapp # postgresql用add-onを作成する(大事) heroku addons:create heroku-postgresql -a myapp # webサービスコンテナをリリース(heroku上でコンテナを実行する?) heroku container:release web -a myapp # heroku上でdbのマイグレーション heroku run rails db:migrate -a myapp # デプロイしたWebページを開く heroku open -a myapp基本的には、参考にさせていただいたサイト通りなのですが、
いくつかそのままでは実行できない箇所があったので以下記述します。2.
-a
オプションが必要おおよそ全てのコマンドにおいて、
デプロイ対象のインスタンス(herokuではdynoと呼ばれる)の名称を指定する必要があります。
名称は、herokuのマイページでも確認できますし、CLIでheroku apps
と入力しても確認できます。ダメな例heroku container:push webと入力すると
エラーメッセージ› Error: Missing required flag: › -a, --app APP app to run command against › See more help with --helpというエラーメッセージが出るので、
-a
オプションで指定します。良い例heroku container:push web -a myapp
3. dbコンテナはherokuのアドオンを使う
herokuではvolumeが使えません。
したがって、PostgreSQLはherokuのアドオンを利用して、
webコンテナと接続させます。その設定がheroku addons:create heroku-postgresql -a myapp
です。4. 参考にさせていただいたページ
本記事は必要最小限の内容しか記述していません。
以下に有用な情報があります。
- 投稿日:2020-02-22T19:56:22+09:00
初めてのチーム開発(フリマサイト)
チーム分けと分担
スクールに通い出して初めてのチーム開発がスタートしました。
チームは5人。みんな現職者です。初日は全体ミーティングで、チーム分けと、全体の流れの説明がありました。
その後、時間も遅かったので翌日再集合として解散。翌日に集まり、開発物の確認、ゴールの設定、分担等の話し合いを実地します。
- 今回の開発物:フリマサイト(メルカリ)のコピーサイト
- ゴールの設定:最低限の機能実装に留める。(スクール指定)
- 分担:きちんと決まりませんでした。
分担は正直初めてでは難しかったです。初見の5人の集まりで技術力も不安だらけ、おまけに現職者で時間も厳しい。
それでもスクラムマスターを決めて格機能の見積もりを付けるところに行きました。
とは言え、技術力が無い=実装がどれくらい大変か分からないと言うことです。
とりあえす高、中、低の3段階で最初のスプリントレビューまで(2週間)やってみようとなりました。
- 投稿日:2020-02-22T19:27:59+09:00
[取り置き機能]Rails初学者が某フリマアプリにオリジナル機能を実装してみた。
某スクールのチーム開発にて某フリマアプリを作成しております。
先日、必須機能の実装が完了し、追加機能を実装していくフェーズになりました。
そこで、自分なりに色々と考えた結果、タイトルに記載いたしました「取り置き機能」を実装しました!
今回は、機能追加を決めるにあたっての背景含めて以下に記載いたします。今までは既存のコンテンツの実装であったため、様々な記事や情報があったため、自分で実装コンテンツや実装方法を考えるという機会は少し少なかった印象ありました。
しかしながら、今回はオリジナル機能であったため、実際のアプリユーザのことを考えながら実装をしていくのは、非常に楽しかったです!!背景(「取り置き機能」を追加機能に選んだ理由)
今回、追加機能を選ぶにあたり、ネットで某フリマアプリユーザのコメントをネットで探したり、自分でサイトをいじっていました。
その結果、以下の状況が見えてきたため、今回の「取り置き機能」実装を決断しました。
- 問題・課題:
特定のユーザに販売する際に、出品写真やタイトルなどに”〇〇様向け”などと書くしかできず、他ユーザに購入できないようにするブロックができない。
その状況のため、別ユーザがユーザ名を偽ることで、本来購入をする予定であったユーザが購入できず、別のユーザに購入されてしまうという問題が発生している。「取り置き機能」実装に関する考え方
今回の機能実装に関して、以下のように考えました。
1. 商品を出品するユーザが、商品に対して”キー”を設定する。
2. その”キー”を持っているユーザは購入をできるが、”キー”を持っていないユーザは購入をできないようにする。ここで問題となったのは何を"キー”とするかでした。
「出品するユーザが合言葉を設定し、それを入力させる」や「ユーザ名を”キー”とする」などを考えましたが、どれもユニーク性がなかったためボツとし、今回は購入して欲しいユーザのemailアドレスを”キー”とすることにし,”reservation_email”というカラムを追加することにしました。
"reservation_email"の情報によって、入力された情報とログインしているユーザの情報が一致すれば、購入できる/一致しなければ、購入できないようにします。*実際はemailアドレスを"キー”とするのはプライバシーの観点からあまりよろしくはないと思われますため、可能で有れば、会員番号などを”キー”とするのがベターかと考えます。(今回は会員番号を設定していなかったため、アドレスにしました)
実装内容(イメージ)
今回実装した機能のイメージは以下の通りになってます。
出品ユーザ側機能
機能1:取り置きをする
機能2:取り置きをやめる
機能3:その他ユーザに購入できないようにする
購入ユーザ側機能
機能1:取置き品を購入する
*購入すると以下のように、SOLDとなります。
購入についての実装は、以下の記事をご参照ください。
[HowTo]Pay.jpを用いた商品購入機能実装から商品購入後の設定まで
https://qiita.com/Tatsu88/items/eb420e372077939a4627#%E5%95%86%E5%93%81%E8%B3%BC%E5%85%A5%E7%A2%BA%E8%AA%8D%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AE%E7%B7%A8%E9%9B%86機能実装:マイグレーションファイル
マイグレーションファイルは以下のように実装してます。
今回は、t.string :reservation_emailを追加してます。
こちらが、取り置き機能を実現するための要となります。
"reservation_email"の情報によって、入力された情報とログインしているユーザの情報が一致すれば、購入できる/一致しなければ、購入できないようにします。マイグレーションファイルclass CreateProducts < ActiveRecord::Migration[5.0] def change create_table :products do |t| t.string :name, limit: 191,null:false,index: true t.integer :price, index: true t.text :explain,null:false t.integer :postage,null:false t.integer :status t.integer :shipping_date t.integer :size t.integer :brand_id t.integer :category_id t.integer :prefecture t.integer :buyer_id t.references :user,index: true, foreign_key: true t.string :reservation_email t.timestamps end end end機能実装(ルーティング)
ルーティングは以下のように実装してます。
'reserve'、'reserved',patch 'reserve_cancel'を今回追加してます。
内容としては、予約・予約完了・予約取り消しとなります。ルーティングresources :products do member do post 'purchase' get 'purchased' get 'buy' get 'reserve' patch 'reserved' patch 'reserve_cancel' end resources :comments,only:[:create,:destroy] end機能実装(ビュー)
ビューは以下のように実装してます。
基本的に"reservation_email"の情報有無によって表示内容を変えてます。1. 商品一覧
こちらでは購入済みか取り置き済みかでサムネイルとして表示する写真にラベルを取り付けます。
view.product__thumbnail--image =image_tag (product.images[0].product_image.url) #購入されていれば、"SOLD"のラベルをつけます。 -if product.buyer_id.present? .items-box_photo__sold .items-box_photo__sold__inner SOLD #取り置きされていれば、"Reserved"のラベルをつけます。 -if product.reservation_email.present? .items-box_photo__reserved .items-box_photo__reserved__inner Reserved2. 商品詳細
こちらではユーザの状態によって、ビューの表示内容を変えてます。
view.product-buy__btn__box #ログインユーザが出品者の場合の、表示内容 - if user_signed_in? && current_user.id ==@product.user_id = link_to "削除する", product_path(@product.id), method: :delete,class:"product-details-delete__btn" = link_to "編集する", edit_product_path(@product.id),class:"product-details-edit__btn" = link_to "取り置きする/編集する", reserve_product_path(@product.id),class:"product-details-resorve__btn" #購入者向けの表示内容 #既に購入されている時 - elsif @product.buyer_id.present? = link_to "売り切れました",buy_product_path,class:"disabled-button bold" #取り置きされていて、その取り置きを商品を購入することを許可されているユーザの時 - elsif @product.reservation_email.present? && @product.reservation_email == current_user.email = link_to "取り置き商品を購入する",buy_product_path,class:"product-purchase__btn" #取り置きされていて、その取り置きを商品を購入することを許可されていないユーザの時 - elsif @product.reservation_email.present? && @product.reservation_email != current_user.email = link_to "取り置き商品のため購入できません",buy_product_path,class:"disabled-button bold" - else = link_to "購入画面に進む",buy_product_path,class:"product-purchase__btn"2. 取り置き画面
こちらでは、"reservation_email"というカラムの情報有無によって表示する内容を変えております。
情報が有れば、「取り消す」ボタンが出てきます。ビュー(取り置き画面)%main.buy-main .buy-item-container %h2.buy-item-head 取り置き内容の確認 %section.buy-content.buy-item .buy-content-inner .buy-item-box .buy-item-image =image_tag(@product.images[0].product_image.url,class:"buy-image") .buy-item-detail %p.buy-item-name =@product.name %p.buy-item-price.bold = number_to_currency(@product.price,format: "%u%n",unit:"¥",precision: 0) %span.item-shipping-fee.f14.bold (税込)送料込み =form_for(@product, url: reserved_product_path,method: :patch) do |f| .form-group =f.label :お取り置きをする方のアドレス %span.form-group__require 必須 %br/ = f.email_field :reservation_email, {autofocus: true, autocomplete: "email", placeholder: "PC・携帯どちらでも可",class:'form-group__input'} = f.submit '取り置きする', class: "reserve" - if @product.reservation_email.present? =link_to reserve_cancel_product_path,method: :patch, class:"btn-default btn-red" do 取り置きをやめる機能実装について(コントローラ)
コントローラは以下のように実装してます。
ポイントは以下の通りになってます。
1. reservedアクションで入力された”reservation_email”をproductに追加します。
*この”reservation_email”が取置き品を購入するためのキーとなります。
2. 取り置きをキャンセルする時と購入がされた後は”reservation_email”のvalueをなくします。コントローラ(該当箇所のみ)before_action :set_product, only: [:reserved,:reserve,:reserve_cancel,:purchase] def reserve end def reserved #reservedアクションで入力された”reservation_email”をproductに追加します。 @product.update(product_params) if @product.reservation_email.present? else render :reserve end end def reserve_cancel #”reservation_email”のvalueをなくします。 if @product.update(reservation_email:"") redirect_to product_path else redirect_to product_path end end def purchase Payjp.api_key = Rails.application.secrets.payjp_access_key charge = Payjp::Charge.create( amount: @product.price, customer: Payjp::Customer.retrieve(@creditcard.customer_id), currency: 'jpy' ) #”reservation_email”がある場合は、valueをなくします。 if @product.reservation_email.present? @product.update(reservation_email:"") end @product_buyer= Product.find(params[:id]) @product_buyer.update( buyer_id: current_user.id) redirect_to purchased_product_path end private def product_params params.require(:product).permit(:name,:category_id,:price,:explain,:size,:brand_id,:status,:postage,:shipping_date,:prefecture,:reservation_email,images_attributes: [:product_image,:_destroy,:id]).merge(user_id: current_user.id) end def set_product @product = Product.includes(:comments).find(params[:id]) end参照
【Rails】updateメソッドの使い方を徹底解説!
https://pikawaka.com/rails/update【Rails】form_forの使い方を徹底解説!
https://pikawaka.com/rails/form_for#form_for%E3%81%A7%E3%81%AE%E4%BF%9D%E5%AD%98%E6%96%B9%E6%B3%95%E3%81%AE%E6%B3%A8%E6%84%8F%E7%82%B9Railsのモデルの作成、検索、更新、削除のよく使うメソッドのまとめ
https://ruby-rails.hatenadiary.com/entry/20140724/1406142120以上となります。最後までご覧いただき、ありがとうございました!
今後も学習した事項に関してQiitaに投稿していきますので、よろしくお願いします!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。
- 投稿日:2020-02-22T19:27:59+09:00
[取り置き機能]某フリマアプリにオリジナル機能を実装してみた。
某スクールのチーム開発にて某フリマアプリを作成しております。
先日、必須機能の実装が完了し、追加機能を実装していくフェーズになりました。
そこで、自分なりに色々と考えた結果、タイトルに記載いたしました「取り置き機能」を実装しました!
今回は、機能追加を決めるにあたっての背景含めて以下に記載いたします。今までは既存のコンテンツの実装であったため、様々な記事や情報があったため、自分で実装コンテンツや実装方法を考えるという機会は少し少なかった印象ありました。
しかしながら、今回はオリジナル機能であったため、実際のアプリユーザのことを考えながら実装をしていくのは、非常に楽しかったです!!より良いコードの書き方や修正すべき点などご意見ございましたら、是非いただけますと幸いです!
背景(「取り置き機能」を追加機能に選んだ理由)
今回、追加機能を選ぶにあたり、ネットで某フリマアプリユーザのコメントをネットで探したり、自分でサイトをいじっていました。
その結果、以下の状況が見えてきたため、今回の「取り置き機能」実装を決断しました。
- 問題・課題:
特定のユーザに販売する際に、出品写真やタイトルなどに”〇〇様向け”などと書くしかできず、他ユーザに購入できないようにするブロックができない。
その状況のため、別ユーザがユーザ名を偽ることで、本来購入をする予定であったユーザが購入できず、別のユーザに購入されてしまうという問題が発生している。「取り置き機能」実装に関する考え方
今回の機能実装に関して、以下のように考えました。
1. 商品を出品するユーザが、商品に対して”キー”を設定する。
2. その”キー”を持っているユーザは購入をできるが、”キー”を持っていないユーザは購入をできないようにする。ここで問題となったのは何を"キー”とするかでした。
「出品するユーザが合言葉を設定し、それを入力させる」や「ユーザ名を”キー”とする」などを考えましたが、どれもユニーク性がなかったためボツとし、今回は購入して欲しいユーザのemailアドレスを”キー”とすることにし,”reservation_email”というカラムを追加することにしました。
"reservation_email"の情報によって、入力された情報とログインしているユーザの情報が一致すれば、購入できる/一致しなければ、購入できないようにします。*実際はemailアドレスを"キー”とするのはプライバシーの観点からあまりよろしくはないと思われますため、可能で有れば、会員番号などを”キー”とするのがベターかと考えます。(今回は会員番号を設定していなかったため、アドレスにしました)
実装内容(イメージ)
今回実装した機能のイメージは以下の通りになってます。
出品ユーザ側機能
機能1:取り置きをする
機能2:取り置きをやめる
機能3:その他ユーザに購入できないようにする
購入ユーザ側機能
機能1:取置き品を購入する
*購入すると以下のように、SOLDとなります。
購入についての実装は、以下の記事をご参照ください。
[HowTo]Pay.jpを用いた商品購入機能実装から商品購入後の設定まで
https://qiita.com/Tatsu88/items/eb420e372077939a4627#%E5%95%86%E5%93%81%E8%B3%BC%E5%85%A5%E7%A2%BA%E8%AA%8D%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AE%E7%B7%A8%E9%9B%86機能実装:マイグレーションファイル
マイグレーションファイルは以下のように実装してます。
今回は、t.string :reservation_emailを追加してます。
こちらが、取り置き機能を実現するための要となります。
"reservation_email"の情報によって、入力された情報とログインしているユーザの情報が一致すれば、購入できる/一致しなければ、購入できないようにします。マイグレーションファイルclass CreateProducts < ActiveRecord::Migration[5.0] def change create_table :products do |t| t.string :name, limit: 191,null:false,index: true t.integer :price, index: true t.text :explain,null:false t.integer :postage,null:false t.integer :status t.integer :shipping_date t.integer :size t.integer :brand_id t.integer :category_id t.integer :prefecture t.integer :buyer_id t.references :user,index: true, foreign_key: true t.string :reservation_email t.timestamps end end end機能実装:ルーティング
ルーティングは以下のように実装してます。
'reserve'、'reserved',patch 'reserve_cancel'を今回追加してます。
内容としては、予約・予約完了・予約取り消しとなります。ルーティングresources :products do member do post 'purchase' get 'purchased' get 'buy' get 'reserve' patch 'reserved' patch 'reserve_cancel' end resources :comments,only:[:create,:destroy] end機能実装:ビュー
ビューは以下のように実装してます。
基本的に"reservation_email"の情報有無によって表示内容を変えてます。1. 商品一覧
こちらでは購入済みか取り置き済みかでサムネイルとして表示する写真にラベルを取り付けます。
view.product__thumbnail--image =image_tag (product.images[0].product_image.url) #購入されていれば、"SOLD"のラベルをつけます。 -if product.buyer_id.present? .items-box_photo__sold .items-box_photo__sold__inner SOLD #取り置きされていれば、"Reserved"のラベルをつけます。 -if product.reservation_email.present? .items-box_photo__reserved .items-box_photo__reserved__inner Reserved2. 商品詳細
こちらではユーザの状態によって、ビューの表示内容を変えてます。
view.product-buy__btn__box #ログインユーザが出品者の場合の、表示内容 - if user_signed_in? && current_user.id ==@product.user_id = link_to "削除する", product_path(@product.id), method: :delete,class:"product-details-delete__btn" = link_to "編集する", edit_product_path(@product.id),class:"product-details-edit__btn" = link_to "取り置きする/編集する", reserve_product_path(@product.id),class:"product-details-resorve__btn" #購入者向けの表示内容 #既に購入されている時 - elsif @product.buyer_id.present? = link_to "売り切れました",buy_product_path,class:"disabled-button bold" #取り置きされていて、その取り置きを商品を購入することを許可されているユーザの時 - elsif @product.reservation_email.present? && @product.reservation_email == current_user.email = link_to "取り置き商品を購入する",buy_product_path,class:"product-purchase__btn" #取り置きされていて、その取り置きを商品を購入することを許可されていないユーザの時 - elsif @product.reservation_email.present? && @product.reservation_email != current_user.email = link_to "取り置き商品のため購入できません",buy_product_path,class:"disabled-button bold" - else = link_to "購入画面に進む",buy_product_path,class:"product-purchase__btn"2. 取り置き画面
こちらでは、"reservation_email"というカラムの情報有無によって表示する内容を変えております。
情報が有れば、「取り消す」ボタンが出てきます。ビュー(取り置き画面)%main.buy-main .buy-item-container %h2.buy-item-head 取り置き内容の確認 %section.buy-content.buy-item .buy-content-inner .buy-item-box .buy-item-image =image_tag(@product.images[0].product_image.url,class:"buy-image") .buy-item-detail %p.buy-item-name =@product.name %p.buy-item-price.bold = number_to_currency(@product.price,format: "%u%n",unit:"¥",precision: 0) %span.item-shipping-fee.f14.bold (税込)送料込み =form_for(@product, url: reserved_product_path,method: :patch) do |f| .form-group =f.label :お取り置きをする方のアドレス %span.form-group__require 必須 %br/ = f.email_field :reservation_email, {autofocus: true, autocomplete: "email", placeholder: "PC・携帯どちらでも可",class:'form-group__input'} = f.submit '取り置きする', class: "reserve" - if @product.reservation_email.present? =link_to reserve_cancel_product_path,method: :patch, class:"btn-default btn-red" do 取り置きをやめる機能実装:コントローラ
コントローラは以下のように実装してます。
ポイントは以下の通りになってます。
1. reservedアクションで入力された”reservation_email”をproductに追加します。
*この”reservation_email”が取置き品を購入するためのキーとなります。
2. 取り置きをキャンセルする時と購入がされた後は”reservation_email”のvalueをなくします。コントローラ(該当箇所のみ)before_action :set_product, only: [:reserved,:reserve,:reserve_cancel,:purchase] def reserve end def reserved #reservedアクションで入力された”reservation_email”をproductに追加します。 @product.update(product_params) if @product.reservation_email.present? else render :reserve end end def reserve_cancel #”reservation_email”のvalueをなくします。 if @product.update(reservation_email:"") redirect_to product_path else redirect_to product_path end end def purchase Payjp.api_key = Rails.application.secrets.payjp_access_key charge = Payjp::Charge.create( amount: @product.price, customer: Payjp::Customer.retrieve(@creditcard.customer_id), currency: 'jpy' ) #”reservation_email”がある場合は、valueをなくします。 if @product.reservation_email.present? @product.update(reservation_email:"") end @product_buyer= Product.find(params[:id]) @product_buyer.update( buyer_id: current_user.id) redirect_to purchased_product_path end private def product_params params.require(:product).permit(:name,:category_id,:price,:explain,:size,:brand_id,:status,:postage,:shipping_date,:prefecture,:reservation_email,images_attributes: [:product_image,:_destroy,:id]).merge(user_id: current_user.id) end def set_product @product = Product.includes(:comments).find(params[:id]) end参照
【Rails】updateメソッドの使い方を徹底解説!
https://pikawaka.com/rails/update【Rails】form_forの使い方を徹底解説!
https://pikawaka.com/rails/form_for#form_for%E3%81%A7%E3%81%AE%E4%BF%9D%E5%AD%98%E6%96%B9%E6%B3%95%E3%81%AE%E6%B3%A8%E6%84%8F%E7%82%B9Railsのモデルの作成、検索、更新、削除のよく使うメソッドのまとめ
https://ruby-rails.hatenadiary.com/entry/20140724/1406142120以上となります。最後までご覧いただき、ありがとうございました!
今後も学習した事項に関してQiitaに投稿していきますので、よろしくお願いします!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。
- 投稿日:2020-02-22T19:27:01+09:00
rails:ページ移行で、(navbarなど)リンク表示を変更する
いらないリンクを消したい時
ログインページにいるのにログインリンク。新規登録ページにいるのにsignup(新規登録)のリンクはいらないですよね。
僕も今回、学習段階で実装する場面が来たので簡単にご紹介します。・新規登録ページ
navbarのリンクはlogin表示のみ。signup(新規登録)リンクは隠す
・ログインページ
navbarのリンクはsignup(新規登録)表示のみ。loginリンクは隠す
該当コード
request:ユーザのヘッダー情報や環境変数を取得
#新規登録画面におけるnavbarのコード <% unless request.path.include?("login") %> #"login"とのurlを含まなければtrue <li class="nav-item"><%= link_to 'Log in', login_path, class:'nav-link' %></li> <% end %> #ログイン画面におけるnavbarのコード <% unless request.path.include?("users/new") %> #"users/new"とのurlを含まなければtrue <li class="nav-item"><%= link_to 'Sign up', new_user_path, class:'nav-link'%></li> <% end %>#"signup"または"users/new"とのurlを含まなければtrue <% unless request.path.include?("signup") || request.path.include?("users/new")%> <li class="nav-item"><%= link_to 'Sign up', new_user_path, class:'nav-link'%></li> <% end %>補足
足りない部分や、間違っている箇所、もっときれいにコードを書ける部分があればご指摘いただきたいです。
- 投稿日:2020-02-22T16:46:06+09:00
ネストしたフォームを簡潔に実装できるcocoon gemをwebpack環境でセットアップする
前置き
- 公式レポジトリの説明(nathanvda/cocoon: Dynamic nested forms using jQuery made easy; works with formtastic, simple_form or default forms )には、Rails5までのインストール方法しか説明されていなかった
- Rails6環境でwebpackをつかったインストール方法を知りたかった
cocoonは、ネストしたフォームをサポートするgem
Railsガイドにも説明のあるネストしたフォーム。この実装をサポートしてくれるgemです。
「レシピ」と「手順」が1:Nの関連をもつときに、レシピの登録のとき手順も同時に(複数)作成するときなどに便利です。
レシピと手順の関連の例class Recipe < ApplicationRecord has_many :steps end class Step < ApplicationRecord belongs_to :recipe endこのようなフォームをつくれます。
環境
- Ruby (2.7.0)
- Rails (6.0.2.1)
- cocoon (1.2.14)
Gemfilegem 'cocoon'Rails6環境で、
cocoon gem
をつかうための前提知識以下の点を覚えておく必要があります。
- cocoonはjQueryに依存している
- Rails5.1以降はjQueryに依存していないので、デフォルトでは組み込まれていない
本題:webpackでcocoonを組み込む方法
1. jQueryをセットアップする
- webpackからjQueryを有効にする方法は、Rails6でjQueryの導入方法 - Qiitaが参考になりました。
2. cocoon をセットアップする
以下のスレッドを参考にしました。
Gemfileに追加し、
bundle install
します。Gemfilegem 'cocoon'jQueryとおなじように、
yarn add
で追加していきます。このとき、対象のライブラリ名の指定が特殊になります。(cocoon
ではなくてgithub:nathanvda/cocoon#c24ba53
になります。)yarn add github:nathanvda/cocoon#c24ba53
実行後に、
app/assets/javascripts/cocoon.js
にファイルができているはずです。package.jsonに以下の記述が追加されていればOK。package.json(追加された行)"cocoon": "github:nathanvda/cocoon#c24ba53"最後に application.js にimportを追加してください。
app/javascript/packs/application.jsimport "cocoon";よくわかっていないところ
コミット番号については、参照先のスレッドにも
コミット番号
#c24ba53
は重要な意味があるので省略してはいけない。という説明だけでした。このコミットでwebpackに対応した変更が入ったのだとおもわれます。(詳細未調査)
おわりに
cocoonの公式レポジトリにあるように、Rails6での使用法は明記されていません(正式対応はもうすこし先?)、ですのでここで説明した方法は今後のアップデートで変更になる場合がありますのでご了承ください。
フロントエンド側のライブラリ(cocoon, jQuery, bootstrapなど)はwebpackに管理させる方法にまだ慣れていなくて、前の方法で動作しない!と焦って調べ始めたことが今回の記事のきっかけでした。
おなじような状況にあったときに、もしかしてwebpackかも?と考える選択肢が増えると解決の糸口がみつかるかもしれませんね!
- 投稿日:2020-02-22T15:19:47+09:00
ユーザ情報をどうDB設計するか突き詰めて考えてみる
この記事について
- システム開発するうえで絶対に避けては通れないユーザ情報周りの設計を自分なりに考察してみた
- 結局何が最適かわからないけど色々と考え出すと奥が深かった
きっかけ的な
Railsでアプリ作ったんだけど、bootstrapとかdeviseとか使って横着したので、正直やった感ないなーって思いました。HTML/CSS多少勉強しなおして、あとこの本読んで、もう一回横着しないでなんちゃってQiitaみたいななんちゃってメディアシステムみたいなの作ろうかなと。そうなると、当然ユーザ情報の設計とか考えなきゃなんですが、いろいろ考えたら奥が深かったのでQiitaに載せて整理しようかなーって思いました。あと、ここに公開すればありがたいお言葉を頂戴いただけるのではないかとか思ったり。
本記事のユーザ情報の定義
利用用途
- ログイン認証に用いる。
- サインアップ画面より、新規登録をユーザ自身で可能とする。
- ユーザによるプロフィール編集を可能とする。
- プロフィールは他ユーザにより、CMS上のある画面から一覧表示できる。また、個別のユーザはユーザ名で検索可能とする。
- プロフィール詳細はほかユーザにより、CMS上のある画面から確認可能とする。
- ユーザ削除時は、ログイン情報も含めてすべて物理削除する
項目定義
- 一意のログインID、パスワードをもつ。また、最終ログイン日時を保持する。
- CMS内で利用される一意のユーザ名を保持する。
- 上記ユーザ名とは別でユーザの氏名、氏名カナを保持する。ユーザの氏名、氏名カナは他ユーザは見れないものとする。
- ユーザ登録時や運営お知らせに利用するためのメールアドレスを保持する。
- プロフィール情報として年齢、誕生日、自由記述欄を保持する。
私の設計
こうなりました。詳細は追ってですが、この後簡単に思考プロセスを書いていきます。なお、簡単にするため、インデックスどうするとかは一旦考えません。
テーブルにどう持たせるか
まあ単純に考えるとusersテーブルにえいやって全項目突っ込みたくなりますよね。こんな感じです。
別に個人で勉強用途に使う程度ならこれでいいんですが、ある程度ユーザ増えるという前提で考えると、ユーザに関する全操作のたびにテーブルがロックされて、パフォーマンス落ちるんじゃない?とかデータ量多くなって適切にインデックス設計とかしても限界ありそうだよねとか思ったわけです。あと、このテーブルにカラム追加するとか、検索のためのタグ付けするとか、そういうの考えるとなんだか背筋が凍ります。特に住所情報持たせるとかなったらとんでもないことになりそう。テーブルを分けてみる
要件に立ち返ると、認証、プロフィールとあるので、この観点で分けてみます。
ログインIDとパスワード、最終ログイン日時をauthenticatesとして定義しました。なお両テーブルは1:1で関連付けられています。
とりあえずusersテーブルのごった煮感はなくなりましたが、profileもう少しなんとかできないかなーとか思うので、もう少し分けてみます。
よく見るとほかユーザに見られてほしくない氏名の情報が混じってるのでこれは分けたほうがよさげ。と思ったのですが、、、
いい感じに分けられたんですけど、なんかセンスないというか、どう関連付けていいのかぱっと見わからないですね。。。
というわけで、fullnamesを親にして、profilesとauthenticatesと関連付けてみます。
なんかいい感じに見えました。でも親にfullnameってのがなんかあれですね。もう分けてしまいましょう。
いい感じになったけど、なんかこれでいいのか感があります。ちょっと大げさに分けすぎてしまったかな。。。
なんかもやっとしてますけど、これで終わりにします。名前のセンスないなとかそういうのはありますが。。。最後に
もやっとした感じで終わってしまったので、なんか不完全燃焼ですが、これはこれで結論ということで。
authenticatesとusersの関連付けっているのかな。正直model側でdestroyの依存をかけてしまうだけでいい気もする。でも、直接SQLで触られたりすると一気にデータがおかしくなる。。。
あと、Railsの規約って観点でもどうなのかなとも少し思ったりもした。
- 投稿日:2020-02-22T15:13:34+09:00
ユーザ情報のDB設計について突き詰めて考えてみる
この記事について
- システム開発するうえで絶対に避けては通れないユーザ情報周りの設計を自分なりに考察してみた
- 結局何が最適かわからないけど色々と考え出すと奥が深かった
きっかけ的な
Railsでアプリ作ったんだけど、bootstrapとかdeviseとか使って横着したので、正直やった感ないなーって思いました。HTML/CSS多少勉強しなおして、あとこの本読んで、もう一回横着しないでなんちゃってQiitaみたいななんちゃってメディアシステムみたいなの作ろうかなと。そうなると、当然ユーザ情報の設計とか考えなきゃなんですが、いろいろ考えたら奥が深かったのでQiitaに載せて整理しようかなーって思いました。あと、ここに公開すればありがたいお言葉を頂戴いただけるのではないかとか思ったり。
本記事のユーザ情報の定義
利用用途
- ログイン認証に用いる。
- サインアップ画面より、新規登録をユーザ自身で可能とする。
- ユーザによるプロフィール編集を可能とする。
- プロフィールは他ユーザにより、CMS上のある画面から一覧表示できる。また、個別のユーザはユーザ名で検索可能とする。
- プロフィール詳細はほかユーザにより、CMS上のある画面から確認可能とする。
- ユーザ削除時は、ログイン情報も含めてすべて物理削除する
項目定義
- 一意のログインID、パスワードをもつ。また、最終ログイン日時を保持する。
- CMS内で利用される一意のユーザ名を保持する。
- 上記ユーザ名とは別でユーザの氏名、氏名カナを保持する。ユーザの氏名、氏名カナは他ユーザは見れないものとする。
- ユーザ登録時や運営お知らせに利用するためのメールアドレスを保持する。
- プロフィール情報として年齢、誕生日、自由記述欄を保持する。
私の設計
こうなりました。詳細は追ってですが、この後簡単に思考プロセスを書いていきます。なお、簡単にするため、インデックスどうするとかは一旦考えません。
テーブルにどう持たせるか
まあ単純に考えるとusersテーブルにえいやって全項目突っ込みたくなりますよね。こんな感じです。
別に個人で勉強用途に使う程度ならこれでいいんですが、ある程度ユーザ増えるという前提で考えると、ユーザに関する全操作のたびにテーブルがロックされて、パフォーマンス落ちるんじゃない?とかデータ量多くなって適切にインデックス設計とかしても限界ありそうだよねとか思ったわけです。あと、このテーブルにカラム追加するとか、検索のためのタグ付けするとか、そういうの考えるとなんだか背筋が凍ります。特に住所情報持たせるとかなったらとんでもないことになりそう。テーブルを分けてみる
要件に立ち返ると、認証、プロフィールとあるので、この観点で分けてみます。
ログインIDとパスワード、最終ログイン日時をauthenticatesとして定義しました。なお両テーブルは1:1で関連付けられています。
とりあえずusersテーブルのごった煮感はなくなりましたが、profileもう少しなんとかできないかなーとか思うので、もう少し分けてみます。
よく見るとほかユーザに見られてほしくない氏名の情報が混じってるのでこれは分けたほうがよさげ。と思ったのですが、、、
いい感じに分けられたんですけど、なんかセンスないというか、どう関連付けていいのかぱっと見わからないですね。。。
というわけで、fullnamesを親にして、profilesとauthenticatesと関連付けてみます。
なんかいい感じに見えました。でも親にfullnameってのがなんかあれですね。もう分けてしまいましょう。
いい感じになったけど、なんかこれでいいのか感があります。ちょっと大げさに分けすぎてしまったかな。。。
なんかもやっとしてますけど、これで終わりにします。名前のセンスないなとかそういうのはありますが。。。最後に
もやっとした感じで終わってしまったので、なんか不完全燃焼ですが、これはこれで結論ということで。
authenticatesとusersの関連付けっているのかな。正直model側でdestroyの依存をかけてしまうだけでいい気もする。でも、直接SQLで触られたりすると一気にデータがおかしくなる。。。
あと、Railsの規約って観点でもどうなのかなとも少し思ったりもした。
- 投稿日:2020-02-22T14:13:59+09:00
Railsチュートリアル bitbucketにプッシュできない
bitbucketにプッシュする方法。
軽くハマったので備忘録。git push -u origin --allだと失敗するので、
git push --mirror git@bitbucket.org:[ユーザー名]/sample_appこっちを使いましょう。
- 投稿日:2020-02-22T14:11:09+09:00
[Ruby on Rails]画像投稿ボタンをFont Awesomeのアイコンにする方法
概要
表題の通りですが、調べるとHaml記法で書かれた投稿しか見つけられず、今のところHaml記法を使っていない私にはわかりづらかったので、メモがてら共有させていただきます。
環境
Ruby:2.6.3
Rails:5.1.6
bootstrap:4.4.1
FontAwesome方法
1.file_fieldで画像投稿ボタンを表示。
2.file_fieldボタンによって出力されるinputタグを見えなくする(dislay:none)。
3.file_fieldの上にFont Awesomeのアイコンをiタグで表示。
4.iタグをlabelタグで囲って、アイコンをボタンとして有効にする。home.html.erb<%= form_for(@dreampost) do |f| %> <div class="field"> <%= f.text_area :content, placeholder: "投稿できます" %> </div> <div class="space-between"> <span class="picture"> <label for="dreampost_picture"> <i class="far fa-image"></i> </label> <%= f.file_field :picture, placeholder: '',accept: 'image/jpeg,image/gif,image/png' %> <div class="clear"></div> </span> <%= f.submit "送信", class: "btn btn-primary" %> <% end %>custom.scss.space-between { display: flex; justify-content: space-between; } .picture>input { display: none; } .picture>label { margin-bottom: 0; float: left; } .fa-image { color: #fff; float: left; } .fa-image::before { font-size: 2rem; } .clear { clear: both; }補足
div.classに{ display: flex; justify-content: space-between;}と、
labelタグとiタグにfloat:leftで、
アイコンを左寄せ、送信ボタンを右寄せにしています。ご指摘などございましたら、ぜひよろしくお願いいたします。
- 投稿日:2020-02-22T13:23:03+09:00
Rails でトークン認証 API を 15 分で実装する
下記を作成してみます。SPA のバックエンド側を想定しています。
/auth
でログインし token を発行してもらえる。/users
でユーザー一覧を取得できる。
- ただし token が必要。
- 更に admin ユーザーのみでき、member ユーザーはアクセス許可がない。
上記を利用したフロント側の記事も書いておりますので宜しければご覧ください。
Vue.js で簡単なログイン画面 (トークン認証) を作ってみたUser API 作成
rails new で API モードで新規アプリ作成します。
$ rails new yourappname --apiscaffold で User Model と Controller を作ります。
lock_version というカラムを追加すると Rails で楽観ロックを実装してくれます。便利ですね。$ cd yourappname $ rails g scaffold User name:string \ email:string \ role:integer \ password_digest:string \ register_user:integer \ update_user:integer \ lock_version:integer \ activated_at:datetime \ deleted_at:datetimeseeds に初期データを書いてみましょう。
db/seeds.rbUser.create!([ { name: 'admin', email: 'admin@example.com', role: 'admin', password_digest: '$2a$10$HjQH2VBdguACJLyZHoVSs.yBZbwypqY3vUJGnxlWj94rmilWIuWzK', register_user: 1, update_user: 1, lock_version: 0, activated_at: '2020-02-03 00:00:00', deleted_at: nil, }, { name: 'member', email: 'member@example.com', role: 'member', password_digest: '$2a$10$HjQH2VBdguACJLyZHoVSs.yBZbwypqY3vUJGnxlWj94rmilWIuWzK', register_user: 1, update_user: 1, lock_version: 0, activated_at: '2020-02-03 00:00:00', deleted_at: nil, }, ])開発用の DB をマイグレーション & 初期データを投入します。
$ rails db:migrate && rails db:seedサーバーを起動します。
$ rails s
別ターミナルにて curl で叩くと初期データが返ってきます。
まずは簡単な API ができたことを確認します。# 別ターミナル $ curl -s http://localhost:3000/users | jq [ { "id": 1, "name": "admin", "email": "admin@example.com", "role": "admin", "password_digest": "$2a$10$HjQH2VBdguACJLyZHoVSs.yBZbwypqY3vUJGnxlWj94rmilWIuWzK", "register_user": 1, "update_user": 1, "lock_version": 0, "activated_at": "2020-02-03T00:00:00.000Z", "deleted_at": null, "created_at": "2020-02-22T04:08:58.769Z", "updated_at": "2020-02-22T04:08:58.769Z" }, { "id": 2, "name": "member", "email": "member@example.com", "role": "member", "password_digest": "$2a$10$HjQH2VBdguACJLyZHoVSs.yBZbwypqY3vUJGnxlWj94rmilWIuWzK", "register_user": 1, "update_user": 1, "lock_version": 0, "activated_at": "2020-02-03T00:00:00.000Z", "deleted_at": null, "created_at": "2020-02-22T04:08:58.775Z", "updated_at": "2020-02-22T04:08:58.775Z" } ]テストも実行してエラーが無いことも確認しましょう。
$ rails t Running via Spring preloader in process 16858 Run options: --seed 26013 # Running: ..... Finished in 0.320389s, 15.6060 runs/s, 21.8484 assertions/s. 5 runs, 7 assertions, 0 failures, 0 errors, 0 skipsちなみに jq が入っていない場合は入れると JSON 整形してくれて便利です。
$ brew install jqGemfile もろもろ
Gemfile は Node.js で言う、package.json 的なファイルだと思います。
下記を追記します。ついでにオススメの gem も。
gem 'redis-rails' # Redis を扱うための gem gem 'mock_redis' # Redis のモック。テスト実行時に使用。 gem 'config' # 環境ごとに yml の設定ファイルを作成可能。 gem 'pundit' # 認証周りを REST ベースでシンプルに実装できる。 gem 'paranoia' # 論理削除できる。下記をコメントアウトします。
has_secure_password を使う際に必要です。
これはテーブルに password_digest というカラムを用意すると、Rails がパスワードをハッシュ化してくれます。gem 'bcrypt', '~> 3.1.7'bundle install するとパッケージがインストールされます。
$ bundle installconfig をインストールすると rails g config:install が使えます。
環境ごとに yml ファイルができます。便利ですね。$ rails g config:install Running via Spring preloader in process 17408 create config/initializers/config.rb create config/settings.yml create config/settings.local.yml create config/settings create config/settings/development.yml create config/settings/production.yml create config/settings/test.yml append .gitignoreRedis 設定
セッション情報を保存するためにインメモリ DB の Redis を使います。
Mac の方は brew でインストール。$ brew install redisredis-server で起動できます。簡単でいいですね。
$ redis-server
いったん、全環境共通の settings.yml に url と timeout を追記します。
こうすると例えば、下記の url の値を取り出すにはSettings.session.url
と記述すれば OK です。config/settings.ymlsession: url: redis://localhost:6379 timeout: 7200 role: member: member admin: adminところで Redis 接続時に毎回 Redis.new() を書くのは不便なので、initializers/ 下に redis.rb を作成します。
ついでにテストの時は Redis 立ち上げなくても済むようにしました。config/initializers/redis.rbif Rails.env.test? REDIS = MockRedis.new else REDIS = Redis.new(url: Settings.session.url) endAuth コントローラー作成
コマンドラインから枠を作ります。
$ rails g controller auth
auth のルーティングを追加します。
個人的にはなるべく resources を使うようにすると綺麗だと思います。config/routes.rbRails.application.routes.draw do resources :users + resources :auth, :only => [:create, :destroy] endセッション作成処理を concerns に切り出してみます。
app/controllers/concerns/session.rbmodule Session def self.create(user) token = SecureRandom.hex(64) REDIS.mapped_hmset( token, 'user_id' => user.id, 'role' => user.role, ) REDIS.expire(token, Settings.session.timeout) return token end endAuthController で先ほど作った Session モジュールを利用します。
app/controllers/auth_controller.rbclass AuthController < ApplicationController + def create + user = User.find_by(email: params[:email]) + token = '' + status = :unauthorized + if user && user.authenticate(params[:password]) + token = Session.create(user) + status = :created + end + render json: { token: token }, status: status + end endmodels/user.rb に下記を追記。
物理削除とパスワードハッシュ化を利用します。
enum で member と admin も定義しておきます。app/models/user.rbclass User < ApplicationRecord + acts_as_paranoid + has_secure_password + enum role: { member: 0, admin: 1 } end動作確認してみましょう。
ログイン OK
まずは seeds.rb に記述した email と password で /auth を叩きます。
token が返ってきますね$ curl -s \ -X POST http://localhost:3000/auth \ -H "Content-Type: application/json" \ -d '{"email": "admin@example.com", "password": "password1234"}' {"token":"ed64bbcf830e4feca9203b0955c5916fda815a23da80da8a7bb62b8c6466dbc34a07e21e9dd96447882fc8b9cb06121c8ee414b79ffedef4892e7197bbe9edd1"}redis-cli で Redis に接続して、トークンが作成されたか確認してみましょう。
おお、できています!$ redis-cli 127.0.0.1:6379> keys * 1) "ed64bbcf830e4feca9203b0955c5916fda815a23da80da8a7bb62b8c6466dbc34a07e21e9dd96447882fc8b9cb06121c8ee414b79ffedef4892e7197bbe9edd1"ログイン NG
password を適当に変えてみます。
token が空になっていますね。$ curl -s -X POST http://localhost:3000/auth \ -H "Content-Type: application/json" \ -d '{"email": "admin@example.com", "password": "wrongpassword"}' {"token":""}認証を設定
Session モジュールにセッション情報を取得する function を追加します。
app/controllers/concerns/session.rbmodule Session + def self.get(token) + REDIS.hgetall(token) + end def self.create(user) token = SecureRandom.hex(64) REDIS.mapped_hmset( token, 'user_id' => user.id, 'role' => user.role, ) REDIS.expire(token, Settings.session.timeout) return token end end続いて基幹コントローラーに手を入れます。
authenticate_with_http_token
を利用すると、 リクエストヘッダに 'Authorization: Token hogehoge' がセットされていた場合に、トークン hogehoge を取り出せます。
- 上記を利用するには
include ActionController::HttpAuthentication::Token::ControllerMethods
する必要があります。before_action :set_session
で、Redis に登録してあるセッション (ここでは user.id と user.role ) をメンバにセットしています。before_action :require_login
で、基本的にセッションがない場合に認証エラーとしています。app/controllers/application_controller.rbclass ApplicationController < ActionController::API + include ActionController::HttpAuthentication::Token::ControllerMethods + + before_action :set_session + before_action :require_login + + @session = {} + + def require_login + render json: { error: 'unauthorized' }, status: :unauthorized if @session.empty? + end + + private + def set_session + authenticate_with_http_token do |token, options| + @session = Session.get(token) + end + end endAuthController に skip_before_action を追加します。
これを追加しないと一生ログインができません・・。app/controllers/auth_controller.rbclass AuthController < ApplicationController + skip_before_action :require_login, only: [:create] + def create user = User.find_by(email: params[:email]) token = '' status = :unauthorized if user && user.authenticate(params[:password]) token = Session.create(user) status = :created end render json: { token: token }, status: status end endユーザー一覧 OK
Authorization: Token xxx
には、先ほど /auth を叩いて得られた token をセットします。
/users の結果が返ってきています。$ curl -s http://localhost:3000/users \ -H "Content-Type: application/json" \ -H "Authorization: Token ed64bbcf830e4feca9203b0955c5916fda815a23da80da8a7bb62b8c6466dbc34a07e21e9dd96447882fc8b9cb06121c8ee414b79ffedef4892e7197bbe9edd1" \ | jq [ { "id": 1, "name": "admin", "email": "admin@example.com", "role": 0, "password_digest": "$2a$10$HjQH2VBdguACJLyZHoVSs.yBZbwypqY3vUJGnxlWj94rmilWIuWzK", "register_user": 1, "update_user": 1, "lock_version": 0, "activated_at": "2020-02-03T00:00:00.000Z", "deleted_at": null, "created_at": "2020-02-21T14:28:10.627Z", "updated_at": "2020-02-21T14:28:10.627Z" }, { "id": 2, "name": "member", "email": "member@example.com", "role": 0, "password_digest": "$2a$10$HjQH2VBdguACJLyZHoVSs.yBZbwypqY3vUJGnxlWj94rmilWIuWzK", "register_user": 1, "update_user": 1, "lock_version": 0, "activated_at": "2020-02-03T00:00:00.000Z", "deleted_at": null, "created_at": "2020-02-21T14:28:10.635Z", "updated_at": "2020-02-21T14:28:10.635Z" } ]ユーザー一覧 NG
token を適当なものに変えるとちゃんと認証エラーになりました
$ curl -s http://localhost:3000/users -H "Content-Type: application/json" -H "Authorization: Token wrong_token" | jq { "error": "unauthorized" }権限周り設定
Web アプリでは権限は必須と言えます。
Pundit を使うと Rest ベースでシンプルに実装できます。$ rails g pundit:install $ rails g pundit:policy userまずは application_policy.rb に管理者かどうかの admin? を追加してみます。
app/policies/application_policy.rbclass ApplicationPolicy attr_reader :user, :record def initialize(user, record) @user = user @record = record end def index? false end def show? false end def create? false end def new? create? end def update? false end def edit? update? end def destroy? false end + def admin? + @user['role'] == Settings.role.admin + end class Scope attr_reader :user, :scope def initialize(user, scope) @user = user @scope = scope end def resolve scope.all end end end続いて、user_policy.rb の index に先ほど作った admin? を追加します。
app/policies/user_policy.rbclass UserPolicy < ApplicationPolicy class Scope < Scope def resolve scope.all end end + def index? + admin? + end endあとは利用側です。
application_controller.rb で Pundit を include し、current_user メソッドを追加します。
また、Pundit の NotAuthorizedError を拾えるように rescue_from を追加します。
(エラー処理は増えてきたら concerns に切り出すと良いと思います。)app/controllers/application_controller.rbclass ApplicationController < ActionController::API include ActionController::HttpAuthentication::Token::ControllerMethods + include Pundit before_action :set_session before_action :require_login @session = {} + rescue_from Pundit::NotAuthorizedError do |e| + render json: { detail: e.message }, status: :unauthorized + end + def require_login render json: { error: 'unauthorized' }, status: :unauthorized if @session.empty? end + def current_user + @session + end private def set_session authenticate_with_http_token do |token, options| @session = Session.get(token) end end end後は users_controller.rb の index に Pundit を追加します。
app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :set_user, only: [:show, :update, :destroy] # GET /users def index - @users = User.all - - render json: @users + users = authorize Pundit.policy_scope(@session, User) + render json: users end # GET /users/1 def show render json: @user end # POST /users def create @user = User.new(user_params) if @user.save render json: @user, status: :created, location: @user else render json: @user.errors, status: :unprocessable_entity end end # PATCH/PUT /users/1 def update if @user.update(user_params) render json: @user else render json: @user.errors, status: :unprocessable_entity end end # DELETE /users/1 def destroy @user.destroy end private # Use callbacks to share common setup or constraints between actions. def set_user @user = User.find(params[:id]) end # Only allow a trusted parameter "white list" through. def user_params params.require(:user).permit(:name, :email, :role, :password_digest, :register_user, :update_user, :lock_version, :activated_at, :deleted_at) end endさて、動作確認してみましょう。
まずは member ユーザーでログインし、/users を叩きます。
ユーザー一覧が取得できないことを確認します。$ curl -s -X POST http://localhost:3000/auth -H "Content-Type: application/json" -d '{"email": "member@example.com", "password": "password1234"}' {"token":"482125111d11c2882cad25b700221a45bc64d8745771f427cb8039337c0e7cda95fc8498f98d6d784781b177afe84699bccb2d62628b1bee3ccee0e96eb9e576"} $ curl -s http://localhost:3000/users -H "Content-Type: application/json" -H "Authorization: Token 482125111d11c2882cad25b700221a45bc64d8745771f427cb8039337c0e7cda95fc8498f98d6d784781b177afe84699bccb2d62628b1bee3ccee0e96eb9e576" {"error":"not allowed to index? this User::ActiveRecord_Relation"}続いて admin ユーザーでログインし、/users を叩きます。
結果が取得できましたね$ curl -s -X POST http://localhost:3000/auth -H "Content-Type: application/json" -d '{"email": "admin@example.com", "password": "password1234"}' {"token":"a87dee3d5cb1592e3b3b09f78931e26254292f1453ea77a1d491fd949ce508d5ea7e00dec6163fb83ed9c7f5d39b672aa429c770a6d0487952022fc55a493487"} $ curl -s http://localhost:3000/users -H "Content-Type: application/json" -H "Authorization: Token a87dee3d5cb1592e3b3b09f78931e26254292f1453ea77a1d491fd949ce508d5ea7e00dec6163fb83ed9c7f5d39b672aa429c770a6d0487952022fc55a493487" [{"id":1,"name":"admin","email":"admin@example.com","role":"admin","password_digest":"$2a$10$HjQH2VBdguACJLyZHoVSs.yBZbwypqY3vUJGnxlWj94rmilWIuWzK","register_user":1,"update_user":1,"lock_version":0,"activated_at":"2020-02-03T00:00:00.000Z","deleted_at":null,"created_at":"2020-02-11T12:14:52.557Z","updated_at":"2020-02-11T12:14:52.557Z"},{"id":2,"name":"member","email":"member@example.com","role":"member","password_digest":"$2a$10$HjQH2VBdguACJLyZHoVSs.yBZbwypqY3vUJGnxlWj94rmilWIuWzK","register_user":1,"update_user":1,"lock_version":0,"activated_at":"2020-02-03T00:00:00.000Z","deleted_at":null,"created_at":"2020-02-11T12:14:52.565Z","updated_at":"2020-02-11T12:14:52.565Z"}]
- 投稿日:2020-02-22T11:22:42+09:00
画像を希望ページへリンクさせる
画像を希望ページへリンクさせてクリックしたら遷移させる
上記の機能を実装する際、手こずったので備忘録として残しておきます。
aタグでリンクさせようと思い、書き方を調べて相対パスで記述する事にした。
aタグ パス指定
下記のコードで実行したが、Routing Errorが起きた。tops/index.html.erb<a href="groups/index.html.erb"><img src="assets/1.png" class="img-fluid" alt="Responsive image"></a>下記の方法も試したが、同様にRouting Eroorが起きた。
| ◆ リンク先などの記述例 |
同じフォルダ内にリンクするときは、ファイル名のみ(または ./ に続けてファイル名)を記述します。
下位階層にリンクするときは、フォルダ名に続けて / を入れ、ファイル名を記述します。
上位階層にリンクするときは、../ に続けてファイル名を記述します。2つ上の階層を指定するときは、../../ に続けてファイル名を記述します。
並列した階層のフォルダにリンクするときは、../ に続けてフォルダ名と / を入れ、ファイル名を記述します。解決法
link_toを使用して、記述することにした。
まずは、ルーティングの設定routes.rbRails.application.routes.draw do devise_for :users root 'tops#index' resources :groups, only: :index end次に、コントローラー
groups_controller.rbclass GroupsController < ApplicationController def index end end最後にビュー
tops/index.html.erb<%= link_to (groups_path) do%> <img src="assets/1.png" class="img-fluid" alt="Responsive image"> <% end%>上記手順で画像リンクに成功した。
なぜaタグでできなかったのか?
・railsでリンクさせる場合は相対パスが使用できない?
・rails,rubyのバージョンが相対パスの仕様に対応してない?
(rails 5.0.7.2 , ruby 2.5.1)確証が持てないのでわかる方はぜひコメントお願いします!!!!!
- 投稿日:2020-02-22T10:24:08+09:00
【Rails+Redis】セッション管理をredisに任せる
現在作成してるrailsポートフォリオで、せっかくだからセッション管理はredisに任せようと思い、色々いじったので共有したいと思います。
環境
- macOS
- Docker
- RubyonRails
- Redis
- MySQL
前提
- Redisはすでにインストール済み
- DockerにもRedisは追加済み
セッション管理をRedisに任せる理由
RedisはRDBとは違って、
- KVS
- データをメモリに保つので動作が高速
という特徴があります。
この特徴からセッション管理に向いてるとして結構使われることが多いみたいです。
さっそく実装してみる
ではさっそく実装しましょう。
gemの追加
Gemfileに以下を追加してインストールしてください。
# Use Redis adapter to run Action Cable in production gem 'redis', '~> 4.0' gem 'redis-rails'config/initializers/session_store.rbの設定
初期設定はコメントアウトしておきましょう。
最初からない人は問題ないです。config/initializers/session_store.rb#Rails.application.config.session_store :cookie_store, key: 'XXXXXX'
config/environments/development.rbの設定
config/environments/development.rbに以下を追加。
config/environments/development.rbconfig.session_store :redis_store, servers: 'redis://redis:6379/0', expire_in: 1.dayこれで一日セッションが保たれる設定になってるはずです。
確認
以下のコマンドでデータが入ってるか確認しましょう。
docker exec -it コンテナID redis-cli 127.0.0.1:6379> keys *1) "2::63c3d325054ad936b6408b30ba8c4e1395cce110d77c3e5171c7e22840d75a9d" 2) "2::383c6dfd3c95d07631fc8e879911fef09760a789461d8b7d0736a32d7d8ee656"keys * で出てきたkeyを使って中身を確認。
127.0.0.1:6379> get "2::63c3d325054ad936b6408b30ba8c4e1395cce110d77c3e5171c7e22840d75a9 d"何か長い謎の文字列が出てきたら情報がしっかり入ってることになっていると思います。
試しにredisのデータを全部削除したら、アプリがログイン画面に戻りましたので、成功と見て問題ないでしょう。
まとめ
以前Redisを利用した時はランキング機能の実装だったので、また違った角度でRedisを扱えたことは勉強になりました。
ぶっちゃけ、まだRedisの知識がちゃんと身に付いてるとは言い難いですが、これからも色々利用しながら学んでいきたいと思います。
ではでは。
- 投稿日:2020-02-22T10:08:06+09:00
Ruby on Rails チュートリアル学習記録 第1章
はじめに
初投稿なので簡単に自己紹介。
私は独学でプログラミング学習を始めたアラサーの男性です。
未経験からエンジニアへの転職を目指しています。
ProgateでHTML/CSS/Ruby/Ruby on Rails5をひと通り学習後Railsチュートリアル開始。
Progateと違い詰まったら自分で調べたり考えたりしながら進めていく必要がありそうなので学習記録を付けることにしました。
主に自分が詰まったところについて書いていくつもりなので章ごとに文章量は疎らになっていくと思います。1.1
特になし
1.2
特になし
1.3
gemfileの内容を書き換え
bundle install
を実行するとエラー発生You have requested: spring = 2.0.2 The bundle currently has spring locked at 2.1.0. Try running `bundle update spring` If you are updating multiple gems in your Gemfile at once, try passing them all to `bundle update`
bundle update
を実行後、bundle install
を再度実行で解決。1.4
Rails チュートリアルにおいては、マニュアル作成時点ではGithubで非公開型レポジトリを無料で使えなかったためにBitbucketを使用していようだが、現在はGitHubでも非公開型レポジトリを無料で使えるようになっていたので、自分は既にアカウントを作っていたGitHubを使用。
GitHubでもチュートリアル通りに進めて特に問題は発生せず。1.5
特になし
1.6
特になし
感想
マニュアル通りに進めていれば特に困ることはなかった。
正直やってることの意味はきちんと理解できていない部分もあると思うが、どうせ2週やるつもりなのでそこはスピード感重視で。
ファイルの内容を変更した後、保存をし忘れてうまくいかないというケアレスミスが何度かあったので気を付けたい。
- 投稿日:2020-02-22T07:46:03+09:00
【これで無理なら諦めろ】オブジェクト指向を世界一分かりやすく説明する
はじめに
個人的にオブジェクト指向を理解するのに苦戦しまくったので、その真髄を世界一分かりやすく説明していきます。
何か間違いなどあればどんどんご教示お願いします。言語はRubyです。対象読者
- Ruby、Railsを勉強中の方
- 業務でRubyを使っている方
- チェリー本やRailsチュートリアルで挫折しちゃった人
オブジェクト指向とは
オブジェクト指向とは、プログラムを「手順」ではなく、「モノの作成と反応」として見る考え方です。これだけではイマイチ分からないと思うので、具体的なコードを使って説明していきます。
オベジェクト指向を理解するには、手続き型のコードを知るのが早いです。
例えば、ある数字を加工して、その数字を使って文章を作って、結果を出力するとき、手続き型では以下のように書きます。
def calculate(number_1, number_2) number_1 * number_2 + 1 end def update_content(value) "結果は#{value}です" end def output(content) puts number end value = 100 content = "" new_value = calculate(value,20) // 2001 content = update_content(new_value) // "結果は2001です" output(content) => "結果は2001です"一方で、オブジェクト指向で書くとこんな感じ。
num = Num.new(100,"") num.calculate(20) // 2001 num.update_content // "結果は2001です" num.output => "結果は2001です"なんとなく違いは分かりますか?
前者は、先に関数を定義して、処理を実行するたびにいろんなメソッドに値を飛ばして、返ってきた値を変数に代入して...
と流れるようにプログラムが進んでいきます。なんだか忙しそうですよね?
一方で後者は、はじめに
num
というモノを作成し、そいつに対して「計算して」「中身を更新して」「出力して」と呼びかけるような感じで処理が進んでいきます。情報の記憶も処理の内容も「モノ」が全部やってくれるので、一度作ってしまえば見通しよく実行できるわけです。
この後者こそがまさに、オブジェクト指向の考え方であり、今回でいう所の
num
がオブジェクトと呼ばれるものです。つまり、オブジェクト指向とは、コードにオブジェクトという主人公を登場させ、それをメインに話を進めていく設計の考え方といえます。
やっぱり、ドラマを見るにも、主人公がいた方が面白いし分かりやすいじゃないですか。
プログラミングも同じで、処理の中に主人公がいた方が読みやすくて扱いやすくなるわけです。ちなみにnumというオブジェクトは以下のクラスから作成されていました。へぇ〜ぐらいで流してください。
num.rbclass Num attr_accessor :value, :content def initialize(value, content) @value = value @content = content end def calculate(number) self.value = value * number + 1 end def update_content self.content = "計算結果は#{value}です" end def output puts content end end num = Num.new(100,"") num.calculate(100) num.update_content num.outputオブジェクトとは
先ほどの例で少しはオブジェクト指向の雰囲気を掴めたかと思います。
でば、オブジェクトとは一体なんでしょうか?こいつの正体を掴むのに僕は半年ほどかかりました。そして、僕が腑に落ちた例えで分かりやすーーーく説明します。
結論から言うと、オブジェクトとは魔法の箱です。
この魔法の箱は2つの性質を持ちます。
- 情報を持つ
- 反応する
プログラミングをやっていると、オブジェクトが色んなことをしているように見えますが、結局やっていることはこの2つだけなんです。
情報を保持するから「箱」、メッセージに対して反応できるから「魔法の」と言う例えにしました。
オブジェクトとは、情報を持ち、メッセージに対して反応するただの箱なんだ。
とりあえず今の段階ではそのように理解してもらえれば大丈夫です。
1. 情報を持つとは?
オブジェクトは情報を持つことができます。分かりやすいのが以下の例。
user.name => 山田 user.age => 18これはuserという魔法の箱が
name
とage
という2つの情報を持っていることを示しています。すごく簡単ですね。この情報はいつでも呼び出すことができますし、その気になれば更新することも消すこともできます。
2. 反応するとは?
オブジェクトはあるメッセージに対して反応することもできます。
user.hello => "おはようございます。"これまで5万回は見たhelloの例です。
これは、hello
というメッセージを伝えたら"おはようございます。"
と返すようにuser
という魔法の箱が反応したと見ることができます。userをレシーバ、helloをメッセージと呼ぶのはこのような理由です。
ちなみに、そこらへんのゴミ箱にhelloと話しかけても何も返事しません。
dustbox.hello => NameError (undefined local variable or method `dustbox' for main:Object)userという箱が反応してくれたのは、事前に反応パターンを記憶させたからです。
class User def hello puts "おはようございます。" end endこのように、魔法の箱(オブジェクト)に教え込む反応方法のことをメソッドと言います。
では、この教えこむ場所のことを(学校の)クラスと呼ベばかなり分かりやすいのではないかと閃きました。
言い換えれば、classとは魔法の箱の養成クラスとでもいいましょうか。すごく簡単にまとめると...
- オブジェクトとは魔法の箱である
- 魔法の箱は、情報を持ったり、メッセージに対して反応したりできる
- 魔法の箱に教え込む反応パターンのことをメソッドと呼ぶ
- 反応パターンを教え込む場所のことをクラスと呼ぶ
- オブジェクト指向とは、プログラムを「手順」ではなく、「魔法の箱(オブジェクト)の反応」と見る考え方
以上が、オブジェクト指向の説明になります。
この記事でオブジェクト指向への理解が少しでも深まれば幸いです。追記1
Numクラスのソースコードに間違いがあったため、修正いたしました。
ご指定いただいた方々ありがとうございます。変更点
Class Num => class Num
へ修正- 属性名を
num => value
へ変更。(属性名とインスタンス名が同じだとややこしいため)- caluculateメソッドにおける、
value = => self.value
に修正(属性を上書きする際はself.の省略はできないようです。)
- 投稿日:2020-02-22T07:44:31+09:00
【Ruby on Rails で簡単!】PAY.JPを利用したクレジットカード決済の導入
何かサービスを作る際に、決済機能を導入したいはずです。
今回はPAY.JPを利用した決済機能を案内していきます。PAY.JPの導入準備
スクリプトの記述
PAY.JPを使うためのスクリプトを記述します。
下記をコピーしてください。スクリプト%script{src: "https://js.pay.jp/", type: "text/javascript"}コピーしたら、application.html.hamlに貼り付けましょう!!
application.html.haml%html %head %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %title payjptest %script{src: "https://js.pay.jp/", type: "text/javascript"} -# このscriptを記載 = csrf_meta_tags = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %body = yieldPAY.JPの登録
PAY.JPのアカウントを作成しましょう!
https://pay.jp/APIキーを取得します
アカウントを作成してログインし、下記の場所のAPIキーを確認しましょう!
Appにgem 'payjp'を追加
Gemfile# PAY.JPのgem gem 'payjp' # 環境変数を簡単に定義できるENVファイルを対応させるgem gem 'dotenv-rails'追加したら
ターミナル$ bundle install $ rails s再起動しないとgemもscriptも読み込まれないので、エラーがおきます。
環境変数を利用して、APIキーをAppに登録
gem 'dotenv-rails'をインストールできたので、.envファイルを作成しましょう
app > .env の場所に作成します。
.gitignoreの上だと思えば簡単です。
秘密鍵をGithubにコミットしてしまうとAPIキーを世に公開してしまうので、.gitignoreに.envを記述します
gitignore/.env
ここ本当に重要なので、注意してくださいね!
では、APIキーを記述します
.envPAYJP_PRIVATE_KEY = 'sk_test_111111111111111111111111' PAYJP_KEY = 'pk_test_111111111111111111111111'記載したら、Githubのコミットに表示されていないか?確認します。
コミットに表示されてたら、ヤバイです。
危険です。.gitignoreを再確認してください。ここまでで準備完了です。
素材作成(view):ここは自身で記述すると良いかと思います。
ここはviewですが、必要なければ飛ばしてください。
クレジットカードを登録するためのviewを作成します。
下記はformの部分テンプレートですが下記のようなスクリプト
記述#{asset_path 'creditcards/master-card.svg'}master-card.svgの画像素材がないとエラーがおきますので、
コピペするなら素材を集めてください。form
formの部分テンプレート= form_with url:creditcards_path, method: :post, html: { name: "inputForm" },class:"form" do |f| .form__upper .form__upper__group = f.label :カード番号 %span.form-require 必須 = f.text_field :card_number, name: "card_number", id:"card_number", type: "text", placeholder: '半角数字のみ', class: 'input-default', maxlength: "16" %ol %li %object{type: "image/svg+xml", data: "#{asset_path 'creditcards/visa.svg'}", width:"35", height:"20"} %li %object{type: "image/svg+xml", data: "#{asset_path 'creditcards/master-card.svg'}", width:"35", height:"20"} %li %object{type: "image/svg+xml", data: "#{asset_path 'creditcards/saison-card.svg'}", width:"35", height:"20"} %li %object{type: "image/svg+xml", data: "#{asset_path 'creditcards/jcb.svg'}", width:"35", height:"20"} %li %object{type: "image/svg+xml", data: "#{asset_path 'creditcards/american_express.svg'}", width:"35", height:"20", class:"american_express"} %li %object{type: "image/svg+xml", data: "#{asset_path 'creditcards/dinersclub.svg'}", width:"35", height:"20"} %li %object{type: "image/svg+xml", data: "#{asset_path 'creditcards/discover.svg'}", width:"35", height:"20"} .form__upper__group.exp .name = f.label :有効期限 %span.form-require 必須 = f.select :exp_year, [["19",2019],["20",2020],["21",2021],["22",2022],["23",2023],["24",2024],["25",2025],["26",2026],["27",2027],["28",2028],["29",2029]], {}, class: 'input-default harf', name: "exp_year", id:"exp_year" %span 年 = f.select :exp_month, [["01",1],["02",2],["03",3],["04",4],["05",5],["06",6],["07",7],["08",8],["09",9],["10",10],["11",11],["12",12]],{}, class: 'input-default harf', name: "exp_month", id:"exp_month" %span 月 .form__upper__group = f.label :セキュリティーコード %span.form-require 必須 = f.text_field :cvc, name: "cvc", id:"cvc", class:"cvc", type: "text", placeholder: 'カード背面4桁もしくは3桁の番号', class: 'input-default', maxlength: "16" .form__upper__group %p.about = fa_icon 'question-circle' %span = link_to 'カード裏面の番号とは?', root_path, class:'about__registered' .form__upper__group = f.submit '次へ進む', class: 'btn-default', id: "charge-form"data: "#{asset_path 'creditcards/visa.svg'}"は、
【 app > asset > images > creditcards > visa.svg 】を読み込む
という設定になります。scss.form{ &__upper{ margin: 0 auto; max-width: 343px; p { text-align: center; } &__group { font-size: 14px; color: #333; &:not( :first-child ){ margin-top: 32px; } label{ font-weight: 600; } ol{ display:flex; li { margin: 5px 8px 0 0; } } .form-require { background-color: $green; color: #fff; font-size: 12px; margin: 0 0 0 8px; padding: 2px 4px; border-radius: 2px; vertical-align:top; &-optional { background-color: gray; color: #fff; font-size: 12px; margin: 0 0 0 8px; padding: 2px 4px; border-radius: 2px; vertical-align:top; } } .input-default{ width: 90%; margin: 8px 0 0; height: 48px; padding: 10px 16px 8px; border-radius: 4px; border: 1px solid #ccc; background: #fff; line-height: 1.5; font-size: 16px; &.harf{ width: calc(40% - 6px); margin: 8px 8px 0 0; height: 45px; border-radius: 4px; border: 1px solid #ccc; background: #fff; line-height: 1.5; font-size: 16px; &.exp{ -webkit-appearance: none; -moz-appearance: none; appearance: none; } } &-select{ width: 76px; margin-top: 8px; height: 45px; border-radius: 4px; border: 1px solid #ccc; background: #fff; line-height: 1.5; font-size: 16px; } } h3 { font-size: 16px; font-weight: bold; } .attention{ margin: 8px 0 0; } .agree{ text-align: center; } a { color: #0099e8; text-decoration: none; } span { margin: 0 2px; } .about{ text-align: right; &__registered{ color: #0099e8; text-decoration: none; } .fa-chevron-right { color: #0099e8; } .fa-question-circle { color: #0099e8; font-size: 1rem } } } .form-info-text { color: #888; margin-top: 8px; font-size: 14px; } } &__bottom { margin: 0 auto; max-width: 343px; .registance { text-align: right; } } .btn-default { width: 100%; height: 50px; background-color: $green; color: #FFFFFF; font-size: 15px; cursor: pointer; } .btn-registration{ color: #fff; border-radius: 4px; width: 50%; line-height: 48px; border: 1px solid transparent; text-align: center; margin: 0 auto; position: relative; i{ font-size:20px; position: absolute; top:13.5px; left:15px; } .about__registered { color: #FFFFFF; text-decoration: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; cursor: pointer; } &.email{ background-color: $green; } &.facebook{ background-color:#385184; } &.google{ background-color:#FFFFFF; color: black; background: #fff image-url('google.svg') no-repeat 3px top; border: #979797 solid 1px; } &:not( :first-child ){ margin-top: 16px; } } }new.html.haml= render 'shared/main-header' .container-fluid .row.py-5.w-100 = render 'shared/mypage-side' .mypage-main.col-9 %h2.header_title 支払い方法 .single-container .rgs-main__section = render "shared/creditcard-form" .payment-explain = fa_icon 'chevron-right', class:"arrows" = link_to '支払い方法について', '#'モデル作成
ターミナル$ rails g model creditcardcreditcardsテーブル
Column Type Options user_id references foreign_key: true, null: false payjp_id string null: false Association
- belongs_to :user
ということでマイグレーションファイルは下記になります。
migrationファイルclass CreateCreditcards < ActiveRecord::Migration[5.2] def change create_table :creditcards do |t| t.references :user, foreign_key: true, null: false t.string :payjp_id, null: false t.timestamps end end endここは他の記事と異なります。
- user_id: AppのUser-ID
- payjp_id: PAYJPのUser-ID
他の記事だとカード用のカラムも作成していますが、PAYJPのアカウントから引っ張りだせばいいので不要です。
本題のjQueryです。
Payjp.js$(document).on('turbolinks:load',function(){ // PAY.JPの公開鍵をセットします。 Payjp.setPublicKey('pk_test_111111111111111111'); //formのsubmitを止めるために, クレジットカード登録のformを定義します。 var form = $(".form"); $("#charge-form").click(function() { // submitが完了する前に、formを止めます。 form.find("input[type=submit]").prop("disabled", true); // submitを止められたので、PAY.JPの登録に必要な処理をします。 // formで入力された、カード情報を取得します。 var card = { number: $("#card_number").val(), cvc: $("#cvc").val(), exp_month: $("#exp_month").val(), exp_year: $("#exp_year").val(), }; // PAYJPに登録するためのトークン作成 Payjp.createToken(card, function(status, response) { if (response.error){ // エラーがある場合処理しない。 form.find('.payment-errors').text(response.error.message); form.find('button').prop('disabled', false); } else { // エラーなく問題なく進めた場合 // formで取得したカード情報を削除して、Appにカード情報を残さない。 $("#card_number").removeAttr("name"); $("#cvc").removeAttr("name"); $("#exp_month").removeAttr("name"); $("#exp_year").removeAttr("name"); var token = response.id; form.append($('<input type="hidden" name="payjpToken" />').val(token)); form.get(0).submit(); }; }); }); });コントローラーの記述
コントローラー(new&create)の作成
creditcards_controller.rbclass CreditcardsController < ApplicationController require "payjp" before_action :set_card def new # cardがすでに登録済みの場合、indexのページに戻します。 @card = Creditcard.where(user_id: current_user.id).first redirect_to action: "index" if @card.present? end def create # PAY.JPの秘密鍵をセット(環境変数) Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"] # jsで作成したpayjpTokenがちゃんと入っているか? if params['payjpToken'].blank? # トークンが空なら戻す render "new" else # トークンがちゃんとあれば進めて、PAY.JPに登録されるユーザーを作成します。 customer = Payjp::Customer.create( description: 'test', email: current_user.email, card: params['payjpToken'], metadata: {user_id: current_user.id} ) # PAY.JPのユーザーが作成できたので、creditcardモデルを登録します。 @card = Creditcard.new(user_id: current_user.id, payjp_id: customer.id) if @card.save redirect_to action: "index", notice:"支払い情報の登録が完了しました" else render 'new' end end end private def set_card @card = Creditcard.where(user_id: current_user.id).first if Creditcard.where(user_id: current_user.id).present? end endコントローラーの追記(index、destory)
creditcards_controller.rbclass CreditcardsController < ApplicationController require "payjp" before_action :set_card def index # すでにクレジットカードが登録しているか? if @card.present? # 登録している場合,PAY.JPからカード情報を取得する # PAY.JPの秘密鍵をセットする。 Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"] # PAY.JPから顧客情報を取得する。 customer = Payjp::Customer.retrieve(@card.payjp_id) # PAY.JPの顧客情報から、デフォルトで使うクレジットカードを取得する。 @card_info = customer.cards.retrieve(customer.default_card) # クレジットカード情報から表示させたい情報を定義する。 # クレジットカードの画像を表示するために、カード会社を取得 @card_brand = @card_info.brand # クレジットカードの有効期限を取得 @exp_month = @card_info.exp_month.to_s @exp_year = @card_info.exp_year.to_s.slice(2,3) # クレジットカード会社を取得したので、カード会社の画像をviewに表示させるため、ファイルを指定する。 case @card_brand when "Visa" @card_image = "visa.svg" when "JCB" @card_image = "jcb.svg" when "MasterCard" @card_image = "master-card.svg" when "American Express" @card_image = "american_express.svg" when "Diners Club" @card_image = "dinersclub.svg" when "Discover" @card_image = "discover.svg" end end end def new @card = Creditcard.where(user_id: current_user.id).first redirect_to action: "index" if @card.present? end def create Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"] if params['payjpToken'].blank? render "new" else customer = Payjp::Customer.create( description: 'test', email: current_user.email, card: params['payjpToken'], metadata: {user_id: current_user.id} ) @card = Creditcard.new(user_id: current_user.id, payjp_id: customer.id) if @card.save if request.referer&.include?("/registrations/step5") redirect_to controller: 'registrations', action: "step6" else redirect_to action: "index", notice:"支払い情報の登録が完了しました" end else render 'new' end end end def destroy # 今回はクレジットカードを削除するだけでなく、PAY.JPの顧客情報も削除する。これによりcreateメソッドが複雑にならない。 # PAY.JPの秘密鍵をセットして、PAY.JPから情報をする。 Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"] # PAY.JPの顧客情報を取得 customer = Payjp::Customer.retrieve(@card.payjp_id) customer.delete # PAY.JPの顧客情報を削除 if @card.destroy # App上でもクレジットカードを削除 redirect_to action: "index", notice: "削除しました" else redirect_to action: "index", alert: "削除できませんでした" end end private def set_card @card = Creditcard.where(user_id: current_user.id).first if Creditcard.where(user_id: current_user.id).present? end end登録したカード情報を表示
index.html.haml.mypage-main.col-9 %h2.header_title 支払い方法 .single-container %section.creditcard_section %h3 クレジットカード一覧 - if @card.present? .container .creditcard-info = image_tag "creditcards/#{@card_image}",width:'34',height:'20', alt:'master-card' %p.creditcard-info__number = "**** **** **** " + @card_info.last4 #クレジットカードの下4桁を表示 %p.creditcard-info__period = @exp_month + " / " + @exp_year = button_to "削除する", creditcard_path(@card), method: :delete, class:"creditcard-info__delete" - else .new-card = link_to new_creditcard_path, class:"new-card-btn" do %i.far.fa-credit-card クレジットカードを追加する購入処理を追加
creditcards_controller.rbdef buy @product = Product.find(params[:product_id]) # すでに購入されていないか? if @product.buyer.present? redirect_back(fallback_location: root_path) elsif @card.blank? # カード情報がなければ、買えないから戻す redirect_to action: "new" flash[:alert] = '購入にはクレジットカード登録が必要です' else # 購入者もいないし、クレジットカードもあるし、決済処理に移行 Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"] # 請求を発行 Payjp::Charge.create( amount: @product.price, customer: @card.customer_id, currency: 'jpy', ) # 売り切れなので、productの情報をアップデートして売り切れにします。 if @product.update(buyer_id: current_user.id) flash[:notice] = '購入しました。' redirect_to controller: 'products', action: 'show', id: @product.id else flash[:alert] = '購入に失敗しました。' redirect_to controller: 'products', action: 'show', id: @product.id end end endあとはボタンを押したら、buyアクションが動くようにすれば、完了です!!
以上です
お疲れ様です。参考リンク
新規登録時にクレジットカード登録
購入処理
テストカード
- 投稿日:2020-02-22T04:16:33+09:00
Rails+jQuery イベントの発火により部分テンプレートを更新させる方法
はじめに
インクリメンタルサーチについて書いた記事ではajaxとjsonの組み合わせを使ってビューを変化させていましたが、今回はjsonを使わずRailsの部分テンプレートを更新させる方法でビューを変更する方法について自分なりにまとめていきます。
環境
Ruby 2.5.1
Rails 6.0.2
haml、jQueryを使っていきます。やりたいこと
form_withのsubmitを使わず、jsのイベント発火(clickやchangeなど)により非同期通信で部分テンプレートを更新させたいです。
jsonを活用して更新する方法もありますが、ここでは部分テンプレートを活用して、js内の記述を簡略化させていきます。まずはsubmitを使った時の更新
post_controller.rbclass PostController < ApplicationController def index @posts = Post.all end def create @post = Post.create(message: params[:message]) @posts = Post.all render partial: "post", collection: @posts end endindex.html.haml%h2 Posts = form_with model: @post do |f| = f.text_field :message = f.submit "Post", id: :submit .posts = render partial: "post", collection: @posts_post.html.haml= post.messageまずは比較として、jsを使わず更新してみます。
結果
部分テンプレートのみの更新ではなく、ページ全体を部分テンプレートに変更されてしまいました。部分テンプレートのみ更新させる
post_controller_rbclass PostController < ApplicationController def index @posts = Post.all end def create @post = Post.create(message: params[:message]) @posts = Post.all # partial: "post", collection: @posts <- 削除 end endcreate.js.haml(新規作成)$('.posts').html("#{j(render partial: "post", collection: @posts)}");結果
本題 submitでの送信ではなく、jsのイベントで部分テンプレートの更新を行う
post.js$(function() { $('#submit').on('click', function(event) { event.preventDefault(); var input = $('#message').val(); console.log(input) $.ajax({ type: "POST", url: "/post", data: {message: input} }) .done(function(response) { $('.posts').html(response); }) }) })post_controller.rbclass PostController < ApplicationController def index @posts = Post.all end def create @post = Post.create(message: params[:message]) @posts = Post.all render partial: "post", collection: @posts <- 復元 end endサーバーから返されるデータの形式を指定しないことで、標準的なHTMLで記述された部分テンプレートの情報が返されてきているようです。
結果
jsで更新させる部分にajax通信で返されたデータを置換させることで部分テンプレートの更新が実現できました。活用できそうな場面
マウスオーバーと組み合わせてマウスの位置によってビューを更新させたり、一時的に処理を停止させたりといったjsとの組み合わせで活用できそうです。
- 投稿日:2020-02-22T01:41:22+09:00
【Ruby on Rails】gem 'ransack' を利用した検索機能/フォームの実装
検索機能をgemを使って簡単に実装しましょう!
gem 'ransack'を利用した検索機能実装
インストール
Gemfilegem 'ransack'インストールしましょう
ターミナル$ bundle install検索機能の作成
application_controller.rbbefore_action :set_search def set_search @search = Product.ransack(params[:q]) #ransackの検索メソッド @search_products = @search.result(distinct: true).order(created_at: "DESC").includes(:user).page(params[:page]).per(5) # productsの検索結果一覧 # 最終的に、@search_productsを検索結果画面(例:search.html.haml)に挿入します。 # 検索結果の一覧: @search_products = @search.result.order(created_at: "DESC") # distinct: trueは検索結果のレコード重複しないようにします。 # ページネーション: .includes(:user).page(params[:page]).per(5 endapplication_controller.rbで定義します。
products_controller.rbに書くとproducts_controller.rbを使わないページを表示した時にエラーになるため、application_controller.rbで記述。
kaminariでページネーションをつけているのでpage(params[:page])をつけた。
searchメソッドよりransackメソッド推奨なのでransackメソッドを使った。ransackの検索オプション
検索方法 意味(英語) 意味 *_eq equal 等しい *noteq not equal 等しくない *_lt less than より小さい *_lteq less than or equal より小さい(等しいものも含む) *_gt grater than より大きい *_gteq grater than or equal より大きい(等しいものも含む) *_cont contains value 部分一致(内容を含む) 今回使ったのは*_contですね
検索フォームの作成
検索.html.haml.search_wap = search_form_for @search do |f| = f.text_field :name_cont , placeholder: "何かお探しですか?", class: 'input' = button_tag type: 'submit', class: 'search__button' do .icon_wap %i.fas.fa-searchhaml形式でフォームを作成した。
この段階では、url:〇〇_pathはつけていません。あとで付与していきます。
scssのmixinも記載しておきます。mixin_search@mixin input{ border: none; border-radius: 50px; background: #F4F8F9; height: 37px; width: 100%; padding: 10px 20px; } .search_wap{ margin-right: 15px; position: relative; .input{ @include input; margin: 13px 0 0; } .search__button{ background-color: transparent; cursor: pointer; position: absolute; right: 0px; top: 13px; height: 37px; width: 40px; border-style: none; border-radius: 0px 4px 4px 0px; padding: 0px; .icon_wap{ i{ position: absolute; right: 14px; top: 13px; font-size: 13px; font-weight: 900; color: #999999; } } } }検索結果のview作成
あとは、検索画面を作成して、検索結果である@search_productsを挿入してあげれば完成です。
検索結果用の画面に移行するために、route.rbにget :searchを追加します。
route.rbRails.application.routes.draw do resources :products do collection do get :search end end endでは、検索フォームにurlを追加しましょう
検索.html.haml.search_wap = search_form_for @search, url: search_users_path do |f| = f.text_field :name_cont , placeholder: "何かお探しですか?", class: 'input' = button_tag type: 'submit', class: 'search__button' do .icon_wap %i.fas.fa-searchview用のhtml.hmlとコントローラーを記述します。
products_controller.rbclass UsersController < ApplicationController def search end endsearch.html.haml= render 'shared/main-header' .container-fluid .row.py-5.w-100 .jscroll = render partial: 'user', collection: @search_products, as: "product", class: "jscroll" // @search_productはapplication_controller.rbで定義している。@search_productで検索結果のProductを表示させるので、indexで作成したviewに@productsを@search_productに書き換えれば、出来上がりです。
まとめ
検索処理は下記だけ
application_controller.rbbefore_action :set_search def set_search @search = Product.ransack(params[:q]) #ransackの検索処理 @search_products = @search.result # 検索結果 end検索フォームを作って
検索.html.haml.search_wap = search_form_for @search do |f| = f.text_field :name_cont , placeholder: "何かお探しですか?", class: 'input' = button_tag type: 'submit', class: 'search__button' do .icon_wap %i.fas.fa-searchあとはviewに@search_productsを挿入すれば完成。
if文を追加する
if文を追加するなら、下記の3パターンがいいかもしません
例// 検索キーワードなし: 空白だとスペースを入れいてるname全部がヒットしてしまうため - if params[:q]['name_cont'] == "" = "検索キーワードがありません。" // 検索結果ありの場合 - elsif @search_products.present? = "「#{params[:q][:name_cont]}」の検索結果: #{@search_products.count}個" // 検索数0の場合 - else = "検索に一致する商品はありませんでした"では実際に書いていきましょう!
参考リンク
ransackでRailsアプリのヘッダーに検索機能をつける
【Rails】ヘッダーへの検索機能の付け方
Rubyon Rails で検索機能を作ろう(ransack)
[Rails]ransackを利用した色々な検索フォーム作成方法まとめ
Rails 5.1とBootstrapで作るシンプルな検索機能のテンプレ
- 投稿日:2020-02-22T01:37:36+09:00
Railsを用いたアプリケーション開発の流れ
はじめに
Railsを用いてアプリケーション開発をするための流れを簡潔にまとめたいと思います。
概要
以下の流れで実装を行い、トップページにデータベースに保存してあるデータを表示する機能を作成します。
1.アプリケーションの土台となるものを作る
2.データベースを作る
3.ルーティングの設定
4.コントローラの作成
5.ビューの作成
6.モデルの作成
7.ビューにインスタンス変数を埋め込む1.アプリケーションの土台となるものを作る
まずはアプリケーション用のディレクトリを作成してください。
そのディレクトリの直下で以下のコマンドを入力します。x.x.xの部分はrailsのバージョンを入れてください。
rails _x.x.x_ new アプリケーション名 -d 使用するデータベース
これでコマンドを実行したディレクトリの直下に様々なファイルが作成されたはずです。これがアプリケーションの土台となります。2.データベースを作る
以下のコマンドでアプリケーションで使用するデータベースを作成する事ができます。
rails db:create
実際にデータベースを確認して見ましょう。「アプリケーション名_development」と「アプリケーション名_test」というデータベースが作成されているはずです。3.ルーティングの設定
「1.アプリケーションの土台となるものを作る」を実行していれば、/アプリケーション用のディレクトリ/config/app の配下にroutes.rbというファイルが作成されていると思います。このファイルに「root to: "posts#index"」を追記しましょう。
Rails.application.routes.draw do root to: "posts#index" endこれで「root」ディレクトリにリクエストがあった場合、
「posts」コントローラに処理が遷移し、
postsコントローラの「index」アクションの処理を行う、となります。4.コントローラの作成
以下のコマンドでコントローラーを作成できます。「3.ルーティングの設定」でpostsコントローラに処理が遷移するとしたため、今回はコントローラー名をpostsにしましょう。
rails g controller コントローラー名
5.ビューの作成
今回は、postsコントローラのindexアクションに対応するビューであるため、/アプリケーション用のディレクトリ/app/views/posts の配下にindex.html.erbを作成しましょう。コントローラから受け取るインデックス変数などの処理は後ほど記載します。
6.モデルの作成
ターミナルで以下のコマンドを実行しましょう。
これでモデル名をpostにすればpostsテーブルに対応するモデルができます。
rails g model モデル名
7.ビューにインスタンス変数を埋め込む
1~6までの流れでテーブルからデータを抽出する事ができ、@posts(インスタンス変数)にデータが格納されました。あとはビューを修正してインスタンス変数を画面に出力するようにすれば完成です。
# 修正例 <% @posts.each do |post|%> <div> <%= post.text %> </div> <% end %>雑感
3日ほどRubyとRailsについて学び、WEBアプリケーション制作におけるはじめの一歩が踏み出せたのではないかと思います。ちなみに筆者は漫画のはじめの一歩も大好きで、ベストバウトは間柴対木村です。
- 投稿日:2020-02-22T00:10:37+09:00
Rails新規アプリケーションの作り方
フレームワークRailsの作成手順(ほぼ備忘録)
ディレクトリ作成からRailsのひな形作成までを簡単にアウトプット練習も兼ねて書いていく。(DB設計は割愛)
開発環境↓
- Railsバージョン5.2.3
- データベース MySQL
ここからはターミナルを実行してひな形を作っていく。
ターミナル# ディレクトリ作成 $ mkdir ~/test
Rails new
コマンドで作成ターミナル$ rails new アプリケーション名 -オプションtest#test-appファイルの作成 $ rails _5.2.3_ new test-app -d mysql
$ cd test-app
でtest-app
ファイルに移動test-app# bundle installしておく $ bundle installこれでRailsフレームワークのひな形が完成。
htmlファイルやcssファイル、routes.rbがあることを確認しておく。
これからDBとモデルを作成する。
以上
- 投稿日:2020-02-22T00:02:24+09:00
[Rails] ActiveRecord::import で NoMethodError: undefined method `raise_record_invalid' が発生したときの対応
はじめに
ActiveRecord
を使ってDBアクセスをしている場合、バルクインサートをやってくれる gem に activerecord-import というのがある。
とても便利なライブラリなのだが、RSpec でActiveRecord::RecordInvalid
の発生を期待するテストを書いた際に当該エラーが発生しなかったので備忘録として残す。環境
バージョン 備考 Ruby v2.5.5p157 Ruby on Rails v5.2.3 activerecord-import v0.14.1 バルクインサートをやってくれる なにが発生したか
前述のとおり
ActiveRecord::RecordInvalid
が発生するテストを書いたのだが、次のエラーが発生した。expected ActiveRecord::RecordInvalid, got #<NoMethodError: undefined method `raise_record_invalid' for結論から
この現象の解決には
activerecord-import
の update を行う。( 下記のバージョンを指定してbundle install
を実行する )gem 'activerecord-import', '~> 0.15.0'発生していた理由
activerecord-import
のバグ。下記で fix の報告があった。https://github.com/zdennis/activerecord-import/pull/294
まとめにかえて
上記のプルリクエストがマージされたのは
on 3 Jul 2016
と古い。そういうわけなので、最近導入した方は本記事で取り上げた現象は発生していないと思われるが、導入したのが古いバージョンのままで更新をかけていないとハマるのでご注意を。
なんせ
ActiveRecord::import
,ActiveRecord::RecordInvalid
,NoMethodError
,undefined method raise_record_invalid
といったキーワードで検索かけても全然糸口が見つからない。
そんななか、検索でひっかかった こちら に前掲のプルリクエストがあって気づけたのは幸いでした。。。参考