- 投稿日:2020-02-10T19:04:42+09:00
Railsにおける単体テストの解説(Railsチュートリアル)
はじめに
自分が作成したWebサービスに単体テストを何気なく導入していましたので、ここで一旦立ち止まって、詳しく分かりやすくアウトプットしていこうと思います。
そもそも単体テストとは何か??
めちゃくちゃ簡単に言うと
合体前の部品(メソッド、モデル、ビューヘルパー)が、それぞれちゃんと動くかを確認するテストのこと。
下記の記事が用語自体の理解をするには非常に分かりやすいので参考にしてみてください。
単体テストとはコードの解説
ここではRailsチュートリアルの「3.3 テストから始める」編を例にとってみていきます。
Railsチュートリアル3.3テストから始めるまずコントローラの作成で
$ rails generate controller StaticPages home helpを実行した時点でコントローラーのファイルに付随してテスト用のファイルも作成されるため、このテスト用のファイルにテストコードを書いていくことを理解する。
作成されたテスト用ファイルはこちら。
test/controllers/static_pages_controller_test.rb中身を見ていきましょう。
require 'test_helper' #test_helper.rbが読み込まれる class StaticPagesControllerTest < ActionDispatch::IntegrationTest test "should get home" do #homeメソッドに対するテストコード get static_pages_home_url assert_response :success end test "should get help" do #helpメソッドに対するテストコード get static_pages_help_url assert_response :success end endここではコントローラを作成したと同時に作成されたhomeとhelpのメソッドに対するテストが書かれています。ここで書かれているテストコードは自動で作成されたものなので、これを場合によっては編集して実際にテストコードとして有効なものへと自分でいじっていかなければなりません。
上のコードを理解する前に
まずは下記の2つのコードが同等に振る舞うということを理解します。
これはRailsガイドにも書かれています。
Railsガイドtest "the truth" do assert true end ---------------------------------------------------------------------------------- def test_the_truth assert true end上記のことから先ほどのhome,helpメソッドは下記のようになります
def test_should_get_home #homeメソッドに対するテストコード get static_pages_home_url assert_response :success end def test_should_get_help #helpメソッドに対するテストコード get static_pages_help_url assert_response :success endここでなぜわざわざdefじゃなくてtestというコードを使うの?
と思われた方もいるのではないでしょうか。僕も思いました。
おそらくメソッド名の命名に気を使わなくて良くなり、扱いやすいからだと僕は解釈しています。では次にメソッドの中身を見ていきましょう。
get static_pages_home_url assert_response :success簡単に訳すと、
「static_pages_home_urlを取得して、取得したものを評価し、成功と返す」
となります。詳しく書きます。
1行目にstatic_pages_home_urlをgetする(取得する)ということをあらわしています。
2行目にその取得したurlから返ってきたHTTPステータスコードが200なら成功ということをあらわしています。
assert_responseに関してはRailsガイドを参照します
assert_response(type, message = nil)
レスポンスが特定のステータスコードを持っていることを主張する。:successを指定するとステータスコード200-299を指定したことになり、同様に:redirectは300-399、:missingは404、:errorは500-599にそれぞれマッチする。ステータスコードの数字や同等のシンボルを直接渡すこともできる。ステータスコードとは、「PCやスマホの画面にこのウェブサイトを表示させたい」という<リクエスト>に対し、「現在このサイトは表示できません」「このサイトはURLが変更になりました」などのウェブサーバーからの<レスポンス>を表す数字のことで下記のような組み合わせとなっています。
以上のテストコードからここではhome,helpメソッドが単体でテストされているという解釈になります。
終わりに
ここではRailsチュートリアルにおける単体テストをほんの一部ではありますが記述しました。
また編集と改善を繰り返して行きたいと考えています。
私も勉強中であるため誤りがあるかもしれませんが、指摘していただけると幸いです。
- 投稿日:2020-02-10T18:26:27+09:00
ransackでラベル検索をチェックボックス検索にしたい
Ransackを使ったラベル(label)検索をどうしてもチェックボックス(check_box)で実装したいことがあったのでメモ
ラベル機能がある状態からスタート!!!
※ラベル機能の作成まではこの記事で完結できます↓
【gemなし】Railsでラベル機能を作るgemのransackをインストール
rb.Gemfilegem 'ransack'$ bundle installposts_controller.rbdef index @search = Post.ransack(params[:q]) @posts = @search.result(distinct: true) end private def post_params params.require(:post).permit(:title, :details, label_ids: [] ) endview.posts.index.html.erb<%= search_form_for @search do |f| %> <% Label.all.each do |label| %> <%= f.check_box :labels_id_eq_any, { multiple: true, checked: label[:checked], disabled: label[:disabled], include_hidden: false }, label[:id] %> <label><%= label.title %></label> <% end %> <%= f.submit "検索" %> <% end %>以上です!
- 投稿日:2020-02-10T18:15:13+09:00
[Rails]paranoiaを使わずに退会機能を実装する(ユーザーを論理削除する)
実装すること
paranoiaを使わずに、ユーザーの退会機能を作成するために下記のコードを書いていきます。
①ユーザーを論理削除する。
②退会ユーザーをログアウトさせる。
③退会ユーザーはログインできなくする。ページ設計
マイページ(users/show.html.erb)で退会できるようにします。
Userテーブル定義
is_deleted
カラムを作成し、デフォルトをfalseにしておきます。
退会ユーザーのis_deleted
をtrueにすることで退会ユーザーのデータは残したまま、退会しているかどうかを区別できるようにします。
カラム名 カラム説明 データ型 デフォルト ID ユーザーID integer name ユーザー名 string is_deleted 削除フラグ datetime FALASE created_at 登録日 datetime NOW updated_at 更新日 boolean NOW Userモデルの作成
Userモデルはdeviseを使用してモデルを作成していきます。
deviseの導入・Userモデルの作成
Gemfile.gem 'devise'$ bundle install $ rails g devise:install $ rails g devise user name:string is_deleted:boolean $ rails g devise:viewsコントローラの作成・編集
$ rails g controller usersusers_controller.rb
hide
アクションを作成します。
・@user.update(is_deleted: true)
で、@userのis_deletedカラムをtrueにupdateします。
・reset_session
で、ユーザーをログアウトさせます。
・redirect_to root_path
で、ログアウト後ルートパスに飛ばします。app/controllers/users_controller.rbdef show @user = User.find(params[:id]) end def hide @user = User.find(params[:id]) #is_deletedカラムにフラグを立てる(defaultはfalse) @user.update(is_deleted: true) #ログアウトさせる reset_session flash[:notice] = "ありがとうございました。またのご利用を心よりお待ちしております。" redirect_to root_path endルーティングの作成
hide
アクションのルーティングを作成します。
as: 'users_hide'
で、showアクションと間違われないようURL名を指定します。config/routes.rbRails.application.routes.draw do devise_for :users resources :users put "/users/:id/hide" => "users#hide", as: 'users_hide' end退会ボタンの作成
マイページ(users/show.html.erb)
if user_signed_in? && @user.id == current_user.id
で、ユーザーがログインしていて且つログインユーザーであれば退会ボタンを表示するようにしています。
"data-confirm" => "本当に退会しますか?"
で退会ボタンクリック時にアラートが出るようにしています。app/views/users/show.html<% if user_signed_in? && @user.id == current_user.id %> <%= link_to "退会", users_hide_path(current_user), method: :put, "data-confirm" => "本当に退会しますか?", class: "btn btn-outline-danger" %> <% end %>退会ユーザーはログインできなくする
user.rb
ログインする時に退会済み(is_deleted==true)のユーザーを弾くためのメソッドを作成します。
super && (self.is_deleted == false)
で、userのis_deletedがfalseならtrueを返すようにしています。app/models/user.rbclass User < ApplicationRecord def active_for_authentication? super && (self.is_deleted == false) end endsessions_controller.rb
・
if (@user.valid_password?(params[:user][:password])
で、入力されたパスワードが正しいことを確認しています。
・(@user.active_for_authentication? == false))
で、@userのactive_for_authentication?メソッドがfalseであるかどうかを確認しています。
・上記の2点が当てはまれば、ログインページにリダイレクトし、エラーメッセージを表示するようにしています。app/controllers/sessions_controller.rbclass Users::SessionsController < Devise::SessionsController before_action :reject_user, only: [:create] protected def reject_user @user = User.find_by(email: params[:user][:email].downcase) if @user if (@user.valid_password?(params[:user][:password]) && (@user.active_for_authentication? == false)) flash[:error] = "退会済みです。" redirect_to new_user_session_path end else flash[:error] = "必須項目を入力してください。" end end endsessions/new.html.erb
エラーメッセージを表示するため、
<%= flash[:error] %>
をお好きなところに追記してください。app/views/sessions/new.html<%= flash[:error] %>最後に
最後までご覧いただきありがとうございます。
初学者ですので間違っていたり、分かりづらい部分もあるかと思います。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。参考
RailsでDeviseを使っていて、入力されたパスワードが正しいかどうかを自前で実装したい場合
https://qiita.com/knt45/items/49f8ad2bdef906dca302
- 投稿日:2020-02-10T18:03:38+09:00
【RSpec】「都道府県」を表示させるため、View側でDB参照した部分のテストでハマった
はじめに
まとめをかきます。
- テスト実行時には、DBのレコードは空っぽです。マスターテーブルをView側で参照した部分のテストでは事前にレコードをつくりましょう。
- さもないと「ブラウザで動いてるのに、テスト時だけコケる」ので注意しましょう。
ActiveHashに置き換えると、
db:seed
の手間もなくなり、変更も容易になります-------✁-------✁-------✁-------✁-------
※以下は、解決までにどのようなことを考えて試したかの話になります。
筆者自身への備忘録な成分が多めですのでご了承ください。開発の環境
- Rails 6.0.2
- Ruby 2.7
- simple_form 5.0.2
- RSpec 3.9.1
住所の都道府県の選択を
Prefecture
テーブルとsimple_formで実装
Prefecture
はnameだけをもつ、都道府県のマスターテーブルです。app/views/addresses/_form.html.haml= f.input :prefecture, selected: "東京都", collection: Prefecture.all, label_method: :name, value_method: :idこうすると、こう。
Prefecture
テーブル用に以下のseedをつかいました。prefecture_seed.rbprefs = %w[北海道 青森県 岩手県 宮城県 秋田県 山形県 福島県 茨城県 栃木県 群馬県 埼玉県 千葉県 東京都 神奈川県 新潟県 富山県 石川県 福井県 山梨県 長野県 岐阜県 静岡県 愛知県 三重県 滋賀県 京都府 大阪府 兵庫県 奈良県 和歌山県 鳥取県 島根県 岡山県 広島県 山口県 徳島県 香川県 愛媛県 高知県 福岡県 佐賀県 長崎県 熊本県 大分県 宮崎県 鹿児島県 沖縄] insert_prefs = prefs.map {|pref| {name: pref, created_at: Time.current, updated_at: Time.current } } Prefecture.insert_all!(insert_prefs)System Testでセレクト要素を選択する部分がコケた【今回の問題点】
「新規住所を登録する」Specをつくりました。
addresses_spec.rbvisit new_user_address_path(user) select "高知県", from: "都道府県"すると、
ElementNotFound
がでました。("高知県" がみつかっていない)ブラウザで実行すると表示される
ブラウザではたしかに表示されているし、期待通りの動きをしているのに、、、、なぜだ。。。
スクショを確認すると、セレクト要素が空白
System Specが落ちたときのスクショを確認すると、セレクト要素が空白でした。(ここで気づきたかった!)
頭の中は「?? ??」
IDEを起動したりしました。
ためしたこと 1.配列を直接わたすとパスする
simple_formのcollectionが動いていない?
app/views/addresses/_form.html.haml= f.input :prefecture, selected: "東京都", collection: ["東京都", "高知県"]書き換えてみると、テストはパスしました。
ためしたこと 2.ActiveHashにするとパスする
gem 'active_hash' bundle installしたあとに、2都県の日本でためしてみました。
app/models/prefecture.rbclass Prefecture < ActiveHash::Base field :name add id: 1 , name: "北海道" add id: 2 , name: "高知県" endパスします。
あとで調べてわかったのですが、ActiveHash化したPrefecture.all
は単に配列を返すので、1.の検証とおなじ挙動になります。テスト実行時は「レコード0件の世界」であることに気づく【解決】
要素が表示されない。
Prefecture.all
はエラーではない。つまりレコードが存在しない。
なぜ・・・
時計の長針が1周しようかというところで、ついにテスト実行時は「レコード0件の世界」であることに気づくわけです。長かった。
スペック内でPrefectureにレコードをつくるとパスする
検証。
addresses_spec.rbPrefecture.create!(name: "東京都") Prefecture.create!(name: "高知県") visit new_user_address_path(user) select "高知県", from: "都道府県"パスしました。
さいごに
ここまで読んでいただきありがとうございました。
普段の記事では、問題に対する答えだけをかくようにしていますが、今回は「どのようにして解決に至ったか」までの道のりをかいてみました。
「simple_formの使い方がまちがっているんだろう」というところから、おなじ問題がIssueにあがってないかと公式レポジトリを検索したり、迷走の極みでした。
人に聞くと解決するメソッド
気づいてしまえばなんでもないことほど深くハマってしまうことがあります。筆者の場合は、チャットで質問しようと文章を書いているときに 解決しました。
ハマったときこそ考え込まず、文章 にしたり、他の人に説明 してみるといいかもしれません。
追伸:
Prefecture
はActiveHashにしました。
- 投稿日:2020-02-10T17:16:54+09:00
[HowTo]Pay.jpを用いたクレジットカードの登録機能実装について
某スクールのチーム開発にてpay.jpを活用したクレジットカード登録と商品購入機能実装を担当させていただくことになりました!
色々と苦戦しましたがなんとか実装できましたので、以下にまとめてみたいと思います。
今回はクレジットカード登録機能実装までまとめており、商品購入機能に関しては、準備出来次第、別記事として投稿します!そもそもPay.jpとは
シンプルなAPI・多彩な機能、分かりやすい料金形態でクレジットカード決済をかんたんに導入できる決済サービスです。手数料も比較的リーズナブル(2.59%〜)であることと、導入が簡単ということもあり、スタートアップ企業などに多く採用されているようです。
クレジットカード登録について
クレジットカード登録に関しては、カードの登録のフォーマットによってやり方が異なります。
- チェックアウト:pay.jp社にて用意されているフォーマットを利用する方法。
- カスタムフォーム:ご自身でフォーマットを作成し、そちらに準じてカード登録を行う方法。今回は既に入力フォームを作成していたので、カスタムフォームにて実装を行うことにしました!
全体の流れ
1. クレジットカード番号などの情報登録:view
2. ”トークン”作成:Javascript
3. ”トークン”をキーとしてPayjpに顧客情報として登録:controller
4. 登録情報の確認:view+controller今回の記事では、まず、Viewの実装内容を確認いただき、JSを用いたトークンの作成をご確認いただきます。
そして、最後にコントローラでそのトークンをキーとしてPayjpに顧客登録をする動きを確認いただければと思います。1.クレジットカード番号などの情報登録:view
今回は以下のようなviewで登録画面を作ってます。
view.single-container %header.single-header %h1.single-header__logo = link_to root_path do =image_tag("fmarket_logo_red.svg") %nav.single-header__progress %ol %li.single-header__progress__text{ id: "first" } 会員情報 .single-header__progress__round--red %li.single-header__progress__text お届け先住所入力 .single-header__progress__round--red %li.single-header__progress__text--active 支払い方法 .single-header__progress__round--red .single-header__progress__round--red-long{ id: "long" } %li.single-header__progress__text{ id: "end" } 完了 .single-header__progress__round %main.single-main %section.single-main__container %h2.single-main__container__title 支払い方法 .single-main__container__form .single-main__container__form__frame = form_for(@creditcard, url: creditcards_path,method: :post,html: {id: "form" }) do |f| = render "devise/shared/error_messages", resource: @creditcard .form-group = f.label :カード番号 %span.form-group__require 必須 = f.text_field :card_number, {placeholder: "半角数字のみ", class: "form-group__input",maxlength:"16"} .form-group = f.label :カード会社 %span.form-group__require 必須 = f.select :card_company, Creditcard.card_companies.keys, {}, {class: 'form-group__input'} %ul.signup__card--list %li.icon--visa = image_tag("visa.svg", id:"icon--visa") %li.icon--master = image_tag("master-card.svg", id:"icon--master") %li.icon--saison = image_tag("saison-card.svg", id:"icon--saison") %li.icon--jcb = image_tag("jcb.svg", id:"icon--jcb") %li.icon--americanexpress = image_tag("american_express.svg", id:"icon--americanexpress") %li.icon--diners = image_tag("dinersclub.svg", id:"icon--diners") %li.icon--discover = image_tag("discover.svg", id:"icon--discover") .form-group = f.label :有効期限 %span.form-group__require 必須 %br = f.select :card_month, options_for_select(["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]), {}, {class: "form-group__input--half"} = f.label :月, class: "form-group__card--year-and-month" -# = f.select :card_year, options_for_select(["20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30"]), {}, {class: "form-group__input--half"} = f.select :card_year, options_for_select((2020..2030)), {}, {class: "form-group__input--half"} = f.label :年, class: "form-group__card--year-and-month" .form-group = f.label :セキュリティコード, class: "label" %span.form-group__require 必須 = f.text_field :card_pass, placeholder: "カード背面4桁もしくは3桁の番", class: "form-group__input" .form-group__add .form-group__add--question ? %p.form-group__text--right--blue カード裏面の番号とは? .form-group = f.submit "登録する", class: "btn-default btn-red", url: "creditcards_path",id:"charge-form",method: :post = render "/registration/registration_footer" f.submit "Sign up"2.”トークン”作成:Javascript
今回はPay.jpの機能を利用するために、入力された値で”トークン”を作成し、その”トークン”をキーとしてクレジットカード情報などを登録します。
そのためにはクレジットカード情報を入力してもらった後に、その情報を元に”トークン”を作成するためには、Javascriptを活用して実装します。今回はJqueryにて実装を行いました。
ポイントは以下の通りです。
- Payjpの公開鍵の記述を忘れずに。
- e.preventDefault()の記述を忘れずに。submitする前にトークンを作成します。
- jsにHTML情報を追加するときはバッククオーテーション``
- 入力エラーのときはprop('disabled', false)でボタンのdisabledを解除。2回以上押せるようにします。
- $("#form").get(0).submit();最後の送信はformの情報をとばす
*上記ミスで、当方はエラー地獄にはまりました。。笑jquery$(function() { Payjp.setPublicKey('pk_test_57c5bfaa1f1d1f2acd058a77'); $("#charge-form").on('click', function(e){ e.preventDefault(); let card = { number: $('#creditcard_card_number').val(), cvc:$('#creditcard_card_pass').val(), exp_month: $('#creditcard_card_month').val(), exp_year: $('#creditcard_card_year').val() }; Payjp.createToken(card, function(status, response) { if (response.error) { $("#charge-form").prop('disabled', false); alert("カード情報が正しくありません。"); } else { $(".number").removeAttr("name"); $(".cvc").removeAttr("name"); $(".exp_month").removeAttr("name"); $(".exp_year").removeAttr("name"); let token = response.id; $("#card_token").append(`<input type="hidden" name="payjpToken" value=${token}>`); $("#form").get(0).submit(); alert("登録が完了しました"); } }); }); });3.”トークン”をキーとしてPayjpに顧客情報として登録:controller
最後に”トークン”をキーとしてPayjpに顧客情報として登録するために、コントローラに記述が必要となります。
*今回はセッションを用いたユーザー情報を登録しおりますため、ごちゃごちゃしてますが、クレジットカードの登録だけであれば、クレジットカードのコントローラを作成し、対応いただけますと幸いです。
*セッションを用いた登録の詳細記述は以下URLよりご確認いただけますと幸いです。
https://qiita.com/Tatsu88/items/7447a669b788b011e96b今回のクレジットカードに関する記述は、「def create_creditcard」に記載ございます。
”#顧客情報をPAY.JPに登録。”という記述で、"トークン”をPay.jpに飛ばしてます。controllerclass Users::RegistrationsController < Devise::RegistrationsController def new super end # POST /resource def create if params[:sns_auth] == 'true' pass = Devise.friendly_token params[:user][:password] = pass params[:user][:password_confirmation] = pass end params[:user][:birthday] = params[:birthday] @user = User.new(sign_up_params) unless @user.valid? flash.now[:alert] = @user.errors.full_messages render :new and return end session["devise.regist_data"] = {user: @user.attributes} session["devise.regist_data"][:user]["password"] = params[:user][:password] @address = @user.build_address render :new_address end def create_address @user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(address_params) unless @address.valid? flash.now[:alert] = @address.errors.full_messages render :new_address and return end @user.build_address(@address.attributes) session["address"] = @address.attributes @creditcard = @user.build_creditcard render :new_credit_card end def create_creditcard @user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(session["address"]) Payjp.api_key = 'sk_test_be263def71d21c8f58b223e3' if params['payjpToken'].blank? redirect_to action: "new" else # 顧客情報をPAY.JPに登録。 customer = Payjp::Customer.create( description: 'test', email: @user.email, card: params['payjpToken'], ) end @creditcard = Creditcard.new(creditcard_params) @creditcard[:customer_id]=customer.id @creditcard[:card_id]=customer.default_card unless @creditcard.valid? flash.now[:alert] = @creditcard.errors.full_messages render :new_credit_card and return end binding.pry @user.build_address(@address.attributes) @user.build_creditcard(@creditcard.attributes) if @user.save sign_in(:user, @user) else render :new end end protected def address_params params.require(:address).permit(:address,:postal_code, :prefecture,:city,:apartment) end def creditcard_params params.require(:creditcard).permit(:card_number,:card_year, :card_month, :card_pass,:card_company) end4.登録情報の確認:view+controller
最後にPay.jpに登録した情報を取得できるようにしましょう。
まずは、情報をpay.jpから取得するための記述をコントローラに行います。view=render "home/header_login" .mypage_a %main.mypage-contents.clearfix .main-content .payment .payment-content .payment-content__title %h1.payment-header 支払い方法 .payment-content__main .payment-content__creditcards %h2.payment-title クレジットカード一覧 .payment-content__creditcards__list %figure = image_tag "#{@card_src}",alt: @card_brand, id: "card_image" .payment-content__creditcards__list__number = "**** **** **** " + @creditcard_information.last4 .payment-content__creditcards__list__number - exp_month = @creditcard_information.exp_month.to_s - exp_year = @creditcard_information.exp_year.to_s.slice(2,3) = exp_month + " / " + exp_year .side-content %nav.mypage-nav %ul.mypage-nav-list %li =link_to "#", class: "mypage-nav-list-item" do マイページ %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do お知らせ %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do やることリスト %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do いいね!一覧 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品する %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品した商品 - 出品中 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品した商品 - 取引中 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品した商品 - 売却済み %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 購入した商品 - 取引中 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 購入した商品 - 過去の取引 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do ニュース一覧 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 評価一覧 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do ガイド %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do お問い合わせ %i.icon-arrow-right %h3.mypage-nav-head-merpay メルペイ %ul.mypage-nav-list-merpay %li =link_to "#", class: "mypage-nav-list-merpay-item" do 売上・振込申請 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-merpay-item" do ポイント %i.icon-arrow-right %h3.mypage-nav-head-setting 設定 %ul.mypage-nav-list-setting %li =link_to "#", class: "mypage-nav-list-setting-item" do プロフィール %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 発送元・お届け先住所変更 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 支払い方法 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do メール/パスワード %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 本人情報 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 電話番号の確認 %i.icon-arrow-right %li =link_to destroy_user_session_path, method: :delete, class: "mypage-nav-list-setting-item" do ログアウト %i.icon-arrow-right =render "home/footer"controllerclass CardsController < ApplicationController require "payjp" before_action :set_creditcard def show Payjp.api_key = "sk_test_be263def71d21c8f58b223e3" customer = Payjp::Customer.retrieve(@creditcard.customer_id) @creditcard_information = customer.cards.retrieve(@creditcard.card_id) @card_brand = @creditcard_information.brand case @card_brand when "Visa" @card_src = "visa.svg" when "JCB" @card_src = "jcb.svg" when "MasterCard" @card_src = "master-card.svg" when "American Express" @card_src = "american_express.svg" when "Diners Club" @card_src = "dinersclub.svg" when "Discover" @card_src = "discover.svg" end end完成イメージは以下のようになっております。
此の情報はpay.jpでも同じ情報が登録できてます。
注意点(テストする時のクレジットカード番号について)
テストを行う時のクレジットカードの番号が定められており、この番号以外で適当な番号を入れるとエラーとなります。
下記URLに詳細載ってますので、ご確認の上、対応ください。
https://pay.jp/docs/testcardメモ
-function(e) {} のeって何?
function(e)の「e」。これはイベントハンドラ、イベントリスナとして設定したコールバック関数が受け取ることができるイベントオブジェクトです。
JavaScriptの関数は引数を指定しなくてもOKなのでイベントオブジェクトを省略してもエラーとはなりません。-get(index)
DOMエレメントの集合からインデックスを指定して、ひとつのエレメントを参照する。
これによって、特にjQueryオブジェクトである必要のないケースで特定のDOM Elementそのものを操作することが可能。例えば$(this).get(0)は、配列オペレータである$(this)[0]と同等の意味になる。参照
JavaScriptをしっかり勉強 vol.6 Eventオブジェクト
http://brush-clover.com/program/js-study6/jQuery日本語リファレンス
http://semooh.jp/jquery/api/core/get/index/payjpリファレンス
https://pay.jp/docs/api/#payjp-apiトークン作成
https://pay.jp/docs/cardtokenカード情報非通過化対応のお願い
http://payjp-announce.hatenablog.com/entry/2017/11/10/182738顧客を作成
https://pay.jp/docs/api/#%E9%A1%A7%E5%AE%A2%E3%82%92%E4%BD%9C%E6%88%90【Rails5】簡単便利!PAY.JPでクレジットカードのオンライン決済機能を導入!
https://qiita.com/emincoring/items/ce29dbbd182aa3c49c6bpayjp.jsの導入方法<Rails>
https://qiita.com/tripoodle/items/57d1cf9aef74ac5c9ab6#payjp%E3%81%A8%E3%81%AFPayjpでクレジットカード登録と削除機能を実装する(Rails)
https://qiita.com/takachan_coding/items/f7e70794b9ca03b559dd以上となります。最後までご覧いただき、ありがとうございました!
今後も学習した事項に関してQiitaに投稿していきますので、よろしくお願いします!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。
- 投稿日:2020-02-10T17:16:54+09:00
[HowTo]Pay.jpを用いたクレジットカードの登録機能実装について(カスタムフォーム使用)
某スクールのチーム開発にてpay.jpを活用したクレジットカード登録と商品購入機能実装を担当させていただくことになりました!
色々と苦戦しましたがなんとか実装できましたので、以下にまとめてみたいと思います。
今回はクレジットカード登録機能実装までまとめており、商品購入機能に関しては、準備出来次第、別記事として投稿します!そもそもPay.jpとは
シンプルなAPI・多彩な機能、分かりやすい料金形態でクレジットカード決済をかんたんに導入できる決済サービスです。手数料も比較的リーズナブル(2.59%〜)であることと、導入が簡単ということもあり、スタートアップ企業などに多く採用されているようです。
イメージとしては以下のようになっており、一時的なトークンを作成し、取引をすることで、加盟店はクレジットカード情報などの重要な情報を扱う必要がないまま、取引ができます。クレジットカード登録について
クレジットカード登録に関しては、カードの登録のフォーマットによってやり方が異なります。
- チェックアウト:pay.jp社にて用意されているフォーマットを利用する方法。
- カスタムフォーム:ご自身でフォーマットを作成し、そちらに準じてカード登録を行う方法。今回は既に入力フォームを作成していたので、カスタムフォームにて実装を行うことにしました!
全体の流れ
1. クレジットカード番号などの情報登録:view
2. ”トークン”作成:Javascript
3. ”トークン”をキーとしてPayjpに顧客情報として登録:controller
4. 登録情報の確認:view+controller今回の記事では、まず、Viewの実装内容を確認いただき、JSを用いたトークンの作成をご確認いただきます。
そして、最後にコントローラでそのトークンをキーとしてPayjpに顧客登録をする動きを確認いただければと思います。1.クレジットカード番号などの情報登録:view
今回は以下のようなviewで登録画面を作ってます。
view.single-container %header.single-header %h1.single-header__logo = link_to root_path do =image_tag("fmarket_logo_red.svg") %nav.single-header__progress %ol %li.single-header__progress__text{ id: "first" } 会員情報 .single-header__progress__round--red %li.single-header__progress__text お届け先住所入力 .single-header__progress__round--red %li.single-header__progress__text--active 支払い方法 .single-header__progress__round--red .single-header__progress__round--red-long{ id: "long" } %li.single-header__progress__text{ id: "end" } 完了 .single-header__progress__round %main.single-main %section.single-main__container %h2.single-main__container__title 支払い方法 .single-main__container__form .single-main__container__form__frame = form_for(@creditcard, url: creditcards_path,method: :post,html: {id: "form" }) do |f| = render "devise/shared/error_messages", resource: @creditcard .form-group = f.label :カード番号 %span.form-group__require 必須 = f.text_field :card_number, {placeholder: "半角数字のみ", class: "form-group__input",maxlength:"16"} .form-group = f.label :カード会社 %span.form-group__require 必須 = f.select :card_company, Creditcard.card_companies.keys, {}, {class: 'form-group__input'} %ul.signup__card--list %li.icon--visa = image_tag("visa.svg", id:"icon--visa") %li.icon--master = image_tag("master-card.svg", id:"icon--master") %li.icon--saison = image_tag("saison-card.svg", id:"icon--saison") %li.icon--jcb = image_tag("jcb.svg", id:"icon--jcb") %li.icon--americanexpress = image_tag("american_express.svg", id:"icon--americanexpress") %li.icon--diners = image_tag("dinersclub.svg", id:"icon--diners") %li.icon--discover = image_tag("discover.svg", id:"icon--discover") .form-group = f.label :有効期限 %span.form-group__require 必須 %br = f.select :card_month, options_for_select(["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]), {}, {class: "form-group__input--half"} = f.label :月, class: "form-group__card--year-and-month" -# = f.select :card_year, options_for_select(["20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30"]), {}, {class: "form-group__input--half"} = f.select :card_year, options_for_select((2020..2030)), {}, {class: "form-group__input--half"} = f.label :年, class: "form-group__card--year-and-month" .form-group = f.label :セキュリティコード, class: "label" %span.form-group__require 必須 = f.text_field :card_pass, placeholder: "カード背面4桁もしくは3桁の番", class: "form-group__input" .form-group__add .form-group__add--question ? %p.form-group__text--right--blue カード裏面の番号とは? .form-group = f.submit "登録する", class: "btn-default btn-red", url: "creditcards_path",id:"charge-form",method: :post = render "/registration/registration_footer" f.submit "Sign up"2.”トークン”作成:Javascript
今回はPay.jpの機能を利用するために、入力された値で一時的な”トークン”を作成し、その”トークン”をキーとしてクレジットカード情報などを登録します。
そのためにはクレジットカード情報を入力してもらった後に、その情報を元に”トークン”を作成するためには、Javascriptを活用して実装します。今回はJqueryにて実装を行いました。
ポイントは以下の通りです。
- Payjpの公開鍵の記述を忘れずに。
- e.preventDefault()の記述を忘れずに。submitする前にトークンを作成します。
- jsにHTML情報を追加するときはバッククオーテーション``
- 入力エラーのときはprop('disabled', false)でボタンのdisabledを解除。2回以上押せるようにします。
- $("#form").get(0).submit();最後の送信はformの情報をとばす
*上記ミスで、当方はエラー地獄にはまりました。。笑jquery$(function() { Payjp.setPublicKey('pk_test_57c5bfaa1f1d1f2acd058a77'); $("#charge-form").on('click', function(e){ e.preventDefault(); let card = { number: $('#creditcard_card_number').val(), cvc:$('#creditcard_card_pass').val(), exp_month: $('#creditcard_card_month').val(), exp_year: $('#creditcard_card_year').val() }; Payjp.createToken(card, function(status, response) { if (response.error) { $("#charge-form").prop('disabled', false); alert("カード情報が正しくありません。"); } else { $(".number").removeAttr("name"); $(".cvc").removeAttr("name"); $(".exp_month").removeAttr("name"); $(".exp_year").removeAttr("name"); let token = response.id; $("#card_token").append(`<input type="hidden" name="payjpToken" value=${token}>`); $("#form").get(0).submit(); alert("登録が完了しました"); } }); }); });3.”トークン”をキーとしてPayjpに顧客情報として登録:controller
最後に”トークン”をキーとしてPayjpに顧客情報として登録するために、コントローラに記述が必要となります。
*今回はセッションを用いたユーザー情報を登録しおりますため、ごちゃごちゃしてますが、クレジットカードの登録だけであれば、クレジットカードのコントローラを作成し、対応いただけますと幸いです。
*セッションを用いた登録の詳細記述は以下URLよりご確認いただけますと幸いです。
https://qiita.com/Tatsu88/items/7447a669b788b011e96b今回のクレジットカードに関する記述は、「def create_creditcard」に記載ございます。
”#顧客情報をPAY.JPに登録。”という記述で、"トークン”をPay.jpに飛ばしてます。controllerclass Users::RegistrationsController < Devise::RegistrationsController def new super end # POST /resource def create if params[:sns_auth] == 'true' pass = Devise.friendly_token params[:user][:password] = pass params[:user][:password_confirmation] = pass end params[:user][:birthday] = params[:birthday] @user = User.new(sign_up_params) unless @user.valid? flash.now[:alert] = @user.errors.full_messages render :new and return end session["devise.regist_data"] = {user: @user.attributes} session["devise.regist_data"][:user]["password"] = params[:user][:password] @address = @user.build_address render :new_address end def create_address @user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(address_params) unless @address.valid? flash.now[:alert] = @address.errors.full_messages render :new_address and return end @user.build_address(@address.attributes) session["address"] = @address.attributes @creditcard = @user.build_creditcard render :new_credit_card end def create_creditcard @user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(session["address"]) Payjp.api_key = 'sk_test_be263def71d21c8f58b223e3' if params['payjpToken'].blank? redirect_to action: "new" else # 顧客情報をPAY.JPに登録。 customer = Payjp::Customer.create( description: 'test', email: @user.email, card: params['payjpToken'], ) end @creditcard = Creditcard.new(creditcard_params) @creditcard[:customer_id]=customer.id @creditcard[:card_id]=customer.default_card unless @creditcard.valid? flash.now[:alert] = @creditcard.errors.full_messages render :new_credit_card and return end binding.pry @user.build_address(@address.attributes) @user.build_creditcard(@creditcard.attributes) if @user.save sign_in(:user, @user) else render :new end end protected def address_params params.require(:address).permit(:address,:postal_code, :prefecture,:city,:apartment) end def creditcard_params params.require(:creditcard).permit(:card_number,:card_year, :card_month, :card_pass,:card_company) end4.登録情報の確認:view+controller
最後にPay.jpに登録した情報を取得できるようにしましょう。
まずは、情報をpay.jpから取得するための記述をコントローラに行います。view=render "home/header_login" .mypage_a %main.mypage-contents.clearfix .main-content .payment .payment-content .payment-content__title %h1.payment-header 支払い方法 .payment-content__main .payment-content__creditcards %h2.payment-title クレジットカード一覧 .payment-content__creditcards__list %figure = image_tag "#{@card_src}",alt: @card_brand, id: "card_image" .payment-content__creditcards__list__number = "**** **** **** " + @creditcard_information.last4 .payment-content__creditcards__list__number - exp_month = @creditcard_information.exp_month.to_s - exp_year = @creditcard_information.exp_year.to_s.slice(2,3) = exp_month + " / " + exp_year .side-content %nav.mypage-nav %ul.mypage-nav-list %li =link_to "#", class: "mypage-nav-list-item" do マイページ %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do お知らせ %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do やることリスト %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do いいね!一覧 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品する %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品した商品 - 出品中 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品した商品 - 取引中 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品した商品 - 売却済み %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 購入した商品 - 取引中 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 購入した商品 - 過去の取引 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do ニュース一覧 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 評価一覧 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do ガイド %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do お問い合わせ %i.icon-arrow-right %h3.mypage-nav-head-merpay メルペイ %ul.mypage-nav-list-merpay %li =link_to "#", class: "mypage-nav-list-merpay-item" do 売上・振込申請 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-merpay-item" do ポイント %i.icon-arrow-right %h3.mypage-nav-head-setting 設定 %ul.mypage-nav-list-setting %li =link_to "#", class: "mypage-nav-list-setting-item" do プロフィール %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 発送元・お届け先住所変更 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 支払い方法 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do メール/パスワード %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 本人情報 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 電話番号の確認 %i.icon-arrow-right %li =link_to destroy_user_session_path, method: :delete, class: "mypage-nav-list-setting-item" do ログアウト %i.icon-arrow-right =render "home/footer"controllerclass CardsController < ApplicationController require "payjp" before_action :set_creditcard def show Payjp.api_key = "sk_test_be263def71d21c8f58b223e3" customer = Payjp::Customer.retrieve(@creditcard.customer_id) @creditcard_information = customer.cards.retrieve(@creditcard.card_id) @card_brand = @creditcard_information.brand case @card_brand when "Visa" @card_src = "visa.svg" when "JCB" @card_src = "jcb.svg" when "MasterCard" @card_src = "master-card.svg" when "American Express" @card_src = "american_express.svg" when "Diners Club" @card_src = "dinersclub.svg" when "Discover" @card_src = "discover.svg" end end完成イメージは以下のようになっております。
此の情報はpay.jpでも同じ情報が登録できてます。
注意点(テストする時のクレジットカード番号について)
テストを行う時のクレジットカードの番号が定められており、この番号以外で適当な番号を入れるとエラーとなります。
下記URLに詳細載ってますので、ご確認の上、対応ください。
https://pay.jp/docs/testcardメモ
-function(e) {} のeって何?
function(e)の「e」。これはイベントハンドラ、イベントリスナとして設定したコールバック関数が受け取ることができるイベントオブジェクトです。
JavaScriptの関数は引数を指定しなくてもOKなのでイベントオブジェクトを省略してもエラーとはなりません。-get(index)
DOMエレメントの集合からインデックスを指定して、ひとつのエレメントを参照する。
これによって、特にjQueryオブジェクトである必要のないケースで特定のDOM Elementそのものを操作することが可能。例えば$(this).get(0)は、配列オペレータである$(this)[0]と同等の意味になる。参照
JavaScriptをしっかり勉強 vol.6 Eventオブジェクト
http://brush-clover.com/program/js-study6/jQuery日本語リファレンス
http://semooh.jp/jquery/api/core/get/index/payjpリファレンス
https://pay.jp/docs/api/#payjp-apiトークン作成
https://pay.jp/docs/cardtokenカード情報非通過化対応のお願い
http://payjp-announce.hatenablog.com/entry/2017/11/10/182738顧客を作成
https://pay.jp/docs/api/#%E9%A1%A7%E5%AE%A2%E3%82%92%E4%BD%9C%E6%88%90【Rails5】簡単便利!PAY.JPでクレジットカードのオンライン決済機能を導入!
https://qiita.com/emincoring/items/ce29dbbd182aa3c49c6bpayjp.jsの導入方法<Rails>
https://qiita.com/tripoodle/items/57d1cf9aef74ac5c9ab6#payjp%E3%81%A8%E3%81%AFPayjpでクレジットカード登録と削除機能を実装する(Rails)
https://qiita.com/takachan_coding/items/f7e70794b9ca03b559dd以上となります。最後までご覧いただき、ありがとうございました!
今後も学習した事項に関してQiitaに投稿していきますので、よろしくお願いします!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。
- 投稿日:2020-02-10T17:16:54+09:00
[HowTo]Pay.jpを用いたクレジットカードの登録機能実装について/カスタムフォーム版
某スクールのチーム開発にてpay.jpを活用したクレジットカード登録と商品購入機能実装を担当させていただくことになりました!
色々と苦戦しましたがなんとか実装できましたので、以下にまとめてみたいと思います。
今回はクレジットカード登録機能実装までまとめており、商品購入機能に関しては、準備出来次第、別記事として投稿します!そもそもPay.jpとは
シンプルなAPI・多彩な機能、分かりやすい料金形態でクレジットカード決済をかんたんに導入できる決済サービスです。手数料も比較的リーズナブル(2.59%〜)であることと、導入が簡単ということもあり、スタートアップ企業などに多く採用されているようです。
イメージとしては以下のようになっており、一時的なトークンを作成し、取引をすることで、加盟店はクレジットカード情報などの重要な情報を扱う必要がないまま、取引ができます。クレジットカード登録について
クレジットカード登録に関しては、カードの登録のフォーマットによってやり方が異なります。
- チェックアウト:pay.jp社にて用意されているフォーマットを利用する方法。
- カスタムフォーム:ご自身でフォーマットを作成し、そちらに準じてカード登録を行う方法。今回は既に入力フォームを作成していたので、カスタムフォームにて実装を行うことにしました!
全体の流れ
- 下準備(gemインストール+application.html.hamlに追記) 1. クレジットカード番号などの情報登録:view 2. ”トークン”作成:Javascript 3. ”トークン”をキーとしてPayjpに顧客情報として登録:controller 4. 登録情報の確認:view+controller
今回の記事では、まず、Viewの実装内容を確認いただき、JSを用いたトークンの作成をご確認いただきます。
そして、最後にコントローラでそのトークンをキーとしてPayjpに顧客登録をする動きを確認いただければと思います。0. 下準備(gemインストール+application contorollerに追記)
下準備として、pay.jpのgemをインストールします。
Gemfilegem 'payjp'記載後、"bundle install"を忘れずに実行しましょう!
また、viewにあるapplication.html.hamlに以下の記述を行いましょう。
application.html.haml%script{src: "https://js.pay.jp/", type: "text/javascript"} %script{type:"text/javascript"}Payjp.setPublicKey('公開鍵');1.クレジットカード番号などの情報登録:view
今回は以下のようなviewで登録画面を作ってます。
view.single-container %header.single-header %h1.single-header__logo = link_to root_path do =image_tag("fmarket_logo_red.svg") %nav.single-header__progress %ol %li.single-header__progress__text{ id: "first" } 会員情報 .single-header__progress__round--red %li.single-header__progress__text お届け先住所入力 .single-header__progress__round--red %li.single-header__progress__text--active 支払い方法 .single-header__progress__round--red .single-header__progress__round--red-long{ id: "long" } %li.single-header__progress__text{ id: "end" } 完了 .single-header__progress__round %main.single-main %section.single-main__container %h2.single-main__container__title 支払い方法 .single-main__container__form .single-main__container__form__frame = form_for(@creditcard, url: creditcards_path,method: :post,html: {id: "form" }) do |f| = render "devise/shared/error_messages", resource: @creditcard .form-group = f.label :カード番号 %span.form-group__require 必須 = f.text_field :card_number, {placeholder: "半角数字のみ", class: "form-group__input",maxlength:"16"} .form-group = f.label :カード会社 %span.form-group__require 必須 = f.select :card_company, Creditcard.card_companies.keys, {}, {class: 'form-group__input'} %ul.signup__card--list %li.icon--visa = image_tag("visa.svg", id:"icon--visa") %li.icon--master = image_tag("master-card.svg", id:"icon--master") %li.icon--saison = image_tag("saison-card.svg", id:"icon--saison") %li.icon--jcb = image_tag("jcb.svg", id:"icon--jcb") %li.icon--americanexpress = image_tag("american_express.svg", id:"icon--americanexpress") %li.icon--diners = image_tag("dinersclub.svg", id:"icon--diners") %li.icon--discover = image_tag("discover.svg", id:"icon--discover") .form-group = f.label :有効期限 %span.form-group__require 必須 %br = f.select :card_month, options_for_select(["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]), {}, {class: "form-group__input--half"} = f.label :月, class: "form-group__card--year-and-month" -# = f.select :card_year, options_for_select(["20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30"]), {}, {class: "form-group__input--half"} = f.select :card_year, options_for_select((2020..2030)), {}, {class: "form-group__input--half"} = f.label :年, class: "form-group__card--year-and-month" .form-group = f.label :セキュリティコード, class: "label" %span.form-group__require 必須 = f.text_field :card_pass, placeholder: "カード背面4桁もしくは3桁の番", class: "form-group__input" .form-group__add .form-group__add--question ? %p.form-group__text--right--blue カード裏面の番号とは? .form-group = f.submit "登録する", class: "btn-default btn-red", url: "creditcards_path",id:"charge-form",method: :post = render "/registration/registration_footer" f.submit "Sign up"2.”トークン”作成:Javascript
今回はPay.jpの機能を利用するために、入力された値で一時的な”トークン”を作成し、その”トークン”をキーとしてクレジットカード情報などを登録します。
そのためにはクレジットカード情報を入力してもらった後に、その情報を元に”トークン”を作成するためには、Javascriptを活用して実装します。今回はJqueryにて実装を行いました。
ポイントは以下の通りです。
- Payjpの公開鍵の記述を忘れずに。
- e.preventDefault()の記述を忘れずに。submitする前にトークンを作成します。
- jsにHTML情報を追加するときはバッククオーテーション``
- 入力エラーのときはprop('disabled', false)でボタンのdisabledを解除。2回以上押せるようにします。
- $("#form").get(0).submit();最後の送信はformの情報をとばす
*上記ミスで、当方はエラー地獄にはまりました。。笑jquery$(function() { Payjp.setPublicKey('pk_test_57c5bfaa1f1d1f2acd058a77'); $("#charge-form").on('click', function(e){ e.preventDefault(); let card = { number: $('#creditcard_card_number').val(), cvc:$('#creditcard_card_pass').val(), exp_month: $('#creditcard_card_month').val(), exp_year: $('#creditcard_card_year').val() }; Payjp.createToken(card, function(status, response) { if (response.error) { $("#charge-form").prop('disabled', false); alert("カード情報が正しくありません。"); } else { $(".number").removeAttr("name"); $(".cvc").removeAttr("name"); $(".exp_month").removeAttr("name"); $(".exp_year").removeAttr("name"); let token = response.id; $("#card_token").append(`<input type="hidden" name="payjpToken" value=${token}>`); $("#form").get(0).submit(); alert("登録が完了しました"); } }); }); });3.”トークン”をキーとしてPayjpに顧客情報として登録:controller
最後に”トークン”をキーとしてPayjpに顧客情報として登録するために、コントローラに記述が必要となります。
*今回はセッションを用いたユーザー情報を登録しおりますため、ごちゃごちゃしてますが、クレジットカードの登録だけであれば、クレジットカードのコントローラを作成し、対応いただけますと幸いです。
*セッションを用いた登録の詳細記述は以下URLよりご確認いただけますと幸いです。
https://qiita.com/Tatsu88/items/7447a669b788b011e96b今回のクレジットカードに関する記述は、「def create_creditcard」に記載ございます。
”#顧客情報をPAY.JPに登録。”という記述で、"トークン”をPay.jpに飛ばしてます。controllerclass Users::RegistrationsController < Devise::RegistrationsController def new super end # POST /resource def create if params[:sns_auth] == 'true' pass = Devise.friendly_token params[:user][:password] = pass params[:user][:password_confirmation] = pass end params[:user][:birthday] = params[:birthday] @user = User.new(sign_up_params) unless @user.valid? flash.now[:alert] = @user.errors.full_messages render :new and return end session["devise.regist_data"] = {user: @user.attributes} session["devise.regist_data"][:user]["password"] = params[:user][:password] @address = @user.build_address render :new_address end def create_address @user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(address_params) unless @address.valid? flash.now[:alert] = @address.errors.full_messages render :new_address and return end @user.build_address(@address.attributes) session["address"] = @address.attributes @creditcard = @user.build_creditcard render :new_credit_card end def create_creditcard @user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(session["address"]) Payjp.api_key = 'sk_test_be263def71d21c8f58b223e3' if params['payjpToken'].blank? redirect_to action: "new" else # 顧客情報をPAY.JPに登録。 customer = Payjp::Customer.create( description: 'test', email: @user.email, card: params['payjpToken'], ) end @creditcard = Creditcard.new(creditcard_params) @creditcard[:customer_id]=customer.id @creditcard[:card_id]=customer.default_card unless @creditcard.valid? flash.now[:alert] = @creditcard.errors.full_messages render :new_credit_card and return end binding.pry @user.build_address(@address.attributes) @user.build_creditcard(@creditcard.attributes) if @user.save sign_in(:user, @user) else render :new end end protected def address_params params.require(:address).permit(:address,:postal_code, :prefecture,:city,:apartment) end def creditcard_params params.require(:creditcard).permit(:card_number,:card_year, :card_month, :card_pass,:card_company) end4.登録情報の確認:view+controller
最後にPay.jpに登録した情報を取得できるようにしましょう。
まずは、情報をpay.jpから取得するための記述をコントローラに行います。view=render "home/header_login" .mypage_a %main.mypage-contents.clearfix .main-content .payment .payment-content .payment-content__title %h1.payment-header 支払い方法 .payment-content__main .payment-content__creditcards %h2.payment-title クレジットカード一覧 .payment-content__creditcards__list %figure = image_tag "#{@card_src}",alt: @card_brand, id: "card_image" .payment-content__creditcards__list__number = "**** **** **** " + @creditcard_information.last4 .payment-content__creditcards__list__number - exp_month = @creditcard_information.exp_month.to_s - exp_year = @creditcard_information.exp_year.to_s.slice(2,3) = exp_month + " / " + exp_year .side-content %nav.mypage-nav %ul.mypage-nav-list %li =link_to "#", class: "mypage-nav-list-item" do マイページ %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do お知らせ %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do やることリスト %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do いいね!一覧 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品する %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品した商品 - 出品中 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品した商品 - 取引中 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 出品した商品 - 売却済み %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 購入した商品 - 取引中 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 購入した商品 - 過去の取引 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do ニュース一覧 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do 評価一覧 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do ガイド %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-item" do お問い合わせ %i.icon-arrow-right %h3.mypage-nav-head-merpay メルペイ %ul.mypage-nav-list-merpay %li =link_to "#", class: "mypage-nav-list-merpay-item" do 売上・振込申請 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-merpay-item" do ポイント %i.icon-arrow-right %h3.mypage-nav-head-setting 設定 %ul.mypage-nav-list-setting %li =link_to "#", class: "mypage-nav-list-setting-item" do プロフィール %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 発送元・お届け先住所変更 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 支払い方法 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do メール/パスワード %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 本人情報 %i.icon-arrow-right %li =link_to "#", class: "mypage-nav-list-setting-item" do 電話番号の確認 %i.icon-arrow-right %li =link_to destroy_user_session_path, method: :delete, class: "mypage-nav-list-setting-item" do ログアウト %i.icon-arrow-right =render "home/footer"controllerclass CardsController < ApplicationController require "payjp" before_action :set_creditcard def show Payjp.api_key = "sk_test_be263def71d21c8f58b223e3" customer = Payjp::Customer.retrieve(@creditcard.customer_id) @creditcard_information = customer.cards.retrieve(@creditcard.card_id) @card_brand = @creditcard_information.brand case @card_brand when "Visa" @card_src = "visa.svg" when "JCB" @card_src = "jcb.svg" when "MasterCard" @card_src = "master-card.svg" when "American Express" @card_src = "american_express.svg" when "Diners Club" @card_src = "dinersclub.svg" when "Discover" @card_src = "discover.svg" end end完成イメージは以下のようになっております。
此の情報はpay.jpでも同じ情報が登録できてます。
注意点(テストする時のクレジットカード番号について)
テストを行う時のクレジットカードの番号が定められており、この番号以外で適当な番号を入れるとエラーとなります。
下記URLに詳細載ってますので、ご確認の上、対応ください。
https://pay.jp/docs/testcardメモ
-function(e) {} のeって何?
function(e)の「e」。これはイベントハンドラ、イベントリスナとして設定したコールバック関数が受け取ることができるイベントオブジェクトです。
JavaScriptの関数は引数を指定しなくてもOKなのでイベントオブジェクトを省略してもエラーとはなりません。-get(index)
DOMエレメントの集合からインデックスを指定して、ひとつのエレメントを参照する。
これによって、特にjQueryオブジェクトである必要のないケースで特定のDOM Elementそのものを操作することが可能。例えば$(this).get(0)は、配列オペレータである$(this)[0]と同等の意味になる。参照
JavaScriptをしっかり勉強 vol.6 Eventオブジェクト
http://brush-clover.com/program/js-study6/jQuery日本語リファレンス
http://semooh.jp/jquery/api/core/get/index/payjpリファレンス
https://pay.jp/docs/api/#payjp-apiトークン作成
https://pay.jp/docs/cardtokenカード情報非通過化対応のお願い
http://payjp-announce.hatenablog.com/entry/2017/11/10/182738顧客を作成
https://pay.jp/docs/api/#%E9%A1%A7%E5%AE%A2%E3%82%92%E4%BD%9C%E6%88%90【Rails5】簡単便利!PAY.JPでクレジットカードのオンライン決済機能を導入!
https://qiita.com/emincoring/items/ce29dbbd182aa3c49c6bpayjp.jsの導入方法<Rails>
https://qiita.com/tripoodle/items/57d1cf9aef74ac5c9ab6#payjp%E3%81%A8%E3%81%AFPayjpでクレジットカード登録と削除機能を実装する(Rails)
https://qiita.com/takachan_coding/items/f7e70794b9ca03b559dd以上となります。最後までご覧いただき、ありがとうございました!
今後も学習した事項に関してQiitaに投稿していきますので、よろしくお願いします!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。
- 投稿日:2020-02-10T16:59:31+09:00
[Rails]Ajaxを用いて非同期でフォロー機能の実装
実装すること
Ajaxを用いて非同期通信を行い、フォロー機能を書いていきます。
Ajaxについては下記リンク先で説明しています。
[Rails]Ajaxを用いて非同期でコメント機能の実装:https://qiita.com/yuto_1014/items/c7d6213139a48833e21aER図
Userテーブル同士で「多対多」の関係を作ります。なぜなら、フォローしているユーザーもフォロワーもユーザになるからです。そこでUserテーブル同士の中間テーブルとしてRelationshipテーブルを作成します。
ページ設計
ユーザー詳細ページ(user/show.html.erb)でフォローができる。
モデルの作成
Userモデルは作成した前提で進めていきます。
作成手順は下記リンク先で説明しております。
[Rails]Ajaxを用いて非同期で投稿機能といいね機能の実装https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448
それではRelationshipモデルを作成していきます。$ rails g model Relationship follower_id:integer following_id:integer $ rails db:migrateマイグレーションファイルの追記
t.index [:follower_id, :following_id], unique: true
で、一度フォローしたユーザーを2度フォローしてしまわないようにするための一意の設定をしています。20200109085109_create_relationships_.rbclass CreateRelationships < ActiveRecord::Migration[5.2] def change create_table :relationships do |t| t.integer :follower_id t.integer :following_id t.index [:follower_id, :following_id], unique: true ←追記 t.timestamps end end endアソシエーションの確認
Userモデル
・『following_relationships』
following_relationshipsモデルを架空で作成しています。
class_name: "Relationship"
でRelationshipモデルの、foreign_key: "follower_id"
で、follower_idを参考に、following_relationshipsモデルへアクセスするようにしています。
フォロワーに関しても同じです。
・『following』
through: :following_relationships
で、中間テーブルにfollowing_relationshipsテーブルを指定しています。その結果、user.following と打つだけで、userが中間テーブルfollowing_relationships を取得し、その1つ1つのfollowing_idから、「フォローしているUser達」を取得できるようになります。
・『def following?』
following_relationshipsテーブルのfollowing_idにuserのidが存在するか確認しています。
・『def follow?』
フォローするときのメソッドを作成しています。
このメソッドが呼び出されたときには、following_idにuser.idを代入します。
・『def unfollow?』
フォローを外すときのメソッドを作成しています。
このメソッドが呼び出されたときには、following_idのuser.idを削除します。app/models/user.rbclass User < ApplicationRecord has_many :following_relationships, foreign_key: "follower_id", class_name: "Relationship", dependent: :destroy has_many :following, through: :following_relationships has_many :follower_relationships, foreign_key: "following_id", class_name: "Relationship", dependent: :destroy has_many :followers, through: :follower_relationships #フォローしているかを確認するメソッド def following?(user) following_relationships.find_by(following_id: user.id) end #フォローするときのメソッド def follow(user) following_relationships.create!(following_id: user.id) end #フォローを外すときのメソッド def unfollow(user) following_relationships.find_by(following_id: user.id).destroy endRelationshipsモデル
class_name: "User"
と補足設定することで、followerクラス、followingクラスという存在しないクラスを参照することを防ぎ、Userクラスであることを明示しています。app/models/relationship.rb#自分をフォローしているユーザー belongs_to :follower, class_name: "User" #自分がフォローしているユーザー belongs_to :following, class_name: "User" #バリデーション validates :follower_id, presence: true validates :following_id, presence: trueコントローラーの作成
$ rails g controller relationshipsルーティングの作成
フォローユーザーとフォロワーを取れるようにしています。
following_user GET /users/:id/following(.:format) users#following
followers_user GET /users/:id/followers(.:format) users#followersconfig/routes.rbRails.application.routes.draw do resources :users do member do get :following, :followers end end resources :relationships, only: [:create, :destroy] endコントローラーの編集
users_controller.rb
フォローユーザー一覧とフォロワー一覧を表示するためのアクションを作成します。
@user.following
と@user.followers
では、Userモデルのメソッドを呼び出しています。app/controllers/users_controller.rbclass UsersController < ApplicationController def following #@userがフォローしているユーザー @user = User.find(params[:id]) @users = @user.following render 'show_follow' end def followers #@userをフォローしているユーザー @user = User.find(params[:id]) @users = @user.followers render 'show_follower' end endralationships_controller.rb
フォローする・フォロー解除するためのアクションを作成します。
follow
とunfollow
では、Userモデルのメソッドを呼び出しています。app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController def create @user = User.find(params[:following_id]) current_user.follow(@user) end def destroy @user = User.find(params[:id]) current_user.unfollow(@user) end endビューの編集
ユーザー詳細ページ(users/show.html.erb)
・
followers.count
でフォロワーの人数、following.count
でフォローユーザーの人数を取っています。
・フォローボタンは、パーシャルにしています。app/views/users/show.html<div class="follower"> <%= link_to followers_users_user_path(user.id) do %> <h5 style="color: black;">フォロワー<%= user.followers.count %>人</h5> <% end %> </div> <div class="follow"> <%= link_to following_users_user_path(user.id) do %> <h5 style="color: black;">フォロー<%= user.following.count %>人</h5> <% end %> </div> <div> <%= render "follow_form" %> </div>フォローボタン(users/_follow_form.html.erb)
・
if user_signed_in? && @user != current_user
では、ユーザーがログインしていて且つユーザー詳細ページ(users/show.html.erb)に表示されているユーザーが、ログインユーザーでなければ、フォローボタンを表示するようにしています。
・if current_user.following?(@user)
で、ログインユーザーが@userをすでにフォローしているかどうかをUserモデルのメソッドを呼び出して確認しています。フォローしているか否かで呼び出されるパーシャルが変わってきます。app/views/users/_follow_form.html<!-- フォローボタン(MYPAGEのユーザーがcurrent_userでなければ表示------------------------------------------------------> <% if user_signed_in? && @user != current_user %> <div id="follow_form"> <% if current_user.following?(@user) %> <%= render "unfollow" %> <% else %> <%= render "follow" %> <% end %> </div> <% end %>まだフォローしていなかった場合
users/_follow.html.erb
・remote: true
で、relationshipsコントローラーのcreateアクションに飛んだ後、create.js.erbに飛ぶようにしています。
・hidden_field_tag :following_id, @user.id
で、following_idに@user.idを代入しています。app/views/users/_follow.html<!-- フォローボタン ------------------------------------------------------------------> <%= form_for(current_user, url: relationships_path, method: :post, remote: true) do |f| %> <%= hidden_field_tag :following_id, @user.id %> <%= f.submit "フォローする", class: "btn btn-outline-secondary" %> <% end %>relationships/create.js.erb
relationshipsコントローラのcreateアクションの処理後、
users/_follow_form.htmlのid=follow_form
をターゲットに、unfollowパーシャルを差し替えています。app/views/relationships/create.js$("#follow_form").html("<%= j(render("users/users/unfollow")) %>");既にフォローしていた場合
users/_unfollow.html.erb
remote: true
で、relationshipsコントローラーのdestroyアクションに飛んだ後、destroy.js.erbに飛ぶようにしています。app/views/users/_unfollow.html<!-- フォロー解除ボタン ------------------------------------------------------------------> <%= form_for(current_user, url: relationship_path(@user), method: :delete, remote: true) do |f| %> <%= f.submit "フォロー解除", class: "btn btn-outline-secondary" %> <% end %>relationships/destroy.js.erb
relationshipsコントローラのdestroyアクションの処理後、
users/_follow_form.htmlのid=follow_form
をターゲットに、followパーシャルを差し替えています。app/views/relationships/destroy.js$("#follow_form").html("<%= j(render("users/users/follow")) %>");最後に
最後までご覧いただきありがとうございます。
初学者ですので間違っていたり、分かりづらい部分もあるかと思います。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。
Ajax関連でいいねとコメントの非同期も実装しています。
↓
[Rails]Ajaxを用いて非同期で投稿機能といいね機能の実装
https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448
[Rails]Ajaxを用いて非同期でコメント機能の実装
https://qiita.com/yuto_1014/items/c7d6213139a48833e21a参考
・railsでフォロー機能をつける。
https://qiita.com/kitaokeita/items/59b625e0c43a62f5fe6a
・Railsでフォロー機能を作る方法
https://qiita.com/MitsuguSueyoshi/items/e41e2ff37f143db81897
・フォロー機能、完成版
https://qiita.com/Kaisyou/items/86869db6345c9cc1413f
- 投稿日:2020-02-10T16:15:47+09:00
コード書いたことないPdMやPOに捧ぐ、Rails on Dockerハンズオン vol.5 - Model and CRUD -
この記事はなにか?
この記事は私が社内のプログラミング未経験者、ビギナー向けに開催しているRuby on Rails on Dockerハンズオンの内容をまとめたものです。ていうかこの記事を基にそのままハンズオンします。ハンズオンは
1回の内容は喋りながらやると大体40~50分くらいになっています。お昼休みに有志でやっているからです。
現在進行形なので週1ペースで記事投稿していけるように頑張ります。
ビギナーの方のお役にたったり、同じように有志のハンズオンをしようとしている人の参考になれば幸いです。
他のハンズオンへのリンク
・ Vol.1 - Introduction -
・ Vol.2 - Hello, Rails on Docker -
・ Vol.3 - Scaffold, RESTful, MVC -
・ Vol.4 - Static pages -
・ Vol.5 - Model and CRUD -
・ Vol.6 - Model validation -
$
,#
,>
について
$
: ローカルでコマンドを実行するときは、頭に$
をつけています。
#
: コンテナの中でコマンドを実行するときは、頭に#
をつけています。
>
: Rails console内でコマンド(Rubyプログラム)を実行するときは、頭に>
をつけています。
はじめに
第5回は、Modelを作って遊んでみます!
Modelはデータベースと密に関係していますので、メソッドをつかってCRUDを試してみます。Userモデルを作ろう
今回作るModelは以下の通り。
ER図を描くツールはdbdiagram.ioを使ってます。Entityしか書いてないから何ともですが、便利なツールです。Userモデルがどんなモデルかといえば、Integer型の
id
をprimary keyとして、String型のname
,created_at
(作成日時),updated_at
(更新日時)を持っています。rails generate model
モデルを作成するコマンドは
rails generate model
です。Modelに必要なファイルとそのModelのデータをDBに登録するためにDB側にテーブルを作る必要があるのでマイグレーションファイルを作成してくれます。
rails generate model NAME [field:type field:type]
が型ですね。
NAME
がモデルの名前です。field
がモデルのattribute(属性)、type
が型です。早速、ER図のモデルを作ってみましょう!
# rails generate model user name:string email:string注目点は
id
,created_at
,updated_at
のことはコマンドで定義していないところです。ここはあとで説明。マイグレーションファイル
先ほどのコマンド実行でマイグレーションファイルが生成されています。
db/migrate/YYYYMMDDhhmmss_create_user.rbclass CreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| t.string :name t.string :email t.timestamps end end endScaffoldの時も実は同じようにファイルができていたんですが、中身を見るのは初めてですね。
rails db:migrate
をするとRailsがこのファイルを読み込んでDBにSQLを発行してくれているんですね。
先ほどコマンドで定義したname
,timestamps
というものがあります。これがcreated_at
,updated_at
を作ってくれるやつでモデルを作成するときにRailsがデフォルトで付けてくれています。また、
create_table
はデフォルトでprimary keyとしてInteger型のid
を付けてくれます。
なので、このままマイグレーションファイルをdb:migrate
すれば先ほどのER図通りのテーブルを作成してくれます。# rails db:migrateUserモデルで遊んでみる
ここからは作成したUserモデルを使ってCRUDで遊んでみます。
CRUDとはデータを操作する上で基本となるCreate
(作成)、Read
(参照)、Update
(更新)、Delete
(削除)の頭文字をとったものです。Railsの場合はSQL文をコーディングするのではなく、モデルのメソッドを使うだけでCRUDができちゃうのでそれを体感しましょう!ここからはRails consoleを使っていきます。これでRailsアプリケーションとコマンドラインで対話式にやりとりができます。
Rails consoleはそのままでもいいのですが、pry
というツールをインストールすることでRails consoleがみやすくなるのでそうします。Gemfile... group :development, :test do ... gem 'pry-rails' ... end ...gemを追加でインストールする場合は、再度Docker imageをビルドします。今立っているコンテナは古いイメージをもとに作られたコンテナなので一回落として、新しくビルドしたイメージで再度コンテナを作ってあげましょう。
$ docker-compose down $ docker-compose build $ docker-compose up -dコンテナを起動できたら、コンテナの中で
rails console
コマンドを使ってRails consoleを立ち上げましょう!$ docker-compose exec web ash# rails console Running via Spring preloader in process 335 Loading development environment (Rails 6.0.2.1) [1] pry(main)>Rails console内では接頭に
>
がつくので、>
がついている時はRails consoleの中でコマンドを実行しているんだなと思ってくださいね。Create
モデルの作成の方法は大きく2種類あります。
new
メソッドでモデルオブジェクトを作成し、save
メソッドでデータ保存するcreate
メソッドでオブジェクト作成とデータ保存を同時に行うCreate 1:
new
+save
例で
tanaka@sample.com
のメアドのTaro Tanaka
さんを作ってみましょう!> user = User.new(name: "Taro Tanaka", email: "tanaka@sample.com") => #<User:0x000055c0f665aab0 id: nil, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: nil, updated_at: nil> > user.save (0.3ms) BEGIN User Create (11.4ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "Taro Tanaka"], ["email", "tanaka@sample.com"], ["created_at", "2020-01-23 17:08:36.027245"], ["updated_at", "2020-01-23 17:08:36.027245"]] (2.6ms) COMMIT => trueまず、
User.new
で例の属性を持つUserモデルオブジェクトを作成し変数user
に代入しています。その後、
save
メソッドを実行。実行後のコンソールからDBにINSERTしているのがわかると思います。
RailsではModelのメソッドを使うことでとても簡単にSQLの操作ができるようになります。
save
メソッドはデータ作成に成功したらtrue
を失敗したらfalse
を返却するメソッドです。また、.の形で属性の情報を取得したり設定したりできるので、以下のようなやり方でもデータを作成することができます。
yamada@sample.com
のメアドのHanako Yamada
さんを作ってみます!> user = User.new => #<User:0x000055c0f693a488 id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil> > user.name = "Hanako Yamada" => "Hanako Yamada" > user.email = "yamada@sample.com" => "yamada@sample.com" > user.save (0.3ms) BEGIN User Create (0.5ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "Hanako Yamada"], ["email", "yamada@sample.com"], ["created_at", "2020-01-23 17:10:31.545772"], ["updated_at", "2020-01-23 17:10:31.545772"]] (1.4ms) COMMIT => trueCreate 2: create
create
メソッドはnew
メソッドとsave
メソッドを同時に行うメソッドっす。
john@sample.com
のメアドのJohn Smith
を作ります。突然の外国人ですが、『John Smith』は日本でいう『名無しの権兵衛』です。> User.create(name: "John Smith", email: "john@sample.com") (0.3ms) BEGIN User Create (0.5ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "John Smith"], ["email", "john@sample.com"], ["created_at", "2020-01-23 17:11:03.731916"], ["updated_at", "2020-01-23 17:11:03.731916"]] (0.7ms) COMMIT => #<User:0x000055c0f4a511e8 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>
create
は基本的にはnew
+save
なのですが、作成が成功した場合はそのモデルオブジェクト、失敗した場合はfalse
を返却するところが大きく違うポイントです。Read
データを確認するメソッドはかなりあります。ここでは特に利用頻度が高いであろうメソッドをご紹介します。
all
all
メソッドはそのモデルの全てのレコードをオブジェクトの配列として取得します。> User.all User Load (2.5ms) SELECT "users".* FROM "users" => [#<User:0x000055c0f6702508 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00>, #<User:0x000055c0f6702378 id: 2, name: "Hanako Yamada", email: "yamada@sample.com", created_at: Thu, 23 Jan 2020 17:10:31 JST +09:00, updated_at: Thu, 23 Jan 2020 17:10:31 JST +09:00>, #<User:0x000055c0f6702260 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>]find
find
メソッドはprimary keyの値を指定して1件のオブジェクトを取得するメソッドです。> User.find(1) User Load (1.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]] => #<User:0x000055c0f5da80e8 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00>マッチするレコードがない場合は、ActiveRecord::RecordNotFound Exceptionを発生させます。
> User.find(5) User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]] ActiveRecord::RecordNotFound: Couldn't find User with 'id'=5 from /usr/local/bundle/gems/activerecord-6.0.2.1/lib/active_record/core.rb:177:in `find'find_by
find_by
メソッドは指定したカラムの条件にマッチした1件のオブジェクトを取得するメソッドです。> User.find_by(email: "john@sample.com") User Load (1.6ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "john@sample.com"], ["LIMIT", 1]] => #<User:0x000055c0f65cb108 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>マッチするレコードが存在しない場合はnilを返却します。
> User.find_by(email: "hoge@sample.com") User Load (1.7ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "hoge@sample.com"], ["LIMIT", 1]] => nilまた、
find_by
メソッドでは複数の条件全てにヒットする1レコードを取得する書き方もできます。> User.find_by(name: "John Smith", email: "john@sample.com") => #<User:0x000055c0f6af3040 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>こんな感じでユーザーを特定することができます!
where
where
メソッドはSQLのWHERE句と同様、レコードの検索条件を指定し、条件にマッチしたレコードをオブジェクトの配列として取得します。
条件はハッシュ型で書くのがわかりやすくてオススメ。等値条件
あるカラムが特定の値と同一であるレコードを取得する場合、単に
key: value
の形で条件を指定するだけです。> User.where(id: 1) User Load (1.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 1]] => [#<User:0x000055c0f4d7ace8 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00>]範囲条件
valueには範囲を指定することもできます。Rubyでは
0 ≦ x ≦ 2
を0..2
、0 ≦ x < 2
を0...2
と表現します。> User.where(id: 1..2) User Load (2.7ms) SELECT "users".* FROM "users" WHERE "users"."id" BETWEEN $1 AND $2 [["id", 1], ["id", 2]] => [#<User:0x000055c0f638f740 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00>, #<User:0x000055c0f638f5d8 id: 2, name: "Hanako Yamada", email: "yamada@sample.com", created_at: Thu, 23 Jan 2020 17:10:31 JST +09:00, updated_at: Thu, 23 Jan 2020 17:10:31 JST +09:00>]サブセット条件
valueには配列を指定することも可能です。
> User.where(id: [1, 3]) User Load (2.7ms) SELECT "users".* FROM "users" WHERE "users"."id" BETWEEN $1 AND $2 [["id", 1], ["id", 3]] => [#<User:0x000055c0f638f740 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00>, #<User:0x000055c0f6901cc8 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>]NOT条件
where.not
メソッドを使えば、条件にヒットしないものを検索することもできます。> User.where.not(id: 1) User Load (1.9ms) SELECT "users".* FROM "users" WHERE "users"."id" != $1 [["id", 1]] => [#<User:0x000055c0f6901d90 id: 2, name: "Hanako Yamada", email: "yamada@sample.com", created_at: Thu, 23 Jan 2020 17:10:31 JST +09:00, updated_at: Thu, 23 Jan 2020 17:10:31 JST +09:00>, #<User:0x000055c0f6901cc8 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>]AND条件
複数の条件にマッチするレコードを取得したい場合は、ハッシュの組み合わせを増やせばいいだけです。
> User.where(name: "Taro Tanaka", email: "tanaka@sample.com") User Load (1.6ms) SELECT "users".* FROM "users" WHERE "users"."name" = $1 AND "users"."email" = $2 [["name", "Taro Tanaka"], ["email", "tanaka@sample.com"]] => [#<User:0x000055c0f35a3098 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00>]OR条件
複数の条件のうち、どれか一つでも当てはまるレコードを取得したい場合は
or
メソッドを使います。これはやや面倒(直感的でない)かもしれません。> User.where(id: 1).or(User.where(email: "john@sample.com")) User Load (2.0ms) SELECT "users".* FROM "users" WHERE ("users"."id" = $1 OR "users"."email" = $2) [["id", 1], ["email", "john@sample.com"]] => [#<User:0x000055c0f5c56c08 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00>, #<User:0x000055c0f5c56668 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>]order
order
メソッドは並び順を指定してくれます。使い方は並び順の条件にしたいカラムを指定するだけで昇順で並び替えてくれます。> User.order(:email) User Load (2.2ms) SELECT "users".* FROM "users" ORDER BY "users"."email" ASC => [#<User:0x000055c0f63223c0 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>, #<User:0x000055c0f6322258 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00>, #<User:0x000055c0f6322028 id: 2, name: "Hanako Yamada", email: "yamada@sample.com", created_at: Thu, 23 Jan 2020 17:10:31 JST +09:00, updated_at: Thu, 23 Jan 2020 17:10:31 JST +09:00>]メアド昇順に並び変わってますね。
降順に並べる場合は
desc
を使います。(昇順も同じようにasc
を指定して表現することもできます)> User.order(email: :desc) User Load (4.2ms) SELECT "users".* FROM "users" ORDER BY "users"."email" DESC => [#<User:0x000055c0f64a20d8 id: 2, name: "Hanako Yamada", email: "yamada@sample.com", created_at: Thu, 23 Jan 2020 17:10:31 JST +09:00, updated_at: Thu, 23 Jan 2020 17:10:31 JST +09:00>, #<User:0x000055c0f64a1d40 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00>, #<User:0x000055c0f64a1b88 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>]逆順になってる。
first
first
メソッドはprimary keyの順番で最初の1件のオブジェクトを取得するメソッドです。> User.first User Load (1.9ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]] => #<User:0x000055c0f625f460 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00>結果は先ほどの
find
メソッドと同じTaro Tanakaがヒットしていますが、SQL文に違いがあることがわかります。find_by
がWHERE
で検索しているのに対して、first
はORDER
で検索してます。また、
order
メソッドと組み合わせることで、primary key以外の属性に対しても一番先頭のオブジェクトを取得することができます。こっちの方が便利な気がする。> User.order(:email).first User Load (1.8ms) SELECT "users".* FROM "users" ORDER BY "users"."email" ASC LIMIT $1 [["LIMIT", 1]] => #<User:0x000055c0f67357a0 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>last
なんとなくメソッド名からわかりますね。
first
の逆、一番後ろのモデルオブジェクトを取得するメソッドです。> User.last User Load (4.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]] => #<User:0x000055c0f69aa2b0 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00>もちろん
order
と組み合わせて使うことも可能!> User.order(:email).last User Load (2.4ms) SELECT "users".* FROM "users" ORDER BY "users"."email" DESC LIMIT $1 [["LIMIT", 1]] => #<User:0x000055c0f5f5ab70 id: 2, name: "Hanako Yamada", email: "yamada@sample.com", created_at: Thu, 23 Jan 2020 17:10:31 JST +09:00, updated_at: Thu, 23 Jan 2020 17:10:31 JST +09:00, password_digest: nil>Update
データの更新の方法も大きく2つあります。どちらの場合もまずfindやfind_byを使って単一のオブジェクトを取得します。で属性のデータを変更したあとに
save
メソッドを使って更新するか、update
メソッドで更新するかです。属性を更新して
save
一つ目の方法は取得したオブジェクトの属性の値を変更してCreateと同じように
save
メソッドを使うことです。
Taro Tanaka
さんのtaro@sample.com
に変更してみましょう!> user = User.find(1) User Load (2.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]] => #<User:0x000055c0f67434b8 id: 1, name: "Taro Tanaka", email: "tanaka@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 17:08:36 JST +09:00> > user.email = "taro@sample.com" => "taro@sample.com" > user.save (0.6ms) BEGIN User Update (4.1ms) UPDATE "users" SET "email" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["email", "taro@sample.com"], ["updated_at", "2020-01-23 20:05:27.588947"], ["id", 1]] (7.3ms) COMMIT => true > User.find(1) User Load (3.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]] => #<User:0x000055c0f6893660 id: 1, name: "Taro Tanaka", email: "taro@sample.com", created_at: Thu, 23 Jan 2020 17:08:36 JST +09:00, updated_at: Thu, 23 Jan 2020 20:05:27 JST +09:00>
updated_at
も更新されてます!
update
メソッドを使うもう一つの方法はupdateメソッドを使う方法です。
今度は、Hanako Yamada
さんのhanako@sample.com
に変更してみます!> user = User.find(2) User Load (2.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]] => #<User:0x000055c0f692fbc8 id: 2, name: "Hanako Yamada", email: "yamada@sample.com", created_at: Thu, 23 Jan 2020 17:10:31 JST +09:00, updated_at: Thu, 23 Jan 2020 17:10:31 JST +09:00> > user.update(email: "hanako@sample.com") (0.6ms) BEGIN User Update (3.3ms) UPDATE "users" SET "email" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["email", "hanako@sample.com"], ["updated_at", "2020-01-23 20:09:03.502713"], ["id", 2]] (1.2ms) COMMIT => true > User.find(2) User Load (3.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]] => #<User:0x000055c0f6a42740 id: 2, name: "Hanako Yamada", email: "hanako@sample.com", created_at: Thu, 23 Jan 2020 17:10:31 JST +09:00, updated_at: Thu, 23 Jan 2020 20:09:03 JST +09:00>
update
メソッドの場合は更新したい属性と値を括弧の中で指定します。
update
メソッドもcreate
メソッドと同じで、データ保存に成功した場合はそのモデルオブジェクトが結果として返却されていますね。Delete
データの削除には
destroy
メソッドを使います。今回はJohn Smith
さんが退会した、みたいな感じでデータ削除してみましょう。> user = User.find(3) User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]] => #<User:0x000055c0f6af3040 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00> > user.destroy (0.4ms) BEGIN User Destroy (2.4ms) DELETE FROM "users" WHERE "users"."id" = $1 [["id", 3]] (1.3ms) COMMIT => #<User:0x000055c0f6af3040 id: 3, name: "John Smith", email: "john@sample.com", created_at: Thu, 23 Jan 2020 17:11:03 JST +09:00, updated_at: Thu, 23 Jan 2020 17:11:03 JST +09:00> > User.find(3) User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]] ActiveRecord::RecordNotFound: Couldn't find User with 'id'=3 from /usr/local/bundle/gems/activerecord-6.0.2.1/lib/active_record/core.rb:177:in `find'あっけないですね。
最終的にもともとJohn Smith
さんに割り当てられていたID=3
で検索してみたところ、ActiveRecord::RecordNotFoundの例外が発生していることからJohn Smith
さんがちゃんと削除されていることがわかります。後片付け
今日のデータをきれいにしておきましょう。今回もDBの再作成で。
> quit
quit
コマンドでRails consoleから抜け出せます。# exit
exit
コンテナからぬけまして、$ docker-compose down $ docker-compose run --rm web rails db:migrate:resetコンテナを停止して、
rails db:migrate:reset
を実行っと。まとめ
今回は、Modelの作成と基本的なモデル(データ)の操作をやってみました。
ここは僕の中でRailsの使いやすいところだなーと思っているのですが、SQLを隠してくれているんですよね。モデルのメソッドって形でデータのCRUDできるのは本当に使いやすい。次回はモデルにバリデーションをつけていこうと思います。今のままだと
name
や
あと、Userモデルにセキュアなパスワードの属性を追加していきます。単にパスワードをカラム追加してしまったら万が一データが盗まれた時に大変な個人情報流出です。Railsではセキュアなパスワードを扱うためにhas_secure_password
メソッドが用意されているのでその使い方を紹介します。では、次回も乞うご期待!ここまでお読みいただきありがとうございました!
Reference
- Ruby on Rails チュートリアル:実例を使って Rails を学ぼう
- Railsタイムゾーンまとめ - Qiita
- rails-i18n/ja.yml at master · svenfuchs/rails-i18n
- もう迷わない!CSS Flexboxの使い方を徹底解説 | Web Design Trends
- 【2019年版】Google Fontsの使い方:初心者向けに解説!
P.S. 間違っているところ、抜けているところ、説明の仕方を変えるとよりわかりやすくなるところなどありましたら、優しくアドバイスいただけると助かります。
- 投稿日:2020-02-10T15:08:10+09:00
[Rails]Ajaxを用いて非同期でコメント機能の実装
実装すること
Ajaxを用いて非同期通信を行い、下記のコードを書いていきます。
①コメントの作成を非同期で行う。
②コメントの件数もコメント作成と同時に非同期で更新されるようにする。
③3件目以上のコメントは隠して「もっと見る」を押すと見れるようにする。
④コメントの削除も非同期で行えるようにする。完成形
Ajaxとは
✔︎同期通信
クライアントとサーバーが交互に処理を行い、同調して通信を行うこと。
→webブラウザがリクエストを送り、webサーバーが作成したHTMLファイルをレスポンスとして返し、webブラウザがそれを受け取って表示することでコンテンツの内容を変化させている。・欠点
HTMLファイルを受け取ってから表示の処理を行うため、全体としてページの更新に時間がかかってしまう。
また、送信するデータも多くなりがちで、サーバーに負担がかかってしまう。✔︎Ajax(非同期通信)
Webブラウザ上でJavascriptが直接Webサーバーと通信を行い、取得したデータを用いて表示するHTMLを更新する。データのやりとりにはXMLが用いられ、JavascriptはDOMを使ってXMLやHTMLを操作する。HTMLそのものをやりとりするのではなく、更新に必要なデータのみやりとりするため、送信するデータの量は同期通信の時よりも少なくなり、サーバーへの負担が抑えられる。
参考文献:「この1冊で全部わかる Web技術の基本」
ER図
User:Item = 1:N
Item:Comment = 1:N
User:Comment = 1:N
CommentテーブルがItemとUserの中間テーブルになります。
ページ設計
投稿詳細ページ(item/show.html.erb)でコメントができる。
モデルの作成
Userモデル、Itemモデルは作成した前提で進めていきます。
作成手順は下記リンク先で説明しております。
[非同期投稿と非同期いいねの実装]https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448
それではCommentモデルを作成していきます。$ rails g model Comment content:text user_id:integer item_id:integer $ rails db:migrateアソシエーションの確認
Userモデル
dependent: :destroy
は、userが消えればitemもcommentも消えるようにするためです。app/models/user.rbclass User < ApplicationRecord has_many :items, dependent: :destroy has_many :comments, dependent: :destroy endItemモデル
app/models/item.rbclass Item < ApplicationRecord belongs_to :user has_many :comments, dependent: :destroy endCommentモデル
空欄で送信できないようバリデーションを掛けています。
app/models/comment.rbclass Comment < ApplicationRecord belongs_to :user belongs_to :item #バリデーション validates :content, presence: true endコントローラーの作成
$ rails g controller commentsルーティングの作成
コメントがどの投稿へのものであるかを識別するために、ルーティングのURLに投稿のIDを含めます。(ネストする)
具体的には「/item/10/comment/」といったURLになります。10がitem_idです。config/routes.rbRails.application.routes.draw do resources :users resources :items, only: [:index, :show, :new, :create] do resources :comments, only: [:create, :destroy] end endコントローラーの編集
items_controller.rb
order(created_at: :desc)
で、コメントを作成順に取ってきています。app/controllers/items_controller.rbclass ItemsController < ApplicationController def show @item = Item.find(params[:id]) @comment = Comment.new #新着順で表示 @comments = @item.comments.order(created_at: :desc) endcomments_controller.rb
・
build
を使うことで、@itemのidをitem_idに含んだ形でcommentインスタンスを作成します。
・保存がされると、render :index
によって「app/views/comments/index.js.erb」を探しにいきます。
・form_with
でフォームを送信した時は、デフォルトでjsファイルを探しにいく設定になっています。
htmlファイルを探しにいってほしい場合は、form_withの後にlocal: true
と記載する必要があります。
・form_for
でフォームを送信し、jsファイルを探しに行って欲しい場合はremote: true
と記載する必要があります。app/controllers/comments_controller.rbclass CommentsController < ApplicationController def create @item = Item.find(params[:item_id]) #投稿に紐づいたコメントを作成 @comment = @item.comments.build(comment_params) @comment.user_id = current_user.id @comment.save render :index end def destroy @comment = Comment.find(params[:id]) @comment.destroy render :index end private def comment_params params.require(:comment).permit(:content, :item_id, :user_id) end endビューの編集
items/show.html.erb
コメント一覧とコメント入力フォームはそれぞれパーシャルにしています。
id="comments_area"
をターゲットにこのdiv内をAjaxで書き換えます。app/views/items/show.html<div class="row"> <ul> <li class="comment-create"> <h3 class="text-left title">レビュー</h3> </li> <li id="comments_area"> <%= render partial: 'comments/index', locals: { comments: @comments } %> </li> </ul> <hr> <% if user_signed_in? %> <div class="comment-create"> <h3 class="text-left">レビューを投稿する</h3> <%= render partial: 'comments/form', locals: { comment: @comment, item: @item } %> </div> <% end %> </div>comments/_index.html.erb
・each文の
comments.first(2)
で最初の2件、comments.offset(2)
で最初の2件以外を取ってきています。
・ 3件目以上は通常隠して、「もっと見る」を押すことで表示しています。これは Bootstrapのcollapse
を使用しています。
参考: [Bootatrap4移行ガイド]https://cccabinet.jpn.org/bootstrap4/components/collapse
・コメントの削除は、remote:true
を付けることによって、コントローラのdestoryアクションから、index.js.erbを探しに行ってます。
また、(comment.item_id, comment.id)
とすることで、投稿のidとコメントのidを渡しています。app/views/comments/_index.html<!-- コメント内容(2件) ------------------------------------------------------------------> <%= comments.count %>件コメント <h6 class="more" data-toggle="collapse" data-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">もっと見る....</h6> <% comments.first(2).each do |comment| %> <% unless comment.id.nil? %> <li class="comment-container"> <div class="comment-box"> <div class="comment-avatar"> <%= attachment_image_tag comment.user, :profile_image, fallback: "no_image.jpg", class:"comment-image", size: "40x40" %> </div> <div class="comment-text"> <p><%= link_to "@#{comment.user.name}", users_user_path(comment.user.id) %></p> <div class="comment-entry"> <%= comment.content %> <% if comment.user == current_user %> <%= link_to item_comment_path(comment.item_id, comment.id), method: :delete, remote: true, class: "comment_destroy" do %> <i class="fas fa-trash" style="color: black;"></i> <% end %> <% end %> </div> <span class="comment-date pull-right"> <%= comment.created_at.strftime('%Y/%m/%d %H:%M:%S') %> </span> </div> </div> </li> <% end %> <% end %> <!-- コメント内容(3件目以降) ------------------------------------------------------------------> <div class="collapse" id="collapseExample"> <% comments.offset(2).each do |comment| %> <% unless comment.id.nil? %> <li class="comment-container"> <div class="comment-box"> <div class="comment-avatar"> <%= attachment_image_tag comment.user, :profile_image, fallback: "no_image.jpg", class:"comment-image", size: "40x40" %> </div> <div class="comment-text"> <p><%= link_to "@#{comment.user.name}", users_user_path(comment.user.id) %></p> <div class="comment-entry"> <%= comment.content %> <% if comment.user == current_user %> <%= link_to item_comment_path(comment.item_id, comment.id), method: :delete, remote: true, class: "comment_destroy" do %> <i class="fas fa-trash" style="color: black;"></i> <% end %> <% end %> </div> <span class="comment-date pull-right"> <%= comment.created_at.strftime('%Y/%m/%d %H:%M:%S') %> </span> </div> </div> </li> <% end %> <% end %> </div>comments/_form.html.erb
form_withで、
modle: [item, comment]
としています。itemとcommentはそれぞれ、投稿のビューで渡しているインスタンス変数です。投稿に紐づいたコメントを生成するため、ここでitem,commentのインスタンスを渡すことが必要になります。app/views/comments/_form.html<!-- コメント入力フォーム ------------------------------------------------------------> <%= form_with(model: [item, comment], url: item_comments_path(@item) ) do |f| %> <%= f.text_area :content, class: "input-mysize" %> <%= f.submit "送信", class: "btn btn-outline-dark comment-submit float-right" %> <% end %>comments/index.js.erb
・items/show.html.erbの中で
id="comments_area"
の箇所を書き換える処理になります。
$("#comments_area")
でid = "comments_area"
をターゲットとし、render 'index'
で指定しているcomments/_index.html.erbの内容で書き換えています。
・$("textarea").val('')
でコメント送信後のコメント入力フォームを空にしています。app/views/comments/index.js$("#comments_area").html("<%= j(render 'index', { comments: @comment.item.comments }) %>") $("textarea").val('')最後に
最後までご覧いただきありがとうございます。
初学者ですので間違っていたり、分かりづらい部分もあるかと思います。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。
Ajax関連でいいねとフォローの非同期も実装しています。
↓
[Rails]Ajaxを用いて非同期で投稿機能といいね機能の実装
https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448
[Rails]Ajaxを用いて非同期でフォロー機能の実装
https://qiita.com/yuto_1014/items/8d508b84fd0c2316ba01参考
- 投稿日:2020-02-10T09:44:19+09:00
yarnが原因でdocker-compose up ができない ( Your Yarn packages are out of date!)
docker-compose up
すると、下記のエラーが発生。解決していきます。web_1 | => Booting Puma web_1 | => Rails 6.0.2.1 application starting in development web_1 | => Run `rails server --help` for more startup options warning Integrity check: System parameters don't match error Integrity check failed error Found 1 errors. web_1 | web_1 | web_1 | ======================================== web_1 | Your Yarn packages are out of date! web_1 | Please run `yarn install --check-files` to update. web_1 | ======================================== web_1 | web_1 | web_1 | To disable this check, please change `check_yarn_integrity` web_1 | to `false` in your webpacker config file (config/webpacker.yml). web_1 | web_1 | web_1 | yarn check v1.21.1 web_1 | info Visit https://yarnpkg.com/en/docs/cli/check for documentation about this command. web_1 | web_1 | web_1 | Exiting myapp_web_1 exited with code 1原因
ローカルでyarnを最新にしていたから整合性が取れなくなったのかな。と考えています。
yarnのパッケージに古いものが含まれているので、アップグレードしてね!といった様子。
なので、yarn upgradeこれで直るか!と思いましたが、エラー文は変わらず。
結論 check_yarn_integrity: false に変更する
先程のエラー文をもう一度眺める。
web_1 | To disable this check, please change `check_yarn_integrity` web_1 | to `false` in your webpacker config file (config/webpacker.yml).下記の様に変更。
config/webpacker.yml# Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules check_yarn_integrity: false
check_yarn_integrity
については、
webpacker の check_yarn_integrity オプションについて調べてみた
が参考になります。再度、
docker-compose up
を実行すると、無事起動しました。
- 投稿日:2020-02-10T09:10:48+09:00
Docker+RubyonRails でよく使うコマンドメモ
アプリを作成するときはDockerを利用するのですが、Railsコマンド打つとき最初よく分からなくて躓いてたんで自分用メモとして記します。
gemのインストール
docker-compose build --no-cacheDocker-compose downしてから行ってください。
dockerでrailsコマンドを打つ
docker-compose run --rm web railsDockerfileやdocker-compose.ymlの変更を反映、railsサーバーを再起動
docker-compose up --buildMySQL
docker-compose exec db mysql -u root -pMysqlは「database.yml」で指定したパスワードで中身を見ることができます。
まとめ
よくこの辺を使うので参考になればと思います。
- 投稿日:2020-02-10T07:50:28+09:00
rails 星評価の実装
rails 星評価を実装する手順
*players controllers で作業を行なっています。
まずやること
3つの星の画像を保存する
https://github.com/wbotelhos/raty/tree/master/lib/images
上記のファイルの中の「satar」から始まる3つの画像をapp/assets/imagesにダウンロードするjquaryファイルを保存する
https://github.com/wbotelhos/raty/blob/master/lib/jquery.raty.js
上記のファイルをapp/assets/javascript/jquery.raty.jsファイルを作成し、コピーするapp/assets/javascript/application.jsに
「//=require jquary」と記入 *require_treeよりも必ず前に書くGem.fileに
「gem 'jquary-rails'」と記入後bundle installrate(float型)のカラムを作成 (rails g migration AddRateToテーブル名 rate:float)
手順1 routes.rbにsearchアクションを追加する
routes.rbcollection do get 'search' endcollection do => resourcesに含まれないアクションを追加するときに使用する
手順2 controllerでsearchアクションの定義づけをする
players.controllerdef search @players = Player.search(params[:search]) end手順3 rateカラムを保存するフォームを作成する
new.html<input class="number" max="5.0" min="0" name="rate" step="0.5" type="number" placeholder="強さ(5段階評価)"/>type="number" => タグ内で使用すると数値の入力欄が作成される
手順3 rateの数字に応じて星評価を表示する
show.html<div id = "star-rate-<%= @player.id %>"></div> <script> $('#star-rate-<%= @player.id %>').raty({ size: 36, starOff: '<%= asset_path('star-off.png') %>', starOn : '<%= asset_path('star-on.png') %>', starHalf: '<%= asset_path('star-half.png') %>', half: true, readOnly: true, score: <%= @player.rate %>, }) </script>
- id => classと同じ役割。ただ、WEB1ページに1回しか使用できない。idクラスを呼ぶときは「#」を先頭につける
- $ => jQuaryを呼び出す記号
- ('#star-rate-<%= @player.id %>') => body要素内のstar-rate-<%= @player.id %>にアクセスする
- .raty => ratyのプロバティを使用することができるようになる
- asset_path => asset/imageの画像を表示する
- readOnly: true, => 画面表示のみで変更できないようにする
以上の操作で私は星評価を作成することができました。
- 投稿日:2020-02-10T06:25:16+09:00
【Rails】Railsに保存した画像ファイルをVue.js側で表示するサンプルコード(Base64、Active Storage使用)
はじめに
Rails APIモード→Vue.jsでの画像データのやりとりをする方法を残します。(Base64、Active Storage使用)
今回の対象
Vue.js→Rails(こちらの記事をご参照下さい。引用失礼します!)
Rails→Vue.js ←ココ環境
OS: macOS Catalina 10.15.3 Ruby: 2.6.5 Rails: 6.0.2.1 Vue: 2.6.10 axios: 0.19.0前提:実施済とみなすこと
※引用記事の例を使用します。
rails new
- Active Storageのインストール
Post
モデルの作成- Vue.jsのインストールと利用するための準備
eyecatch
として画像ファイルがPost
モデルのインスタンスに添付されている- (今ココ)
1.【Rails】画像ファイルをBase64形式でエンコードするメソッドを定義する
base64_module.rb# 各モデルのレコードに添付された画像ファイルをBase64でエンコードする def encode_base64(image_file) image = Base64.encode64(image_file.download) # 画像ファイルをActive Storageでダウンロードし、エンコードする blob = ActiveStorage::Blob.find(image_file[:id]) # Blobを作成 "data:#{blob[:content_type]};base64,#{image}" # Vue側でそのまま画像として読み込み出来るBase64文字列にして返す end2.【Rails】Active Storageでアタッチした画像ファイルを読み込み
posts#show
で投稿データを返すとします。posts_controller.rbdef show post = Post.find(params[:id]).as_json #JSON形式にしておく eyecatch = post.eyecatch #eyecatchは添付した画像ファイル if eyecatch.present? post['image'] = encode_base64(eyecatch) # 画像ファイルを1.で定義したメソッドでBase64エンコードし、renderするデータに追加する end render json: post end3.【Rails】ルーティングを設定
Rails.application.routes.draw do # 略 get 'posts', to: 'posts#show' # 略 end4.【Vue.js】画像を取得し、表示するコンポーネントを作成
show.vue<template> <div> <p>投稿表示フォーム</p> <!-- preventでsetPost()メソッドがページ遷移なく発火する --> <form v-on:submit.prevent="setPost()"> <p> <label>Title</label> <input name="post.title" type="text" v-model="post.title"><br /> </p> <p> <label>Body</label> <input name="post.body" type="text" v-model="post.body"><br /> </p> <!-- post.idを指定して... --> <p> <label>IDを指定</label> <input name="post.id" type="text" v-model="post.id"> </p> <!-- ここを押してデータ取得 --> <input type="submit" value="ここを押して投稿データ取得" > <!-- Base64形式であればimgタグでそのまま読み込みが可能 --> <img :src="post.image" alt="post.image"> </form> </div> </template> <script> import axios from 'axios' export default { name: 'sample', data() { return { post: {}, } }, methods: { setPost() { axios.get('/posts', {params: {id: this.post.id}}) //入力したidに応じてpostが返ってくる .then(response => { this.post = response.data }) .catch( error => { console.error(error) }) } } } </script>※実際は自分で
id
を指定することはないと思いますので、状況に応じて変更して頂ければと思います。以上です!
おわりに
最後まで読んで頂きありがとうございました
どなたかの参考になれば幸いです
参考にさせて頂いたサイト(いつもありがとうございます)
- 投稿日:2020-02-10T02:32:27+09:00
RedisベースのGo Background Jobライブラリー
Asynq: RedisベースのGo Background Jobライブラリー
Ruby,Railsのサークルの中ではResqueやSidekiqがBackground-jobのライブラリーで人気ですが、Goのコミュニティーの中でこれといったライブラリーがあまり見つからなかったので自分でSidekiqのデザインを基にしてBackground Jobライブラリーを書いてみました(github.com/hibiken/asynq)。
GoやRedisに興味があってGithubでコントリビュートするプロジェクトを探していたら、是非!
- 投稿日:2020-02-10T00:33:07+09:00
deviseのログイン機能でユーザー名にバリデーションをかける。
- 投稿日:2020-02-10T00:08:39+09:00
Rails/Laravel使いに送るドメインモデル~アクティブレコードの功罪~
みなさん、こんにちは!RailsやLaravel使ってますか? ActiveRecord(LaravelではEloquent)ってめっちゃ便利ですね。ただ便利ゆえにActiveRecord以外の存在を知らない人がいるので、メリット・デメリットをまとめてみました。最終的にはドメインモデル入門になっています。
最初にRailsやLaravelから入った人(つまり僕)にありがちなのですが、ActiveRecordがどのようなものか理解せずに実装するため、ActiveRecordなのにロジックがないことがあります。また、ActiveRecordパターン以外を知らないのでActiveRecordのメリット・デメリットを理解してません。そこでActiveRecordがどのようなものかを説明していきたいと思います。
ただ、一年ほどRailsのコードに触れていないので、もし書き方がおかしかったら容赦なく突っ込んでくださし。また個人の見解が多分に含まれているので、皆さんの思うところがあるかもしれません。その時は、ガンガン言ってください。
注意
Railsのよさは結合度の高さによる実装の速さです。RailsはActiveRecordを前提としています。後半に紹介する
POROs
やRepository
はRailsWayから外れたものです。この記事はRailsにRepository層を設けることを勧めているわけではなく、このような考え方もあるよという記事です。もしDDD前提の設計をしたいのであればHanamiというフレームワークがおすすめです。
https://magazine.rubyist.net/articles/0056/0056-hanami.htmlActiveRecordとは
マーチン・ファウラーという方が書いた「Pattern of Enterprise Application Architecture (PofEAA)」という本に、
データベーステーブルまたはビューの行をラップし、データベースアクセスをカプセル化してデータにドメインロジックを追加するオブジェクト
と書かれています。ここからActiveRecordの役割が3つあることがわかります。
- データベースアクセス
- テーブルの行に対応するデータの保持
- ドメインロジックをもつ
データベースアクセスは
rubyuser = new User(params) user.saveのようにモデル自体に
save
やcreate
を持つことです。テーブルの行に対応するデータの保持は
id name 1 ハト太郎 2 ハム助 とテーブルがある場合、
rubyuser = User.find(1) puts(user.id) #ハト太郎のように行単位で属性を保持するインスタンスを生成できます。
ドメインロジックをもつは
例えば、20歳以下であれば料金が半額とする場合はコントローラーに
rubyif user.age <= 20 then # 料金半額 endとするのではなく、モデルにロジックを追加して
rubyclass User < ApplicationRecord 中略 def isPriceHalf self.age <= 20 end endrubyif user.isPriceHalf then # 料金半額 endというふうにユーザーに関するロジック(ドメインロジックといいます)をユーザーモデルにカプセルしてしまうことです。重要なのは自身のデータを用いたインスタンスメソッドを実装することです。
歴史
そもそもアクティブレコードはドメインモデルの一つです。ドメインモデルはかんたんに説明するとオブジェクト指向にのっとり、データとそれに付随するロジックをクラスに閉じ込めたものになります。なぜそうするのが良いかというと、ロジックがデータと一緒にあることで、コードの重複が防げるからです。ここらへんは「現場で役立つシステム設計の原則(増田享)」という本に詳しく載っています。
データに付随するロジックをコントローラ層やサービス層に書くのは自由ですが、一応そういう考え方もあることを知っておいてください。
ActiveRecordのメリット・デメリット
メリット
- テーブルと1対1にモデルが存在するので、テーブル設計が終わったあとすんなりと実装に入れる
- データと永続化メソッドが1つのモデルにあるので、実装スピードが早い
デメリット
- ツールありき。自ら実装するのは割と手間(RailsとLaravelは標準装備なのでこれはデメリットではない)。
- テーブルと1対1にモデルが存在するので、モデルがテーブルに引っ張られる。例えばテーブルを変更するとモデルに影響を与えるので、テーブルとモデルの結合度が高い。
- ツールを利用した場合、モデルの継承(extend)を利用する事が多く1つしかできない継承を消費してしまう(Rubyはmixinという機能があるのであまり問題にならないかもしれない)。
ちなみにPofEAAには次のように書かれています。
アクティブレコードの最大のメリットは、シンプルな構造である。アクティブレコードの構築は容易であり、また理解しやすい。最大の問題は、アクティブレコードが有効であるのが、アクティブレコードオブジェクトがデータベーステーブルと直接対応している(同一構造スキーム)場合だけという点である。
これはテーブルと1対1のモデル設計のメリット・デメリットですね。ほかにデメリットとして
ビジネスロジックが複雑な場合には、オブジェクトの直接的な関係、コレクション、継承などを使用したいとまず考えるだろう。しかし、これらの部品は簡単にはアクティブレコードにマッピングできず、また、断片的に追加すると状況はより複雑になる。以上の理由からデータマッパーの使用を考えるようになる。
と書かれています。オブジェクト指向とリレーショナル・データベースは同一のものではありません。例えばrubyでの配列
rubyarray = ["a", "b", "c']をデータベースに保存するときにどのように保存すればよいでしょうか? リレーショナルデータベースはデータの横持ちが苦手なので、縦持ちにしてテーブルに保存するかもしれません。
また、
Carクラス
とCarクラスを継承するHybridCarクラス
のデータを保存することを考えると、それぞれを保存するのはどうすればよいでしょうか?rubyclass HybridCar < Car def doSomething end endおそらく、一般的には
type
属性を加えて継承を表現するかもしれません。様々な方法があるかもしれませんが、NoSQLとは違ってリレーショナルデータベースはこのような継承を表現するのが苦手です(苦手であってできないことはない)。アクティブレコードはリレーショナルデータベースのテーブルと密結合なので、テーブルが苦手な表現はアクティブレコードも苦手です(くどいができないことはない)。Plain Old Ruby Objects(POROs)について
いままでActiveRecordしか使ったことのない方は、ぜひ他のモデルパターンを知ってほしいです。これから紹介するのは
POROs
とRepository
です。もしデータベースとロジックの分離をしたいのであれば、こちらのパターンはおすすめです。Plain Old Ruby Objectsは特になんのひねりもなくただのRubyの
Class
です。マーティンが単純なJavaのクラスをJavaBeansに対してPlain Old Java Objects (POJOs)と呼んだことにちなんでいます。class Dog def initialize(name) @name = name end def doSomething # do something end endただのRubyのClassなのでActiveRecordなどは継承していません。特定のツールに依存しないので、自分の自由に実装できます。外部API由来のモデルもDB由来のモデルもすべて同じように扱えます。継承を消費しないのでデザインパターンを適用しやすくなります。POROsはオブジェト指向を気持ちよく実装できます。
Repositoryパターンについて
RepositoryパターンはドメインモデルのIOを担当します。今回だと上記のPOROsと外部API通信やデータベースへの永続化を担当します。Repositoryという層をはさむ事によってモデルはデータベースを知る必要がありません。また、データベース以外に外部APIをモデルにマッピングすることもできます。背後にActiveRecordを使ってもQueryBuilderを使っても構いません。
注意: Repositoryパターンは本来インターフェイスを用いて実装することが多いです。今回はクラスとして実装しています。
rubyclass DogRepository def self.findDogById(id) # ActiveRecordもしくはQueryBuilderによって実装 end def self.save(dog) # ActiveRecordもしくはQueryBuilderによって実装 end endrubyclass DogController < ApplicationController def show @dog = DogRepository.findDogById(params.id) end def create dog = new Dog(params) # dog.save()ではない DogRepository.save(dog) end endRepositoryパターンを使うとモデルはActiveRecordではなく、モデル自身に永続化のメソッドがないので、
dog.save()
ではなく、DogRepository.save(dog)
となっていることに注目してください。Repositoryパターンではモデルは自身のロジックに集中してデータベースへの永続化の処理はRepositoryが担当します。モデルは永続化については気にしなくて良いのです。
- 投稿日:2020-02-10T00:02:30+09:00
Rails6 バリデーションをかける
目的
- 空欄でのDB登録を避けるためのバリデーションの記載方法をまとめる
空での保存を避けるバリデーション
下記の記載を当該のモデルファイルに記載する。
class Post < ApplicationRecord validates :バリデーションをかけたいカラム名, {presence: true} end空での保存を防ぎ、かつ総文字数を140文字以下にするバリデーション
下記の記載を当該のモデルファイルに記載する。
class Post < ApplicationRecord validates :content, {presence: true, length: {maximum: 140}} end