20210429のRailsに関する記事は28件です。

docker-compose + rails の環境構築

https://github.com/katoy/test-with-docker に docker-compose + rails の環境構築する手段を記載しました。 現時点では、 system テストで スクリーンショット取得できるところまでです。 今後 request テスト、 models テスト例を追記していく予定です。 また、 metoric 計測レレポート、 profile 計測、 DB の GUI クライアントなどについても記載していきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

form_withで指定するurl:とmodel:の違いについて

はじめに プログラミング初学者のため、自分の理解できている範囲内で言語化しています。 何か間違っている情報や改善点などありましたら、コメントいただけますと幸いです。?‍♂️ これについて書こうと思ったきっかけ 簡単なお問い合わせ機能を作成しようとしたときに、 form_withのmodel:でモデルのインスタンスを指定したら、 pathが違うというエラーが出たので、url:で指定したら、成功した。 が、、、データベースに情報を保存したかったため、model:でどうしてもやりたかったが エラーがなかなか倒せなかったので、form_withをまとめてみようと思った? 最終的な解決策 今回は、url:も指定してみました html.erb <%= form_with model: @information, url: informations_path, class: 'form' do |f| %> model:を使うと、作ってもいないpathを使おうとしていたので、url:も指定して解決することができた。 モデルだけ指定して、変なpathや、うまくいかない人はこれを試すと良い ちなみにモデルの名前を複数形にしたりすると、このようなエラーになるみたい、、、 私はいろんなとこを確認しても、原因がわからなかったので力技みたいですがこれで解決 url: model: の違い 大まかな違いは、データベースに情報を保存するかしないかである 保存する → model: 保存しない → url: を使う。 これによってコントローラーのストロングパラメーターの記述も変わる url: → params.permit(:name, :email) model: → params.require(:user).permit(:name, :email) まとめ 今回は、エラーの原因がわからなかったが、とりあえず完成を優先したかったので解決してよしとしたが、原因を知らないのは悔しい、、、 また、仮にurl:のままでお問い合わせ機能を作ったとして、うまく投稿できなかったときのバリデーションのエラーメッセージの表示はどうするのか気になった。 本来であれば、if model.errors.any? を使うから、modelの部分はどうなる? 疑問がさらに疑問を産んだため、いったんここで打ち切り? また次の機会で投稿してみようと思う腕
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

form_with使用の際model:を使ったときにエラーになった話

はじめに プログラミング初学者のため、自分の理解できている範囲内で言語化しています。 何か間違っている情報や改善点などありましたら、コメントいただけますと幸いです。?‍♂️ 何が起きたのか html.erb <%= form_with model: @information, class: 'form' do |f| %> 簡単なお問い合わせ機能を作成しようとしたときに、 form_withのmodel:でモデルのインスタンスを指定したら、 pathが違うというエラーが出たので、url:で指定したら、成功した。 が、、、データベースに情報を保存したかったため、model:でどうしてもやりたかったが エラーがなかなか倒せなかったので、調べようと思った? このエラーのよくある原因 1.『@モデル名』 の部分が記述ミス 2. コントローラーでインスタンス変数がうまく設定できていない 3. モデル自体を複数形で作ってしまっている まずはそこをチェック。** それ以外の人はこの方法で行けるかも?‍♂️ 最終的な解決策 url:も指定して、正しいpathを記述する html.erb <%= form_with model: @information, url: informations_path, class: 'form' do |f| %> model:を使うと、作ってもいないpathを使おうとしていたので、url:も指定して解決することができた。 モデルだけ指定して、変なpathや、うまくいかない人はこれを試すと良い ちなみにモデルの名前を複数形にしたりすると、このようなエラーになるみたい、、、 私はいろんなとこを確認しても、原因がわからなかったので力技みたいですがこれで解決 url: model: の違い 大まかな違いは、データベースに情報を保存するかしないかである 保存する → model: 保存しない → url: を使う。 これによってコントローラーのストロングパラメーターの記述も変わる url: → params.permit(:name, :email) model: → params.require(:user).permit(:name, :email) まとめ 今回は、エラーの原因がわからなかったが、とりあえず完成を優先したかったので解決してよしとしたが、原因を知らないのは悔しい、、、 また、仮にurl:のままでお問い合わせ機能を作ったとして、うまく投稿できなかったときのバリデーションのエラーメッセージの表示はどうするのか気になった。 本来であれば、if model.errors.any? を使うから、modelの部分はどうなる? 疑問がさらに疑問を産んだため、いったんここで打ち切り? また次の機会で投稿してみようと思う腕
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアルまとめ 第4章

第4章について この章では主にRubyのメソッド、クラスについて紹介している。Rails風となっているがRails特有のメソッドもあるのでRubyで同じようにが使えるとは限らないが、RailsにおけるRubyの使い方について学習した。なおシングルクオートとダブルクオートの違いや他と共通するような簡単な言語の仕様などについては省略する。 目次 1.ヘルパーについて 2.rails console 3.rubyのクラス 4.rubyのメソッドやシンボル 5.おまけ 1. ヘルパーについて Railsにおいてビューで使えるような関数を新規で作成する(カスタムヘルパー)にはapp/helpers配下にある〇〇.helper.rbに関数を定義する。もし仮に全てのページで使用するようなヘルパーを作るのならapplication_helper.rbに、StaticPagesコントローラ用のヘルパーを作るのならstatic_pages.helper.rbにヘルパーの内容を書く。 ちなみにRubyでは関数の引数は型宣言しなくても使用することができる。 2. rails console Railsアプリケーションを対話的に操作できるコマンドラインツール。 Rails consoleを使うとちょっとしたコードを試したり、データベースとのやりとりをここでしたりと開発において便利なツールとなっている。 Rails consoleを使う際デフォルトではdevelopment環境になっているがrails console -e (developmentかtestかproduction)のようにオプションに-e(--environment)を指定することで起動する環境を指定することができる。 また、そのままconsoleを使ってしまうとデータが変更できてしまうのでオプションに-s(--sandbox)を付けることデータを変更することなくコードを試すことができる。 3. rubyのクラス Rubyではnilを含めてあらゆるものがオブジェクトとして扱われる。 例: String < Object < BasicObject Rubyの全てのクラスは最終的にはBasicObjectを継承している。 またRubyではStringクラスのような基本クラスを拡張することもできる。 class String def palindrome? self = self.reverse end end *selfはオブジェクト自身。 4. rubyのメソッドやシンボル Railsチュートリアルで出てきたメソッドは以下の通り。以下は全てRubyのメソッド。 to_a ... 配列に変換する puts, print ... 文字列や変数などを出力する。(putsは改行あり、printはなし) nil? ... オブジェクトが存在するかどうか(そもそも変数が定義されていないなど) empty? ... オブジェクトが空かどうか(空白はfalseになる) blank? ... オブジェクトが空白かどうか(空白やnilはtrueになる) shuffle ... 配列オブジェクトの位置をランダムに移動させる reverse ... オブジェクトを左右反対に並べ替える split ... 文字列を1文字列切り分けて配列にする。引数()に文字を指定するとその文字を区切り文字とすることができる join ... 配列を繋げて一つの文字列にする。引数に文字を指定すると区切り文字と合わせて文字列にすることができる push() ... 配列に引数に指定された要素を追加する 0..9 .... 0~9までの範囲を指定することができる %w [] ... []内にある要素を文字列の配列に変換する length ... 文字列の長さを表示する each文 ... 配列や範囲オブジェクトの要素を一つずつ変数に取り出して命令を実行する。 オブジェクト.each {|変数| 命令文} あるいは オブジェクト.each do |変数| ~~~ endのように使う。 ハッシュではシンボルを使うことができる。シンボルは{:name => '名前'}か{name: '名前'}のように使う。 Rubyでは配列の要素は数字で指定するが、ハッシュでは文字列を要素に指定することができる。 5. おまけ consoleを使っていて気付いた事だがRubyの比較演算子は他の言語と違って厳密な比較はない。むしろ===は'=='に比べてゆるい比較を行う。===は左辺がクラスオブジェクトなら右辺がそのクラスのインスタンスであるかどうかを比較する。 以下の記事がわかりやすかったので参考にした。 Rubyの===演算子についてまとめてみた 参考文献 配列とハッシュとシンボルは紛らわしいので整理!! nil?empty?blank?present?の使い分け
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]deviseを使ったビューファイルの作成

はじめに RailsでWebアプリケーションを作る時にdeviseを使ったので備忘録として残しておきます。 今回はdeviseで作成できるビューファイルの作成をしていきたいと思います。 ビューの作成方法 deviseでは認証周りが実装されたビューファイルを一行のコマンドで作成することができます。 作成する場合はターミナルで下記のコマンドを実行します。 $ rails g devise:views するとapp/viewの下にdeviseフォルダが作成されます。 さらにdeviseフォルダ下に7つのフォルダが作成されています。 これらの各フォルダがどのビューに対応しているかは以下になります。 フォルダ名 フォルダの内容 confirmations パスワード再発行のフォーム mailer メール認証のフォーム passwords パスワード関連のフォーム registrations ユーザー登録やユーザー編集のフォーム sessions ログインフォーム shared 各フォームに表示されている、リンクのパーシャル(部分テンプレート)が入っている unlocks アカウントロック通知を行うフォーム これらのビューは簡素なデザインなので自分でアレンジすると良いです。 以上です。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Railsチュートリアル】第8章 ユーザー登録 演習と解答

Ruby on Railsチュートリアル 第7章の演習問題と解答をまとめました。 第8章 基本的なログイン機構 - Railsチュートリアル 8.1.1 Sessionsコントローラ 8.1.1 - 1 GET login_pathとPOST login_pathとの違いを説明できますか? 少し考えてみましょう。 config/routes.rb Rails.application.routes.draw do # . # . # . get '/login', to: 'sessions#new' post '/login', to: 'sessions#create' delete '/logout', to: 'sessions#destroy' resources :users end GET login_path '/login'にアクセスした際に'sessions#new'を実行する POST login_path '/login'の情報を'sessions#create'に渡す 8.1.1 - 2 ターミナルのパイプ機能を使ってrails routesの実行結果とgrepコマンドを繋ぐことで、Usersリソースに関するルーティングだけを表示させることができます。同様にして、Sessionsリソースに関する結果だけを表示させてみましょう。現在、いくつのSessionsリソースがあるでしょうか? ヒント: パイプやgrepの使い方が分からない場合は 『開発基礎編: コマンドライン』の 「grepで検索する」を参考にしてみてください。 tarminal $ rails routes | grep users# signup GET /signup(.:format) users#new users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PATCH /users/:id(.:format) users#update PUT /users/:id(.:format) users#update DELETE /users/:id(.:format) users#destroy tarminal $ rails routes | grep sessions# login GET /login(.:format)sessions#new POST /login(.:format)sessions#create logout DELETE /logout(.:format)sessions#destroy Sessionsリソースは3つ 8.1.2 ログインフォーム 8.1.2 - 1 リスト 8.4で定義したフォームで送信すると、Sessionsコントローラのcreateアクションに到達します。Railsはこれをどうやって実現しているでしょうか? 考えてみてください。ヒント:表 8.1とリスト 8.5の1行目に注目してください。 生成したログインフォームのHTML <!--1行目に注目する--> <form accept-charset="UTF-8" action="/login" method="post"> <!-----------------> <input name="authenticity_token" type="hidden" value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" /> <label for="session_email">Email</label> <input class="form-control" id="session_email" name="session[email]" type="email" /> <label for="session_password">Password</label> <input id="session_password" name="session[password]" type="password" /> <input class="btn btn-primary" name="commit" type="submit" value="Log in" /> </form> フォームの情報を、「/login」というURLに対してPOSTする そうすると…↓ config/routes.rb Rails.application.routes.draw do # . # . # . get '/login', to: 'sessions#new' # '/login'にPOSTすると、Sessionsコントローラのcreateアクションに到達する post '/login', to: 'sessions#create' #------------------------------------------ delete '/logout', to: 'sessions#destroy' resources :users end 8.1.3 ユーザーの検索と認証 8.1.3 - 1 Railsコンソールを使って、表 8.2のそれぞれの式が合っているか確かめてみましょう. まずはuser = nilの場合を、次にuser = User.firstとした場合を確かめてみてください。ヒント: 必ず論理値オブジェクトとなるように、4.2.2で紹介した!!のテクニックを使ってみましょう。例: !!(user && user.authenticate('foobar')) コンソール $ rails console >> user = nil => nil >> !!(user && user.authenticate('foobar')) => false >> user = User.first => #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org",...(省略) >> !!(user && user.authenticate('password')) => true 8.1.5 フラッシュのテスト 8.1.5 - 1 8.1.4の処理の流れが正しく動いているかどうか、ブラウザで確認してみてください。特に、flashがうまく機能しているかどうか、フラッシュメッセージの表示後に違うページに移動することを忘れないでください。 動作確認済み 8.2.1 log_inメソッド 8.2.1 - 1 有効なユーザーで実際にログインし、ブラウザからcookiesの情報を調べてみてください。このとき、sessionの値はどうなっているでしょうか? ヒント: ブラウザでcookiesを調べる方法が分からない? 今こそググってみるときです!(コラム 1.2) 8.2.1 - 2 先ほどの演習課題と同様に、Expiresの値について調べてみてください。 ↑の画像の② (Expires(sessionの有効期限):ブラウザセッション終了時) 8.2.2 現在のユーザー 8.2.2 - 1 Railsコンソールを使って、User.find_by(id: ...)で対応するユーザーが検索に引っかからなかったとき、nilを返すことを確認してみましょう。 ターミナル $ rails console >> User.find_by(id: 100) => nil 8.2.2 - 2 先ほどと同様に、今度は:user_idキーを持つsessionハッシュを作成してみましょう。リスト 8.17に記したステップに従って、||=演算子がうまく動くことも確認してみましょう。 ターミナル $ rails console >> session = {} => {} >> session[:user_id] = nil => nil >> @current_user ||= User.find_by(id: session[:user_id]) User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT ? [["LIMIT", 1]] => nil >> session[:user_id] = User.first.id User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => 1 # ↓find_byが呼び出され、データベースへ問い合わせた結果をインスタンス変数に保存する >> @current_user ||= User.find_by(id: session[:user_id]) User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] => #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2021-04-28 02:52:06", updated_at: "2021-04-28 02:52:06", password_digest: [FILTERED]> # ↓@current_userにユーザー情報が代入されているため、find_byは呼び出されない >> @current_user ||= User.find_by(id: session[:user_id]) => #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2021-04-28 02:52:06", updated_at: "2021-04-28 02:52:06", password_digest: [FILTERED]> >> 8.2.3 レイアウトリンクを変更する 8.2.3 - 1 ブラウザのcookieインスペクタ機能を使って(8.2.1.1)、セッション用のcookieを削除してみてください。ヘッダー部分にあるリンクは非ログイン状態のものになっているでしょうか? 確認してみましょう。 セッション用cookie削除後にページをリロード。 ヘッダーの表示が変わりました。cookie自体は新しく作成されるようです 8.2.3 - 2 もう一度ログインしてみて、ヘッダーのレイアウトが変わったことを確認してみましょう。その後、ブラウザを再起動させ、再び非ログイン状態に戻ったことも確認してみてください。注意: もしブラウザの[閉じたときの状態に戻す]機能をオンにしていると、セッション情報も復元される可能性があります。もしその機能をオンにしている場合、忘れずにオフにしておきましょう(コラム 1.2)。 sample_appの開発をGoogle Chromeで行なっています。 ブラウザの問題なのか、ログイン後にブラウザを終了・再起動を行なってもログイン状態が 保持されていました。 Chromeの設定を変更するなどしましたが、この演習問題の理想としている動作は実現できませんでした。 8.2.4 レイアウトの変更をテストする 8.2.4 - 1 リスト 8.15の8行目にあるif userから下をすべてコメントアウトすると、ユーザー名とパスワードを入力して認証しなくてもテストが通ってしまうことを確認しましょう(リスト 8.26)。通ってしまう理由は、リスト 8.9では「メールアドレスは正しいがパスワードが誤っている」ケースをテストしていないからです。このテストがないのは重大な手抜かりですので、テストスイートで正しいメールアドレスをUsersのログインテストに追加して、この手抜かりを修正してください(リスト 8.27)。テストが red (失敗)することを確認し、それから先ほどの8行目以降のコメントアウトを元に戻すと green (パス)することを確認してください(この演習の修正は重要なので、この先の 8.3のメインのコードにも修正を反映してあります)。 「8行目にあるif userから下をすべてコメントアウト」という日本語を理解するのに時間がかかった おそらくこういうこと。 (問題文のすぐ下にサンプルコードがあった…) app/controller/sessions_controller.rb class SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user# && user.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy end end ターミナル $ rails test Running via Spring preloader in process 6954 Started with run options --seed 42564 24/24: [============================================] 100% Time: 00:00:01, Time: 00:00:01 Finished in 1.32927s 24 tests, 61 assertions, 0 failures, 0 errors, 0 skips ログインテストを追加 test/integration/users_login_test.rb require 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "login with valid email/invalid password" do get login_path assert_template 'sessions/new' post login_path, params: { session: { email: @user.email, password: "invalid" } } assert_template 'sessions/new' assert_not flash.empty? get root_path assert flash.empty? end . . . end テストの流れとしては /loginにアクセス 'sessions/new'に正しいEmailアドレス(@user.email)、誤ったパスワード(invalid)をPOST 'sessions/new'の表示を確認 flashメッセージが空でないか確認(flashが存在しているかチェック) '/'にアクセス flashメッセージが空であるか確認(存在していないことのチェック) 8.2.4 - 2 “safe navigation演算子”(または“ぼっち演算子)と呼ばれる&.を用いて、リスト8.15の8行目の論理値(boolean値)のテストを、リスト 8.28 のようにシンプルに変えてください。Rubyのぼっち演算子を使うと、obj && obj.methodのようなパターンをobj&.methodのように凝縮した形で書けます。変更後も、リスト 8.27のテストがパスすることを確認してください。 sessionsコントローラーのcreateメソッドの中身をサンプルコードに従って修正。 テストもパスする app/controller/sessions_controller.rb class SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) # if user# && user.authenticate(params[:session][:password]) # ↑obj && obj.methodのようなパターンを    # ↓obj&.methodのように凝縮した形で書ける if user&.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy end end 8.2.5 ユーザー登録時にログイン 8.2.5 - 1 リスト 8.29のlog_inの行をコメントアウトすると、テストスイートは red になるでしょうか? それとも green になるでしょうか? 確認してみましょう。 テストはREDになる。  app/controller/users_controller.rb class UsersController < ApplicationController def show @user = User.find(params[:id]) end def new @user = User.new end def create @user = User.new(user_params) if @user.save # log_inメソッドの呼び出しをコメントアウトする # log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end test/integrarion/users_signup_test/rb require 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest . . . test "valid signup information" do # 省略 # test_helperで定義したis_logged_in?メソッドがfalseを返すためREDになる assert is_logged_in? end ned 8.2.5 - 2 現在使っているテキストエディタの機能を使って、リスト 8.29をまとめてコメントアウトできないか調べてみましょう。また、コメントアウトの前後でテストスイートを実行し、コメントアウトすると red に、コメントアウトを元に戻すと green になることを確認してみましょう。ヒント: コメントアウト後にファイルを保存することを忘れないようにしましょう。また、テキストエディタのコメントアウト機能については『開発基礎編: テキストエディタ』の 「コメントアウト機能」などを参照してみてください。 動作確認のため、省略 8.3 ログアウト 8.3 - 1 ブラウザから[Log out]リンクをクリックし、どんな変化が起こるか確認してみましょう。また、リスト 8.35で定義した3つのステップを実行してみて、うまく動いているかどうか確認してみましょう。 動作確認のため、省略 8.3 - 2 cookiesの内容を調べてみて、ログアウト後にはsessionが正常に削除されていることを確認してみましょう。 Chromeの問題で、session自体は消えていなかったが、ログアウト自体は可能
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】新郎新婦へ最高の結婚祝いギフトを選べるサービス『Best Gifter』をリリースしました!(Rails×Vue)

はじめに 20代半ばに差し掛かると、結婚する友人が出始めませんか? 巷ではこれを「第1次結婚ウェーブ」と呼ぶようで、 最近ではご祝儀に加えて、ギフトを贈る人が多いようです。 ただ、ギフト選びって本当に悩みますよね。 そんな「第1次結婚ウェーブ」渦中の方の悩みを解決してくれるサービス 『Best Gifter』を開発しました 実装中のエラー討伐の道のりは下記のブログにまとめてみました。 同じようなエラーにぶち当たっている方のエラー解決の一助となれば幸いです。 コードロード|エラー討伐ブログ サービス概要 アプリのURL:https://www.best-gifter.work PC・タブレット・スマートフォンに対応しています。 Best Gifter 対象:第1次結婚ウェーブ(20代半ば)渦中の方 場面:新郎新婦へ、結婚祝いのギフトを選ぶ時 悩み: どんなジャンルが適しているのか いくらくらいが常識の範囲内なのか? いくつか厳選したけど本当にこれでいいのか? を1つずつ解消してくれるサービスです。 このサービスを作った背景 仲良くしている友人が24歳で結婚し、結婚祝いを贈ろうと思ったときに、「なにを?」「いくらで?」「これで良いのかな...?」といった疑問があった実体験からです。 自分にとっては結婚する友人はたくさんいるかもしれませんが、結婚する友人からしたら人生で一度きりの出来事かと思います。最高の思い出にスパイスを施してあげたい!という想いから開発しました。 望む未来 第1次結婚ウェーブ渦中の20代半ばのみなさんが、 新郎新婦へ最高のギフトを贈り、みんなハッピーになれることを祈っています? 使い方 機能は大きく分けると3つあります。 キーワード検索 ギフトを贈りたい相手との間柄別検索(目玉機能!?) アンケート投稿/投票(目玉機能!?) 1. キーワード検索 こちらがトップページになります。 キーワードを入力すると検索画面に飛びます。 例えば、「タオル」と入力してみます。 (希望の価格帯が決まっていれば、価格も検索条件に入れることができます) すると、結婚祝いに適した「タオル」の関連商品が表示されます。 自分で設けた「タオル」の検索条件以外にも、裏では「結婚」というキーワードと、ギフト包装「あり」の検索条件をデフォルトで設けています。 vueのstoreから一部抜粋 function searchItem(keyword, genreId, minPrice, maxPrice, sort, changePage) { return axios.get("/v1/rakuten_apis/search", { params: { keyword: "結婚" + " " + keyword, genreId: genreId, minPrice: minPrice || 1000, maxPrice: maxPrice || 150000, sort: sort || "standard", giftFlag: 1, imageFlag: 1, page: changePage || 1, }, }); } 「並び替え」で検索結果の順番を変更することができます。 商品をホバーすることで、「楽天市場へ」ボタンと「お気に入り登録」ボタンが出現します。 「お気に入り登録」ボタンをクリックすると、マイページに保存されます。 「楽天市場へ」ボタンをクリックすると、楽天市場の商品ページにジャンプします。 2. ギフトを贈りたい相手との間柄別検索(目玉機能!?) ギフトを贈りたい相手との間柄ごとに検索ができます。 間柄は「友人」「親族」「同僚」「上司」の4パターンを設けています。 「友人」にギフトを贈ることを例にとってみます。 こちらも同じくトップページから検索ができます。 「友人」であれば、「10,000〜30,000円」で下記のジャンルから選ぶのがおすすめ!ということが、ひと目で分かるようにしたことがこだわりポイントです。 例えば、「インテリア・雑貨」をクリックしてみると、検索ページに飛びます。 検索条件は自動的に「インテリア」「10,000円〜」「〜30,000円」となっています。 もちろん再度、検索条件を設けることも可能です。 3. アンケート投稿/投票(目玉機能!?) 自分でギフトをいくつか選んでみたは良いものの、最後一つに絞れない!というときに使う機能です。流れは下記の通りです。 自分で考えたギフトを3つまで選択肢として設けて、アンケート投稿する(匿名で投稿) 別の誰かが自分のアンケートに対して、投票してくれる(ワンクリックで匿名で投票) 投稿/投票ともに、匿名となっていることがポイントです。 TwitterやYouTubeで、見ず知らずの人のアンケートでも、ワンクリックで匿名だから、と、手軽に投票してあげている人は多いのではないかと思い、その心理をここで利用してみました。 まずは「投稿画面」です。 下記のように、ギフトを贈りたい相手の情報を入力し、現状考えているギフトを3つまで選択肢に設けることができます。 そしてこちらが「投票画面」です。 先程自分で投稿したアンケートが掲載されています。 同じくギフト選びに迷っている人が、みんなのアンケートを参考にしたい時があるかと思います。 アンケート結果を見るためには、投票することが条件となっており、閲覧されるごとに投票数が蓄積されていき、重みのあるアンケート結果になっていきます。 さらに、アカウント作るのは面倒だけど、手っ取り早くアンケートだけ見て参考にしたい・・・という方向けに、トップページからアンケート投票画面へ飛ぶボタンも設けました。 下記のように、ログインしていない状態からでもアンケート投票を行うことができます。 マイページ お気に入りに登録した商品や、投稿したアンケート結果を確認できます。 過去に投稿したアンケートの削除(削除ボタン) お気に入り登録の解除(★マークをクリック) 使用技術 バックエンド Ruby 2.7.2 Rails 6.0.3.6 RSpec 3.10.1 楽天商品検索API(外部API) 機能における主要なGem counter_culture 2.7.0(投票数カウント) sourcery 0.16.0(ログイン) JWT 2.2.2(トークン発行) rakuten_web_service 1.13.0(楽天商品検索API) httpclient 2.8.3(ネットワーク通信) capistrano 3.16.0(自動デプロイ) unicorn 6.0.0(アプケーションサーバ) フロントエンド Vue 2.6.12 vuex 3.6.0 Vue Router 3.4.9 axios 0.21.1 vee-validate 3.4.5 vue-poll 0.1.8(アンケートの投稿/投票) CSSフレームワーク Buefy 0.9.4 Bulma 0.9.1 ER図 インフラ構成図 開発期間 デプロイまでの所要時間は400〜450時間(3.5ヶ月)ほどかかりました。 現職(IT業界とは全く別業界です)を続けながらの開発であったため、 大まかに、平日は朝1.5時間・夜1.5時間、休日に8時間といったスケジュールでした。 苦労したところ たくさんありますが、特に瀕死状態になったエラーと、そこから学んだことを振り返っていきます(時系列順)。 1. 楽天APIの叩き方(実装期間:1ヶ月) そもそもどうやって外部のAPI叩くのか謎 楽天商品検索APIはRailsのキットが存在するらしく、ググって実装してみた。キットを使った実装方法は、ググればたくさん記事は見つかった。(※注意:キットからだと限られたJSONの階層データしか取得できない!) 実装はできたけど、RailsからどうやってVueに渡すのか謎 最初からVueで叩けばいいじゃんと気づく Vueで楽天商品検索APIを叩く場合の記事は全く見つからなかったので、ググりまくった結果、VueでHTTP通信を行うにはaxiosを使うのが通例らしいと知る。なんとかVueで叩くように変更できた。 あれ?Vueで叩こうとするとCSRF問題にぶち当たるじゃん... CSRFについてはこちらを参考にした。 CSRF問題は楽天APIサーバー側の問題だからどうしようもできないのでRails側で叩くしかないと知る。 最終的に、「Vueでformの内容(検索条件)をRailsに送って、受けとった検索条件をもとにRails側から楽天APIを叩いて、その結果をVueにJSONで返す」というように実装した。 こんな感じで行ったり来たりして1ヶ月かけてやっと実装できました。 結果どのようなコードになったかは、下記のブログにまとめてみました。 https://nakano-vil.com/code-rode/portfolio_vue_vuex_api_rakuten/ 学んだこと APIを扱うことにおいて、Vueではaxiosを、Railsではhttpclientを扱えるようにすることが大事 2. 楽天商品検索APIの商品検索のページネーション(実装期間:2週間) よくあるページネーションはkaminari等を使って、Item.all等で一度全てのデータを取り出して、そのうちから1ページに表示させたい件数を調整し、ページネーションするものだった。 が、しかし、楽天APIにおいては一度の通信で引っ張ってこれる件数が30件と決まっていたため、今まで学んだ理論ではページネーションが実装できなかった。つまり、普通にAPIを叩いても、一度のリクエストで「1〜30件まで」の検索結果しか取得できない。 例えば2ページ目を表示したければ、再度31〜60件目に該当するデータをAPIを叩いて表示させる必要があった。 ページネーションの「2」をクリックしたときに「2」をRailsのparamsに渡して、それを元に再度APIを叩きに行く(楽天商品検索APIでは、「2」を送ると「31〜60件まで」のデータを返してくれる)。 そしてページネーションの表示では、現在のページが「2」になるように認識させて、「前に戻るボタン」と「次に戻るボタン」ではちゃんと「1」ページ、「3」ページに移動させる必要があった。 結果、ググリ倒して同じような実装をしている記事を発見し、そちらを参考に実装した。こちらの記事。 学んだこと paramsや引数の扱い方。ググリ倒して似たような記事を見つけて、理解しながらカスタマイズする。 3. TwitterAPIで投票機能が使えない問題 元々は、機能の目玉としていたユーザーのアンケート機能をTwitterAPIで実装しようとした。 Webサービス側からTwitterAPIを通して、普通のコメントやSNS共有をTwitterに流す方法はいくらでもググれば見つかった。が、Webサービス上でアンケートを作成し、APIを通してTwitterのアンケート投稿を行う方法が全く分からなかった。 やっと見つけたのは、gem 'twitter'とは全く別のgem 'twitter-ads'というgemが存在するということ。 が、しかし、このgem 'twitter-ads'は企業アカウントであったり、承認されたアカウントでないと使用できないgemであるらしく、事実上、実装不可なことが分かった。 ちなみにtwitter-adsで実装したかったのはこの部分。 学んだこと 下調べは入念に。 4. Vueの'vue-poll'というライブラリでアンケート機能を実装(実装:3週間) TwitterAPIが使えなくなれば、このままだと、ただ商品を検索するだけの機能であり、どこにでもあるアプリ。どうしてもアンケート機能を取り入れたくググりまくった結果、vue-pollというライブラリを発見。 が、しかし、これを扱っている記事が全くなく、READMEに記載されているサンプルコードを真似して実装してみても、Vue上で「一時的な投票」ができるのみ。Railsと連携させて、「アンケートの投稿」「投票数をDBに保存」という大事な機能の実装がどうしてもできなかった。 そこで、READMEだけでなく、そのライブラリがどのように動いているのか?まで自力で調べるために、ソースコードまで見るようにし、なんとかヒントを見つけ実装することができた。 学んだこと ライブラリは便利!入れるだけで動く! が、ブラックボックスとなっているため、ライブラリのソースコードまで確認し、どうやって動いているのか?まで理解すれば手探りで実装できるようになる。 5. 非同期通信でアンケートの選択肢が順番にならない問題(実装期間:1週間) Vueは画面遷移なくスムーズな表示の切り替えができて便利〜くらいの気持ちでいたが、それゆえの非同期通信問題にぶち当たった。「選択肢1」「選択肢2」「選択肢3」を投稿しても、投稿する度に順番が変わってしまう。(「選択肢3」「選択肢1」「選択肢2」の順番になったり) ただ、非同期通信に関してはググれば記事が溢れていたのでなんとかなった。 promiseやasync/awaitを駆使してなんとか順番通りにさせることができた。 でもよく考えると、カリキュラムのVue編でちゃんとasync/await扱ってたから、いかに理解せず進めていたのかというのを痛感し、反省した。 学んだこと 高校受験、大学受験、国家試験と、統計を把握してテクニックで解答するのと、答えを丸暗記するのでなんとか試験を乗り切ったので、長年の経験から、それが自分に合った勉強法だと思いこんでいた。 が、プログラミングに関しては違った。みんなが言うように手を動かして半べそかきながら自走するのが一番身に付く。人によるかもしれないが、自分の場合はそう感じた。 6. AWSにデプロイ(実装期間:2週間) No space left on device 容量が100%になり全くコマンドが打てなくなる問題にぶち当たった。 そもそもAWSに関しては、「記事を見ながらコピペした」レベルだったので、エラーの際にどこを見ればいいか?のあたりをつけることすらできなかった。 結果、ググリ倒して少しずつ点と点を繋げていく作業をしていたところ、RUNTEQのみなさんがアドバイスを下さり助けていただきました。ありがとうございました! 学んだこと ターミナル上での操作に慣れないといけない。全く分からないからといってコピペは禁物。 対処法は下記のブログで記録しました。 https://nakano-vil.com/code-rode/no_space_left_on_device/ 7. Capistranoが全然実装できない問題(実装期間:2週間) こちらも正直、記事を見ながら見様見真似でコピペしていたのが仇となった。とりあえず自動デプロイ便利そうだから実装しちゃおっとという軽い気持ちで取り組んではダメ。絶対。 unicornとはなんなのか、nginxとはなんなのかがほんわりしていた状態で取り掛かったので手間取った。 とはいえ、ここまでで自作アプリを開発して3ヶ月経っている。開発当初よりはlogの見方や当たりをつけることが出来るようになり、手探りでなんとか実装できた。 でも結局最後はRUNTEQ校長の菊本さんに助けていただきました。ありがとうございました! 学んだこと logはダイイングメッセージ。$ tail -f XXXX.logがめっちゃ便利。 工夫したところ ロゴ 濃いめ 中間 明るめ インパクトがあり、使い回しやすいロゴを高校の後輩に作ってもらいました。 ロゴはサービスの顔なので、「シンプルで、華やかなものを!」と依頼し、サービスのイメージにぴったりのロゴを作ってくれました。大満足です! デザイン Buefy/Bulma Bootstrapっぽさはあまり好みではないので、ほかにオシャレに仕上がりそうなフレームワークを探し、結果、BuefyとBulmaで装飾しました。 (※最初、全てBuefyを使っていましたが、Buefyの仕様上、クリックイベント等の挙動が本来のものと変わってしまったので、form部分はBulmaをメインに使用しています。) UI/UXの向上 「ぱっと見で何ができるか分かること」に拘りました。 シンプル且つ機能的にするために、ボタンの配置、ホバー効果、検索フォーム等において、自分がよく利用するサイトを参考にしました。自分がよく利用するということは、自分にとって使い勝手がいいサイトだと思うので、大いに活用させていただきました。 hoverアニメーションや、サイトの色味は日頃からTwitterで心惹かれたツイートを保存しておき、参考にさせていただきました。 Webデザイナーの小林さん@pulpxstyleの図解ツイートが本当におしゃれでマネしたくなるものばかりです!参考にさせていただき、ありがとうございました! 今後実装したいこと 実際に使っていただいたユーザーさんからの声を取り入れてブラッシュアップしていきたい所存です! ご要望がありましたら、本記事、もしくはTwitterのDMにてご連絡いただけますと幸いです?‍♂️ 4/29時点で、現在、私自身が実装したいと考えていることは下記の通りです。 N+1問題 アンケートの投票数をDBから取得するときは下記のコードとなっています。 includesを使えばいいと思い、一度修正を行ってみましたが上手くいきませんでした。 ですのでデータ数が少ない現状では実装をスキップしています。 ただ、データ数が多くなったときにはDBに負荷がかかるはずなので、このN+1問題を解消する予定です。 def index @answer_counts = Answer.group(:questionnaire_choice_id).count render json: @answer_counts end webmockによるAPIのテスト RSpecでテストをする際、楽天商品検索APIをテストする箇所で、テストが通るときもあれば、エラーになることもあります。 外部APIを叩いているため、毎回レスポンスが違うことや、連続でAPIを叩くため制限がかかってしまうためです。 せっかくCircleCIを用いているので、mockを施す予定です。 おわりに 本当に仲の良い友人だからこそ、「適当なギフトは贈りたくない!日常的に使えて、いつまでも幸せな家庭であってほしい!」と悩みまくった結果、思いついたサービスでした。 人を幸せにしたい!という私の想い・個性が反映されたサービスになったのではないかと思っています。 ちなみに当時の私は、丸の内を探し歩き「女性も振れるIH対応の最強の鉄中華鍋」を贈りました? 本サービスを開発中、使ってみたいAPIや、あったら便利だな〜というサービスを思いついたので、早速そちらにも取り掛かりたいです。 以上、ご覧いただきありがとうございました! 宣伝 私が所属しているプログラミングスクール「RUNTEQ」にて、超有益な記事がアップされました! これを発見できた方は本当にラッキー過ぎます・・・ これでエモーショナルに語れる愛のこもったサービスを開発できるはずです! https://blog.runteq.jp/programming-career/portfolio/4287/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】いいね機能を実装してみた

環境 Rails6.1.3 Ruby3.0.1 同期処理 ①モデルを作成 今回はコメントに対するいいねなので、user_idとcomment_idと関連付けます % rails g model Good user_id:integer comment_id:integer ②モデルに関係性を記述 user,commentモデルにそれぞれ以下の記述を追加 dependent: :destroyでユーザーかコメントが消えたときにいいねも消えるようになります has_many :goods, dependent: :destroy app/model/good.rb belongs_to :user belongs_to :comment ③ルーティングの設定 今回は、コメントに対するいいねということなので、ルーティングがネスト化されます また、いいねは作るか消すかだけなので、onlyも書いておきます config/routes.rb resources :comments do resources :goods, only: %i[create destroy] end ④controllerの編集 createとdestroyについてメソッドを書きます いいねをするのは、current_userなので、current_user.idからuser_idを取得します app/controller/goods_controller.rb class GoodsController < ApplicationController def create @good = Good.create(user_id: current_user.id, comment_id: params[:comment_id]) @good.save end def destroy @good = Good.find_by(user_id: current_user.id, comment_id: params[:comment_id]) @good.destroy end end ⑤viewの設定 <% if logged_in? %>でログインしているか判定 <% if current_user.good_user?(comment.id) %>で現在のユーザーがいいねをしているかどうかを判定し、createかdestroyか分岐 <div class="comment_goods"> <% if logged_in? %> <% if current_user.good_user?(comment.id) %> <%= link_to comment_good_path(comment.id, current_user.goods.find_by(comment_id: comment.id).id), method: :delete do %> <p class="good-button"><i class="far fa-heart like-btn" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= comment.goods.count %></span></p> <% end %> <% else %> <%= link_to comment_goods_path(comment.id), method: :post do %> <p class="good-button"><i class="fas fa-heart unlike-btn" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= comment.goods.count %></span></p> <% end %> <% end %> <% end %> </div> pathの名前は % rails routes で確認 comment_goods POST /comments/:comment_id/goods(.:format) goods#create comment_good DELETE /comments/:comment_id/goods/:id(.:format) ここから持ってきます 非同期処理 実装出来次第追記します!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

authenticate_user!メソッド

authenticate_user!メソッドはログイン状態によって表示するページを切り替えるメソッドでdeviseをインストールをすることで使用できます。 通常はbefore_actiontiと一緒に利用しアクションを実行する前にログインしていなければログイン画面に遷移させられます。 ただindexページ、詳細ページ、検索機能などはログインしていなくても見れる状態にしたいというのがあるので before_action :authenticate_user!, except: [:index, :show, :search] のようにexcept: [:index, :show, :search]をつけてあげればindex,show,searchアクションは除かれるのでログインしていなくても遷移させられることはありません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Payjpの決済処理(備忘録)

私の備忘録です purchase.controller.rb private def pay_item #決済処理の記述 Payjp.api_key = ENV["PAYJP_SECRET_KEY"] Payjp::Charge.create( amount: @item.price , card: address_params[:token], currency: 'jpy') end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Graphql-Rubyの認可についてまとめる

はじめに 最近、GraphQL-Rubyを使って認可実装する機会があったのでまとめてみます。 用語的には認証と認可がありますが、今回は認可を扱います。(GraphQLは一般的には認証を扱ってません) REST APIと違ってコントローラごとに認可を実装できないので、GraphQLの場合はひと工夫必要になります。 resolverやmutationに認可をつける まずは、resolverやmutationに対しての認可に利用できるメソッドについてまとめます。 https://graphql-ruby.org/mutations/mutation_authorization.html ready? ready?メソッドは、以下のようにresolverやmutationで使用できます。ready?メソッドはmutation実行前に自動的に実行されます。戻り値がfalseの場合はnullが返されますが、例のようにGraphQL Errorを発生させた方がベターでしょう。 mutation.rb class Mutations::PromoteEmployee < Mutations::BaseMutation def ready?(**args) # argsはmutationの引数 if !context[:current_user].admin? raise GraphQL::ExecutionError, "Only admins can run this mutation" else true end end def resolver(**args) # mutation実行 end end authorized? GraphGL-Rubyのmutationにはauthorized?メソッドも用意されています。実はこのメソッドはready?メソッドとほとんど同じ動きをします。 上のready?をauthorized?に書き換えても同様に動作します。唯一の違いはargumentsをロードするかどうかです。 argumentsをロードするとは、以下のようにargumentを定義した場合にargumentのemployee_idからloadsで定義されているEmployeeオブジェクトを自動的にDBから取得してくれることを指します。 mutation.rb argument :employee_id, ID, required: true, loads: Types::Employee 上記の場合だと、引数で指定したidのemployeeオブジェクトが取得されます。故にauthorized?メソッドを利用すると、以下のようにも書くことができます。 mutation.rb argument :employee_id, ID, required: true, loads: Types::Employee def authorized?(employee:) context[:current_user].manager_of?(employee) end オブジェクトタイプに認可をつける GraphQL-Rubyでは、スキーマとしてオブジェクトタイプを定義します。関連を持つオブジェクト同士は、クライアント側で自由に取得できてしまいます。すると、本来は権限が必要なリソースにもアクセスできてしまいます。 それを防ぐためにオブジェクトタイプごとに認可を設定する方法をまとめます。 visible?を使う https://graphql-ruby.org/authorization/visibility.html visible?メソッドはその名の通り、スキーマ自体がユーザから見えなくなります。例えば、実験的な機能を特定のユーザのみに開放するみたいなユースケースが考えられます。 以下の例では、visible?メソッドの戻り値がfalseなら、graphqlエラーが返ることになります secret_type.rb class secretType < BaseObject def self.visible?(context) context[:current_user].admin? end field :hoge, Hoge, null: true end authorized?を使う https://graphql-ruby.org/authorization/authorization.html 1番、認可っぽいのはこのauthorized?メソッドでしょうか。名前からしてそうですね。 以下の例では、current_userがobject.userと等しい場合は、Objectが返され、等しくない場合は、nullが返ります。 secret_type.rb class secretType < BaseObject def self.authorized?(object, context) context[:current_user] === object.user end field :hoge, Hoge, null: true end また、authorized?メソッドがfalseの場合にエラーを返したい場合は、schemaのトップレベルにエラーを定義することができます。 my_schema.rb class MySchema < GraphQL::Schema def self.unauthorized_object(error) raise GraphQL::ExecutionError, "An object of type #{error.type.graphql_name} was hidden due to permissions" end end scopeを使う https://graphql-ruby.org/authorization/scoping.html authorized?メソッドは、そのオブジェクトを返すか、返さないかの二択でしたが、ユーザによって返すオブジェクトを制限したい場合もあると思います。その場合はscopeメソッドが便利です。 以下の場合は、adminユーザならば、オブジェクトを全て返し、それ以外はpublishedなproductを返します。 product_type.rb class prodcutType < BaseObject def self.scope_items(items, context) context[:current_user].admin? items : items.published end end このscopeを適用するかどうかは、以下のようにfieldの引数で定義できます。 field :products, [Types::Product] scope: true 戻り値が配列の場合は、デフォルトでtrueになっており、値の場合はfalseになっています。 まとめ 以上、GraphQL-Rubyの認可についてまとめました。Graphqlの認可は結構ややこしいので、しっかり整理していきたいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GraphQL-Rubyの認可についてまとめる

はじめに 最近、GraphQL-Rubyを使って認可を実装する機会があったのでまとめてみます。 用語的には認証と認可がありますが、今回は認可を扱います。(GraphQLは一般的には認証を扱ってません) resolverやmutationに認可をつける まずは、resolverやmutationに対しての認可に利用できるメソッドについてまとめます。 https://graphql-ruby.org/mutations/mutation_authorization.html ready? ready?メソッドは、以下のようにresolverやmutationで使用できます。ready?はmutation実行前に自動的に実行されます。戻り値がfalseの場合はnullが返されますが、例のようにGraphQL Errorを発生させた方がベターでしょう。 mutation.rb class Mutations::PromoteEmployee < Mutations::BaseMutation def ready?(**args) # argsはmutationの引数 if !context[:current_user].admin? raise GraphQL::ExecutionError, "Only admins can run this mutation" else true end end def resolver(**args) # mutation実行 end end authorized? mutationにはauthorized?メソッドも用意されています。実はこのメソッドはready?メソッドとほとんど同じ動きをします。上のready?をauthorized?に書き換えても同様に動作します。唯一の違いはargumentsをロードするかどうかです。 argumentsをロードするとは、以下のようにargumentを定義した場合にargumentのemployee_idからloadsで定義されているEmployeeオブジェクトを自動的にDBから取得してくれることを指します(多分)。 mutation.rb argument :employee_id, ID, required: true, loads: Types::Employee 上記の場合だと、引数で指定したidのemployeeオブジェクトが取得されます。故にauthorized?メソッドを利用すると、以下のようにも書くことができます。 mutation.rb argument :employee_id, ID, required: true, loads: Types::Employee def authorized?(employee:) context[:current_user].manager_of?(employee) end オブジェクトタイプに認可をつける GraphQL-Rubyでは、関連を持つオブジェクト同士は、クライアント側で自由に取得できます。すると、本来は権限が必要なリソースにもアクセスできてしまうことがあります。それを防ぐためにオブジェクトタイプごとに認可を設定する方法をまとめます。 visible?を使う https://graphql-ruby.org/authorization/visibility.html visible?メソッドはその名の通り、スキーマ自体がユーザから見えなくなります。例えば、実験的な機能を特定のユーザのみに開放するみたいなユースケースが考えられます。 以下の例では、visible?メソッドの戻り値がfalseなら、graphqlエラーが返ることになります secret_type.rb class secretType < BaseObject def self.visible?(context) context[:current_user].admin? end field :hoge, Hoge, null: true end authorized?を使う https://graphql-ruby.org/authorization/authorization.html 1番、認可っぽいのはこのauthorized?メソッドでしょうか。名前からしてそうですね。 以下の例では、current_userがobject.userと等しい場合は、Objectが返され、等しくない場合は、nullが返ります。 secret_type.rb class secretType < BaseObject def self.authorized?(object, context) context[:current_user] === object.user end field :hoge, Hoge, null: true end また、authorized?メソッドがfalseの場合にエラーを返したい場合は、schemaのトップレベルにエラーを定義することができます。 my_schema.rb class MySchema < GraphQL::Schema def self.unauthorized_object(error) raise GraphQL::ExecutionError, "An object of type #{error.type.graphql_name} was hidden due to permissions" end end scopeを使う https://graphql-ruby.org/authorization/scoping.html authorized?メソッドは、そのオブジェクトを返すか、返さないかの二択でしたが、ユーザによって返すオブジェクトを制限したい場合もあると思います。その場合はscopeメソッドが便利です。 以下の場合は、adminユーザならば、オブジェクトを全て返し、それ以外はpublishedなproductを返します。 product_type.rb class prodcutType < BaseObject def self.scope_items(items, context) context[:current_user].admin? items : items.published end end このscopeを適用するかどうかは、以下のようにfieldの引数で定義できます。 field :products, [Types::Product] scope: true 戻り値が配列の場合は、デフォルトでtrueになっており、値の場合はfalseになっています。 まとめ 以上、GraphQL-Rubyの認可についてまとめました。Graphqlの認可は結構ややこしいので、しっかり整理していきたいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Hotwire(Turbo)を試す その2: Trubo Streamsでページの一部の差替・追加

環境: Rails 6.1、Turbo 7.0.0-beta.5 Hotwire(Turbo)を試す その1: 導入、作成・更新フォーム の続き Turbo Streamsとは Turbo Streamsとは、サーバーからHTMLの一部を送信して、ページ中の一部を差替、追加、削除するものです。送信の方法には、普通のHTTPのレスポンスのほかにAction Cableが使えます。この「その2」のサンプルは、普通のHTTPのレスポンスを使うだけです。 ページ中に次のような<turbo-frame>要素があるとします。id属性で名前を付けます。 現在のページ中のHTMLの一部 <turbo-frame id="message"> <div>こんにちは</div> </turbo-frame> これが含まれたページに対して、次のような<turbo-stream>要素のHTML片を送信します。target属性で差替対象となる<turbo-frame>の名前を指定します。差替なのでaction属性は"replace"です。<turbo-stream>の内容は、<template>で囲む必要があります。 送信するHTML片 <turbo-stream action="replace" target="message"> <template> <div>こんばんは</div> </template> </turbo-stream> HTML片を送信すると、<turbo-frame>の内容が入れ替わります。 送信後のページ中のHTML <turbo-frame id="message"> <div>こんばんは</div> </turbo-frame> 「いいね」ボタンの例 その1で作ったサンプルに「いいね」ボタンを追加してTurbo Streamsを試します。記事の詳細ページの中に❤️ボタンを置き、クリックするとカウンタが1上がる、ということにします。 テーブルにカウンタを保存するカラムを追加します。 db/migrate/20210419022554_add_likes_count_to_entries.rb class AddLikesCountToEntries < ActiveRecord::Migration[6.1] def change add_column :entries, :likes_count, :integer end end いいね用のアクションを追加します。 config/routes.rb resources :entries do patch :like, on: :member end 記事ページに埋め込む❤️ボタンとカウンタの数字です。turbo_frame_tag "名前" メソッドは <turbo-frame id="名前">〜</turbo-frame> になります。 app/views/entries/_likes.html.erb <%= turbo_frame_tag "entry-likes" do %> <div> <%= link_to '❤️', like_entry_path(@entry), method: :patch %> <span style="color:red"><%= @entry.likes_count %></span> </div> <% end %> これを記事ページに埋め込みます。 app/views/entries/show.html.erb <%= render 'likes' %> クリックで呼び出されるコントローラのいいね用アクションです。 app/controllers/entries_controller.rb def like @entry.increment!(:likes_count) render turbo_stream: turbo_stream.replace('entry-likes', partial: 'likes') end render turbo_stream: turbo_stream.replace('名前', partial: 'テンプレート')は、次のようなHTML片を送信し、<turbo-frame id="名前">の内容を差し替えます。このとき、HTTPレスポンスのContent-typeは、text/vnd.turbo-stream.html になります。 <turbo-stream action="replace" target="名前"> <template> partialのテンプレート </template> </turbo-stream> ❤️をクリックすると数字が増えるようになりました。 Turbo Streamsのテンプレートと送信には、もう1つやり方があります。次のように<turbo-stream>をテンプレートの中に書く方法です。turbo_stream.replace "名前" は <turbo-stream action="replace" target="名前"><template>〜</template></turbo-stream> になります。 app/views/entries/like.html.erb <%= turbo_stream.replace 'entry-likes' do %> <%= render 'likes' %> <% end %> このテンプレートをContent-typeを指定して送信すると、同じ結果になります。 app/controllers/entries_controller.rb def like @entry.increment!(:likes_count) render layout: false, content_type: "text/vnd.turbo-stream.html" end 「もっと見る」ボタンの例 差替の次は、コンテンツの追加を試すために、記事一覧に「もっと見る」ボタンを実装します。まず、一覧のテンプレートを修正します。scaffoldのテンプレートはテーブルを使っているため、Turbo StreamsでHTML要素をうまく扱えません。divに変えます。 追加のターゲットになる記事の一覧は、<turbo-frame>で囲み、名前を"entries"としています。 app/views/entries/index.html <div> <%= turbo_frame_tag "entries" do %> <% @entries.each do |entry| %> <%= render 'entry', entry: entry %> <% end %> <% end %> </div> <br> <%= render 'more_button' %> app/views/entries/_entry.html <div class="entry"> <%= link_to entry.title, entry %> | <%= entry.created_at.strftime('%m/%d %H:%M') %> | <%= link_to 'Edit', edit_entry_path(entry) %> | <%= link_to 'Destroy', entry, method: :delete, data: { confirm: 'Are you sure?' } %> </div> 「もっと見る」ボタンのテンプレートです。10個ずつ記事を表示し、すべて表示されたらボタンを非表示とします。「もっと見る」も<turbo-frame>で囲み、名前を"more-button"としています。 app/views/entries/_more_button.html <% if @offset + 10 < @entries_count %> <%= turbo_frame_tag "more-button" do %> <div> <%= link_to 'もっと見る', more_entries_path(offset: @offset + 10) %> </div> <% end %> <% end %> 「もっと見る」用のルーティングです。 config/routes.rb resources :entries do get :more, on: :collection patch :like, on: :member end コントローラの記事一覧と「もっと見る」用のアクションです。記事は10個ずつ、offsetパラメータの位置から取得、作成日時の降順、とします。 moreアクションでは、render turbo_stream: 〜 を使わない形にします。 app/controllers/entries_controller.rb def index @entries_count = Entry.count @offset = params[:offset].to_i @entries = Entry.offset(@offset).order(created_at: :desc).limit(10) end def more index render layout: false, content_type: "text/vnd.turbo-stream.html" end 「もっと見る」用のTurbo Streamsのテンプレートです。ここでは<turbo-stream>要素を2つ送信していいます。記事一覧の追加分と「もっと見る」の差替分です。追加するときは、replace の代わりに append を使います。 app/views/entries/more.html <%= turbo_stream.append "entries" do %> <% @entries.each do |entry| %> <%= render 'entry', entry: entry %> <% end %> <% end %> <%= turbo_stream.replace "more-button" do %> <%= render 'more_button' %> <% end %> 記事の数を増やして試すためにシードデータを用意します。 db/seeds.rb 32.times do |idx| entry = Entry.create!( title: %w(foo bar baz).sample(3).join(' '), body: 'blah, blah, blah...', created_at: idx.hours.ago ) end 「もっと見る」をクリックすると記事が10個ずつ追加され、何度もクリックすると「もっと見る」が消えるようになります。 turbo-frame の中のリンクの挙動 さて、この記事一覧の中のリンクをクリックすると、ページが切り替わらずに、記事一覧、つまり<turbo-frame id="entries">〜</turbo-frame>で囲んだ部分が消えてしまいます。 <turbo-frame id="名前">の中にあるリンクは、レスポンスのHTMLにも<turbo-frame id="名前">があることを期待し、元の<turbo-frame>を新しい<turbo-frame>で置き換える、というのがデフォルトの動作になっています。 この動作を変えて別のページに切り替わるようにするには、<turbo-frame>に属性target="_top"を加えます。 app/views/entries/index.html <%= turbo_frame_tag "entries", target: "_top" do %> ここまでの感想 Turboでこのサンプルを実装するのは、けっこう面倒でした。実際のアプリケーションに導入するには学習コストが気になります。まだドキュメントも解説サイトも揃っていない段階ではしょうがありませんが。 実際に導入するときは、Turbo+Stimulusの簡単な部分だけ使い、Vue.jsやjQueryと組み合わせるのがいいかもしれない。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]ウィザード形式のフォームの実装

はじめに 先日あるサイトにて新規会員登録の項目が大量あると登録する気が失せるなと感じました。そのことをきっかけに、ユーザへの興味を削がないように登録フォームを進めさせる方法はないかなあと考えていたらウィザード形式なるものに出会いましたのでアウトプットします。 ウィザード形式とは ウィザード形式とは、対話の如く順番に操作画面が進んでいく方式のことです。身の回りでいえば、新幹線の切符購入画面とかそうですよね(出発駅選択→到着駅選択→時間選択→座席指定みたいな)。ユーザからすると、1ページで項目を一気に登録しないといけないフォームよりも、ウィザード形式の方が、今何をしているのかシンプルにわかりやすくてみやすいと言う特徴があります。 実装(ユーザ登録編) ユーザ登録の後、住所情報を登録するウィザード形式を作ることとします。 registrations_controller.rb def new @user = User.new end def create @user = User.new(sign_up_params) unless @user.valid? 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 #1対1の場合は、buildメソッド_関連づけられたモデル名 render :new_address end このコントローラで行うことは次の3つです。 ユーザ情報入力画面から受け取った情報の ①バリデーションチェックを行い、 ②sessionに登録情報を持たせて、 ③次の住所情報登録で使用するインスタンスを生成しておく。 三行目でreturnしている理由は、バリデーションチェックが通らない場合は、その後の処理もいったん止めたいからです。もし、この記述を怠ると、その後出てくるrenderが実行されて「、DoubleRenderErrorというエラーが発生します。 unless @user.valid? render :new and return end 次に、sessionを使用することで登録した情報を保持します。 session["devise.regist_data"] = {user: @user.attributes} session["devise.regist_data"][:user]["password"] = params[:user][:password] attributesメソッド ここでattributesメソッドについて軽く触れておきます。 attributesメソッドはインスタンスメソッドから取得できる値をオブジェクト型からハッシュ型に変換してくれます。 実際に見てみるとよくわかります。 # 例:Userインスタンスがもつオブジェクト型の値 #=><user:12345678921> # 例:Userインスタンスがもつハッシュ型の値 #=> {name=>"yamada", :sex=>"man"} これを踏まえ、今回の記述を再度見てみると、sessionにはハッシュ型で渡していることがわかります。 実装(住所登録編) 住所登録では以下のようなアクションを追加します。 registrations_controller.rb #省略 def create_address @user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(address_params) unless @address.valid? render :new_address and return end @user.build_address(@address.attributes)#住所情報付け足し @user.save session["devise.regist_data"]["user"].clear sign_in(:user, @user) end private def address_params params.require(:address).permit(:postal_code, :address) end おそらくuserモデルとさほど変わりはありませんが、、、 sessionはclearメソッドを使用して明示的に削除するようにしましょう。 そして、最後にsign_inでサインインします。 終わりに ただ開発するのではなく、どうしたらユーザ(お客様)にとって、快適で便利になるのか、実装した結果どうなるのかを意識して開発に取り組んでいきます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】カラム名の変更方法

はじめに Railsでサービス開発中、カラム名を間違えてしまったので カラムの変更方法を備忘録としてまとめておきます。 Rails:5.2.5 モデル名 変更前のカラム名 変更後のカラム名 user profile_image profile_image_id 1.migrationファイルを作成する カラム名を変更するために、migrationファイルを作成する。 $ rails g migrate rename_[変更前のカラム名]_column_to_[モデル名(複数形)] 今回の場合だと、以下のようになる。 $ rails g migrate rename_profile_image_column_to_users 2.migrationファイルを編集する 生成されたファイルにchangeメソッドを追加し、変更したいカラム名を記述する。 rename_column :テーブル名, :変更前のカラム名, :変更後のカラム名 db/migrate/20210425060907_rename_profile_image_column_to_users def change rename_column :users, :profile_image, :profile_image_id end 3.DBに反映する 下記コマンドを実行。 $ rails db:migrate 4.カラム名が変更されているか確認 rails cでカラム名を確認する。 モデル名.column_names $ rails c $ User.column_names
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DBに保存されない

課題 DBに画像が保存されない 原因 バリデーションの条件が合わず保存ができなくなっていた 解決方法 ↑の様な在庫管理アプリにおいて部品の登録を行おうとした。 正常に動作し、登録完了画面に移行したが、DBに情報が保存されていなかった。 binding.pryを使ってエラー箇所の特定を試みたが、正確に情報も送れている様だった。↓ ここからエラーの特定ができなかったため、 createアクションのcreateの後に!を追加し保存ができているか確認をした。↓ すると・・・ Numberでエラーが出ている様 正規表現の条件に『-』が含まれていないことが原因だった!!! 『-』を使うこともあるため今回は正規表現の方を修正して問題解決♪
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsコードリーデイング① validatesメソッドを深掘りしてみた 【備忘録】

はじめに プログラミング学習を開始して1年。自己流でアプリ開発を続けることによる成長幅に限界を感じ、次のステップとしてコードリーディングをすることにしました。 こちらの記事では、コードリーディングで得た知識の備忘録を記していきます。 なお今回はこちらの記事を参考にし、コードリーディングの環境を整えることができました。 またvalidatesの解説も載っていましたのでそちらもかなり参考にしていますが、今回は私のような初学者に価値があるような内容を共有させて戴きます。 https://qiita.com/jabba/items/8a9ac664eb2a0e61e621 今回はmodelのvalidatesメソッドをリーディング ターミナルで以下を実行。 rails c [1] pry(main)> u = User.new(name: 'sample name') nameの長さの最大が30というvalidatesの場合の処理を見ていきます。 user.rb class User < ApplicationRecord #binding.pryで処理を止める binding.pry validates :name, length: { maximum: 30 } end validatesメソッドがどのように宣言されているのか。 UserクラスはApplicationRecordを継承していますが、さらに元をたどるとActiveRecord::Baseを継承しています。 以下Railsのreadmeで以下のようにmodel層に関して解説がありました。 Model layer models can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as provided by the Active Model module. モデルは、通常のRubyクラス、またはActiveModelモジュールによって提供される一連のインターフェースを実装するRubyクラスにすることもできます。 https://github.com/rails/rails いつも利用している便利なメソッドは、ActiveModelモジュールによって提供されたものということがわかりました。 本題のvalidates.rbを読んでみた。 確かにActiveModelモジュールの中に宣言されていました。 rails/activemodel/lib/active_model/validations/validates.rb module ActiveModel module Validations module ClassMethods def validates(*attributes) (省略) end end end end 処理①引数の調整 rails/activemodel/lib/active_model/validations/validates.rb def validates(*attributes) defaults = attributes.extract_options!.dup validations = defaults.slice!(*_validates_default_keys) extract_options!の継承元は以下の通り。 rails/activesupport/lib/active_support/core_ext/array/extract_options.rb # frozen_string_literal: true class Hash # By default, only instances of Hash itself are extractable. # Subclasses of Hash may implement this method and return # true to declare themselves as extractable. If a Hash # is extractable, Array#extract_options! pops it from # the Array when it is the last element of the Array. def extractable_options? instance_of?(Hash) end end class Array # Extracts options from a set of arguments. Removes and returns the last # element in the array if it's a hash, otherwise returns a blank hash. # # def options(*args) # args.extract_options! # end # # options(1, 2) # => {} # options(1, 2, a: :b) # => {:a=>:b} def extract_options! if last.is_a?(Hash) && last.extractable_options? pop else {} end end end defaultに代入する値では、 渡された引数の最後がハッシュクラスの直接のインスタンスで書かれている場合は、pop それ以外は、{} を代入しています。 学び① is_a?(mod) is_a?(mod)メソッドを使用すれば、オブジェクトが指定されたクラス mod かそのサブクラスのインスタンスであるとき真を返します。 したがって、ここではattributes(validatesの引数)がハッシュクラスかどうか判定しています。 学び② instance_of?(klass) オブジェクトがクラス klass の直接のインスタンスである時真を返します。 https://docs.ruby-lang.org/ja/latest/method/Object/i/instance_of=3f.html 学び③ popとpush 基本情報処理の試験勉強で学んでいたものでした。 popは配列の末尾から指定個数取り出し、取り出した要素を返す。 pushは末尾に新たな要素を加える。 https://docs.ruby-lang.org/ja/latest/method/Array/i/pop.html https://docs.ruby-lang.org/ja/1.8.7/method/Array/i/push.html 学び④ dup オブジェクトを複製することができます。 そのもの自体を変更したくない時に便利そうです。 https://docs.ruby-lang.org/ja/latest/method/Object/i/clone.html 疑問点 instance_of?(Hash)のみで条件式として十分なのではないのか? どのような意図があって、AND条件なのだろうか。 終わりに 本日はここまでにします。 たった1行読み込むだけで相当な勉強になりました。(コードリーディング恐るべし) 疑問点が残ってしまいましたが、わかり次第追記して修正します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(RequestSpec編)

はじめに 本記事はAPIをRailsのAPIモードで開発し、フロント側をVue.js 3で開発して、認証基盤にdevise_token_authを用いてトークンベースの認証機能付きのSPAを作るチュートリアルのファイル投稿編の記事になります。(今回はVue.jsには触れませんのであしからず) 前回: Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(ファイル投稿編) Rspecの導入 今回はmini-testではなくRspecを用いてテストを行います。理由は筆者がRspecの方が好きだからです(mini-testはあまり触ったことがない、、、) Gemfileの編集 Gemfileに以下を追加して、bundle installを実行してください。 # Gemfile group :development, :test do gem "rspec-rails" gem "factory_bot_rails" gem 'faker' # ダミーデータを作成するため gem 'gimei' # 日本語のダミーデータを作成するため end # Terminal $ bundle Generatorの実行 その後、Rspecを導入する以下のコマンドを実行してください。 $ rails g rspec:install create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb .rspecファイルの中身を以下のように修正してください。 --require spec_helper --format documentation --color --format documentationはRspecの実行結果を見やすく加工するため、--colorは実行結果を色つきにして見やすくするために追記しています。 application.rbの編集 rails g modelやrails g scaffoldを実行した時に余計なファイルが作成されないように、generator実行時の挙動を修正します。 config/application.rbを以下のように修正してください。 # config/application.rb module App class Application < Rails::Application # 省略 # ここから追加 config.generators do |g| g.test_framework :rspec, view_specs: false, helper_specs: false, controller_specs: false, routing_specs: false end # ここまで end end rails_helper.rbの修正 FactoryBotの省略記法を使うために、以下の行を追加してください。 RSpec.configure do |config| # 省略 # 以下を追加 config.include FactoryBot::Syntax::Methods # 省略 end これで、Factorybot.create(:user)ではなく、create(:user)のように書くことができます。 ログインのヘルパーモジュールを作成 RequestSpecの中でログイン状態を表現したり、getやpostメソッド等のHTTPリクエストメソッドをオーバーライドするヘルパーモジュールを定義します。 以下の実装方法を参考にしました。 以下のコマンドを実行してください。 $ mkdir spec/supports/ $ touch spec/supports/auth_helper.rb 作成したファイルを以下のように修正します。 module AuthHelper HTTP_HELPERS_TO_OVERRIDE = [:get, :post, :patch, :put, :delete] HTTP_HELPERS_TO_OVERRIDE.each do |helper| define_method(helper) do |path, **args| add_auth_headers(args) args == {} ? super(path) : super(path, **args) end end def login(user) @user = user @auth_token = @user.create_new_auth_token end def logout @user = nil @auth_token = nil end private def add_auth_headers(args) return unless defined? @auth_token args[:headers] ||= {} args[:headers].merge!(@auth_token) end end 修正できたらrails_helper.rbでこのファイルをincludeします。 RSpec.configure do |config| # 省略 # ここから追加 Dir[Rails.root.join('spec/supports/**/*.rb')].each { |f| require f } config.include AuthHelper, type: :request # ここまで # 省略 end これでRequestSpecの中で、オーバーライドされたgetメソッドやpostメソッド、login、logoutメソッドを実行することができます。 RequestSpecの作成 投稿APIのRequestSpecを作成します。 以下のコマンドを実行してください。 $ mkdir spec/requests $ mkdir spec/factories $ touch spec/requests/posts_spec.rb $ touch spec/factories/posts.rb $ touch spec/factories/users.rb まずはFactoryファイルから編集しましょう。 # spec/factories/users.rb FactoryBot.define do factory :user do provider { 'email' } uid { Faker::Internet.safe_email } password { Faker::Internet.password } email { uid } name { Gimei.name } end end # spec/factories/posts.rb FactoryBot.define do factory :post do title { Faker::Lorem.word } body { Faker::Lorem.sentence } association :user end end これでspecの中でcreate(:user)等が実行できます。 次にposts_spec.rbを書いていきます。 require 'rails_helper' RSpec.describe "Posts API", type: :request do describe "ログイン済" do describe "GET /posts" do it "投稿の一覧を取得できること" do user = create(:user) post = create(:post, user: user) login user get "/posts" expect(response).to have_http_status(:success) post_json = JSON.parse(response.body).first expect(post_json["id"]).to eq post.id expect(post_json["title"]).to eq post.title expect(post_json["body"]).to eq post.body expect(post_json["created_at"]).to eq post.created_at.iso8601(3) expect(post_json["user_name"]).to eq post.user.name end end describe "GET /posts/:id" do it "投稿の詳細を取得できること" do user = create(:user) post = create(:post, user: user) login user get "/posts/#{post.id}" expect(response).to have_http_status(:success) post_json = JSON.parse(response.body) expect(post_json["id"]).to eq post.id expect(post_json["title"]).to eq post.title expect(post_json["body"]).to eq post.body expect(post_json["created_at"]).to eq post.created_at.iso8601(3) expect(post_json["user_name"]).to eq post.user.name end end describe "POST /annnouncements" do it "投稿を作成できること" do user = create(:user) login user params = attributes_for :post post "/posts", params: params expect(response).to have_http_status(:success) post_json = JSON.parse(response.body) post = Post.find(post_json["id"]) expect(post_json["id"]).to eq post.id expect(post_json["title"]).to eq post.title expect(post_json["body"]).to eq post.body expect(post_json["created_at"]).to eq post.created_at.iso8601(3) expect(post_json["user_name"]).to eq post.user.name end end describe "PATCH /annnouncements/:id" do it "投稿を更新できること" do user = create(:user) post = create(:post, user: user) login user params = attributes_for :post patch "/posts/#{post.id}", params: params expect(response).to have_http_status(:success) post_json = JSON.parse(response.body) _post = Post.find(post_json["id"]) expect(post_json["id"]).to eq _post.id expect(post_json["title"]).to eq _post.title expect(post_json["body"]).to eq _post.body expect(post_json["created_at"]).to eq _post.created_at.iso8601(3) expect(post_json["user_name"]).to eq _post.user.name end end describe "DELETE /posts/:id" do it "投稿を削除できること" do user = create(:user) post = create(:post, user: user) login user delete "/posts/#{post.id}" expect(response.status).to eq 204 end end end describe "未ログイン" do it "投稿一覧のアクセスできないこと" do user = create(:user) post = create(:post, user: user) get "/posts" expect(response.status).to eq 401 end end end authenticate_user!をbefore_actionでコールしているため、ログイン済と未ログイン済のテストケースを用意しています。 ログイン済の場合は、各種APIを叩いたあとに返却される返り値が正しいかどうか、 未ログインの場合は401エラーが返却されるかどうかをテストしています。 まとめ 最低限APIのテストを行うなら上記の実装で十分かと思います。 次回はユーザー登録機能を実装する予定です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails でJavaScriptを使う

商品出品機能をつくる際に、販売手数料や利益を計算させるためにJavaScriptを使ったので まとめておこうと思います。 手順 -手順1 必要なJSファイルを作成する -手順2 必要な要素を取得する -手順3 入力するたびにイベントを発火できるようにする -手順4 必要なvalue属性を取得できるようにする -手順5 HTMLをJavaScriptで作成し、ブラウザ上に描画できるようにする ファイルを作成して読み込もう まずは、app/javascriptディレクトリにファイル名.jsを作成しましょう。 そして、application.jsにrequire("作成したJSファイルのパス");を記述して、作成したJSファイルを読み込めるようにしましょう。このとき、作成したJSファイルが存在するディレクトリに注意して記述してください。 【例】application.js require("../item_price"); 作成したJSファイルがブラウザで読み込まれるか確認するため、JSファイルにconsole.logを記述します。 【例】item_price.js window.addEventListener('load', () => { console.log("OK"); }); loadというイベントは、ページが全て読み込まれた後に発火します。JSファイルの読み込みが正しく出来ていればコンソール上に"OK"という文字列が表示されるはずです。 記述ができたら、ブラウザをリロードし、コンソールを開いて確認しましょう。 ブラウザのコンソールは、command + option + Jで開くことができます。 もし"OK"という文字列がコンソール上で確認できない場合は、以下の項目を確認しましょう。 -application.jsファイルで、作成したJSファイルを読み込むための記述が誤っていないか(ファイル名やディレクトリ構造に注意する) -application.html.erbファイルで、application.jsを読み込むためのscriptタグの挿入を忘れていないか 手順2 必要な要素を取得する 次に、取得すべき要素を確認します。 手順2で実装したいことは、「金額を入力したら、その金額の販売手数料と販売利益を表示すること」です。 金額を入力したときに画面上に動きがあった要素を示すセレクタは以下の3つです。 -金額を入力する場所のidセレクタ -販売手数料を表示する場所のidセレクタ -販売利益を表示する場所のidセレクタ idセレクタを確認するときは、検証ツールで探すと分かりやすい。 idセレクタを取得する場合はdocument.getElementById("id名")を使用します。 document.getElementById("id名")は、id名を指定してHTMLの要素を取得するメソッドです。 【例】item_price.js // 金額を入力した数値をpriceInputという変数に格納する const priceInput = document.getElementById("金額を入力する場所のid"); console.logを記述して、定義した変数にinput要素が格納されているか確認をします。 【例】item_price.js const priceInput = document.getElementById("金額を入力する場所のid"); console.log(priceInput); もしコンソール上に何も表示されないもしくはエラーが出る場合は、以下の項目を確認しましょう。 -取得すべきidセレクタの場所に間違いがないか 手順3 入力するたびにイベントを発火できるようにする 金額を入力するたびに、イベント発火するようにaddEventListenerを使用します。 addEventListenerは、イベント発火の際に実行する関数を定義するためのメソッドです。 今回は、入力があるたびにイベント発火を起こしたいため、以下のようにinputというイベントを使用します。そして console.logを記述して、入力するたびにイベント発火されるか確認をします。 【例】item_price.js const priceInput = document.getElementById("金額を入力する場所のid"); priceInput.addEventListener("input", () => { console.log("イベント発火"); }) 商品出品ページで金額を入力し、コンソール上に"イベント発火"と表示されるか確認をします。 もし"イベント発火"という文字列がコンソール上に表示されない場合は、以下の項目を確認しましょう。 -そもそもJSファイルが読み込めているか -turbolinksの記述が残っていないか -JSファイルのコードの記述に間違いがないか 手順4 必要なvalue属性を取得できるようにする 入力した金額の値を取得したい場合は、以下のようにvalue属性を指定する必要があります。 console.logを記述して、入力した金額の取得ができるか確認します。 【例】item_price.js const priceInput = document.getElementById("金額を入力する場所のid"); priceInput.addEventListener("input", () => { const inputValue = priceInput.value; console.log(inputValue); }) 続いて、商品出品ページで金額を入力し、コンソール上に金額が表示されるか確認をします。 もし金額の値がコンソール上に表示されない、もしくはエラーが出る場合は、以下の項目を確認しましょう。 -取得すべきidセレクタの場所に間違いがないか 手順5 HTMLをJavaScriptで作成し、ブラウザ上に描画できるようにする innerHTMLを使用して、販売手数料や利益計算結果を表示できる ようにしましょう。 innerHTMLは、HTML要素の書き換えを行うことができます。 【例】item_price.js const addTaxDom = document.getElementById("販売手数料を表示する場所のid"); addTaxDom.innerHTML = "入力した金額をもとに販売手数料を計算する処理" innerHTMLを実装することで、計算した販売手数料の金額に書き換え、表示することができます。 販売手数料や利益計算の処理は、Math.floorメソッドを用いて実装してみましょう。 もし、表示されているHTMLが崩れたり表示できない場合は、以下の項目を確認しましょう。 -console.logを使用して取得すべきidの場所に間違いがないか 今回はこんな感じ const priceInput = document.getElementById("item-price"); priceInput.addEventListener("input", () => { const inputValue = priceInput.value; const addTaxDom = document.getElementById("add-tax-price"); addTaxDom.innerHTML = (Math.floor(inputValue * 0.1)); const profitNumber = document.getElementById("profit") const value_result = inputValue * 0.1 profitNumber.innerHTML = (Math.floor(inputValue - value_result)); }) 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails 画像投稿をテストする時

フリマアプリを作っています。画像をつけて投稿するのでその時の、単体テストの方法をまとめておこうと思います。 初心者なので、勉強用で間違えている箇所もあると思います。 もし、違っておりましたらコメントなどで教えて下さい https://qiita.com/on97-nakamatsu-mayumi/items/810a2cd2fdefeb7c3485 単体テストの方は↑こちらの方です 画像投稿のテストのしかた テスト用のダミー画像を用意しましょう テストコードは自動で実行されるものです。自動で画像をアップロードさせることはできないため、画像投稿の挙動をチェックするための画像ファイルを、あらかじめ用意しておく必要があります。 画像をダウンロードや作成したら、publicディレクトリの中に「images」というディレクトリを作成します。 そして、先ほどダウンロードした画像を、imagesディレクトリの中に配置しましょう。 続いてコードを記述しますが、画像は生成したインスタンスに紐付ける必要があります。そのため、afterというメソッドを用いて、インスタンス生成後に画像が保存されるようにしましょう。 afterメソッド afterメソッドは任意の処理の後に指定の処理を実行することができます。例えば、after(:build) とすることで、インスタンスがbuildされた後に指定の処理を実行できます FactoryBotを編集しましょう afterメソッドを用いて、生成するダミーデータに画像を添付します。 例えば FactoryBot.define do factory :message do content {Faker::Lorem.sentence} association :user association :room after(:build) do |message| message.image.attach(io: File.open('public/images/test_image.png'), filename: 'test_image.png') end end end 上の場合は8行目の記述では、io: File.openで設定したパスのファイル(public/images/test_image.png)を、test_image.pngというファイル名で保存をしています。 ちゃんとインスタンスが生成されているか確認するときはコンソール使用できることを確認しましょう。 rails c 例えば FactoryBot.create(:message) ()の中にはテストしたいモデル名 もしKeyErrorというエラーが発生した場合は、下記コマンドを実行した後、もう一度コンソールでFactoryBotの確認を行ってください。 # コンソールを終了する pry(main)> exit # Springを停止 % spring stop Railsに標準で導入されている「Spring」というGemがバックグラウンドで作動していて、稀にロードエラーを起こす為、一度作動を停止します。Springは、railsコマンドと同じタイミングで再起動されます。 画像の保存を確認する場合は、以下のように変数に入れてます。画像が保存できているか確認するにはimage.attached?で確認をしましょう。 実行結果として、trueが返ってきていれば保存されています。 【例】ターミナル(コンソール) pry(main)> message = FactoryBot.create(:message) # 省略 pry(main)> message.image.attached? # 省略 => true 以上です
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby、Railsでの日付・時間の操作系メソッドまとめ

はじめに アプリケーション開発において、日付や時間の情報というのは必ずといっていいほど絡んできます。 毎回日付・時間操作のメソッドを忘れてしまい、ググる時間が勿体ないので、まとめました。 今回だけでは網羅できていない部分とあると思うため、順次更新予定です。 XX日前・後 Dateクラス -1、+1をするだけです。 require 'date' today = Date.today yesterday = today - 1 tomorrow = today + 1 p today #<Date: 2021-04-28 ((2459333j,0s,0n),+0s,2299161j)> p yesterday #<Date: 2021-04-27 ((2459332j,0s,0n),+0s,2299161j)> p tomorrow #<Date: 2021-04-29 ((2459334j,0s,0n),+0s,2299161j)> DateTimeクラス Dateクラスと同様に-1、+1で対応出来ます。 require 'date' today = DateTime.now yesterday = today - 1 tomorrow = today + 1 p today #<DateTime: 2021-04-28T18:46:07+09:00 ((2459333j,35167s,22091000n),+32400s,2299161j)> p yesterday #<DateTime: 2021-04-27T18:46:07+09:00 ((2459332j,35167s,22091000n),+32400s,2299161j)> p tomorrow #<DateTime: 2021-04-29T19:00:30+09:00 ((2459334j,36030s,963561000n),+32400s,2299161j)> Time Timeクラスの場合-1、+1を指定すると単純に1秒の加算・減算になります。 Timeクラスで日付操作をする場面は少ないと思いますが、もし±1日を求めたい場合 86400秒で1日となリます。 p now = Time.now # 2021-04-28 18:48:54.976575 +0900 p now -1 # 2021-04-28 18:48:53.976575 +0900 p now +1 # 2021-04-28 18:48:55.976575 +0900 p now = Time.now # 2021-04-28 18:50:22.141586 +0900 p now -1 * 60 * 60 * 24 # 2021-04-27 18:50:22.141586 +0900 TimeWithZone 主にRailsで使用されるクラス。あまり意識せずに使っている人も多いかもしれないが、Railsでアプリケーションを開発する場合、TimeWithZoneを使っているケースが多いと思います。 activesupportを使えるようにgemをインストールします。 gem install activesupport require 'active_support/all' p Time.current # 2021-04-28 19:27:49.093581 +0900 # 昨日 p TIme.current.yesterday # 2021-04-27 19:27:49.093649 +0900 # 翌日 p Time.current.tomorrow # 2021-04-29 19:27:49.093752 +0900 # 3日前 p Time.current.ago(3.days) # 2021-04-25 19:27:49.093787 +0900 # 3日後 p Time.current.since(3.days) # 2021-05-01 19:27:49.093837 +0900 # 1日前 current = Time.current p current - 1.day # 2021-04-27 19:30:54.945456 +0900 TimeWithZoneは日付関連のメソッドが豊富なため、色々な求め方が出来ます。 Xヶ月 Dateクラス require 'date' today = Date.today p today #<Date: 2021-04-28 ((2459333j,0s,0n),+0s,2299161j)> # 1ヶ月前 p today << 1 #<Date: 2021-03-28 ((2459302j,0s,0n),+0s,2299161j)> # 2ヶ月前 p today.prev_month(2) #<Date: 2021-02-28 ((2459274j,0s,0n),+0s,2299161j)> # 1ヶ月後 p today >> 1 #<Date: 2021-05-28 ((2459363j,0s,0n),+0s,2299161j)> # 3ヶ月後 p today.next_month(3) #<Date: 2021-07-28 ((2459424j,0s,0n),+0s,2299161j)> prev_month、next_monthは引数を指定しない場合それぞれ、レシーバの1ヶ月前、1ヶ月後を返します。 DateTimeクラス DateTimeは日付のときと同様に、Dateクラスと同じメソッドで対応出来ます。 require 'date' today = DateTime.now p today #<DateTime: 2021-04-28T19:39:15+09:00 ((2459333j,38355s,430222000n),+32400s,2299161j)> # 1ヶ月前 p today << 1 #<DateTime: 2021-03-28T19:39:15+09:00 ((2459302j,38355s,430222000n),+32400s,2299161j)> # 2ヶ月前 p today.prev_month(2) #<DateTime: 2021-02-28T19:39:15+09:00 ((2459274j,38355s,430222000n),+32400s,2299161j)> # 1ヶ月後 p today >> 1 #<DateTime: 2021-05-28T19:43:19+09:00 ((2459363j,38599s,727644000n),+32400s,2299161j)> # 3ヶ月後 p today.next_month(3) #<DateTime: 2021-07-28T19:43:19+09:00 ((2459424j,38599s,727644000n),+32400s,2299161j)> TimeWithZone require 'active_support/all' # 1ヶ月前 p Time.current.prev_month # 2021-03-28 19:52:16.958094 +0900 # 1ヶ月後 p Time.current.next_month # 2021-05-28 19:50:22.450258 +0900 # 3ヶ月前 p Time.current.ago(3.month) # 2021-01-28 19:50:22.450291 +0900 # 3ヶ月後 p Time.current.since(3.month) # 2021-07-28 19:50:22.450393 +0900 current = Time.current # 1ヶ月前 p current -1.month # 2021-03-28 19:50:22.450436 +0900 # 1ヶ月後 p current +1.month # 2021-05-28 19:50:22.450436 +0900 X時間・X分・X秒 DateTime require 'date' today = DateTime.new(2021,4,29,0,0,0) # 1時間後 p today + Rational(1,24) #<DateTime: 2021-04-29T01:00:00+00:00 ((2459334j,3600s,0n),+0s,2299161j)> # 1分後 p today + Rational(1,24*60) #<DateTime: 2021-04-29T00:01:00+00:00 ((2459334j,60s,0n),+0s,2299161j)> # 1秒後 p today + Rational(1,24*60*60) #<DateTime: 2021-04-29T00:00:01+00:00 ((2459334j,1s,0n),+0s,2299161j)> 年月日や曜日など日時に関する情報を取得する require 'date' today = Date.today p today #<Date: 2021-04-29 ((2459334j,0s,0n),+0s,2299161j)> # 年 p today.year #2021 # 月 p today.month # 4 # 日 p today.day # 29 # 曜日 week = ['日曜日','月曜日','火曜日','水曜日','木曜日','金曜日','土曜日' ] p week[today.wday] # "木曜日" 曜日を取得するwdayメソッドに関しては、曜日の文字列が返り値になるのではなく、対応する数値が返って来ます。 (0-6、日曜日は0) DateTimeクラスでも同様のメソッドで年月日と曜日を取得出来ます。 差分 Dateクラス Date型の場合純粋に引き算をするだけで、日にちの差分が取得出来ます。 1点気をつける部分としては、Date型同士で引き算した際の戻り値がRational型(分数などを扱えるクラス)で返って来るので、分子のみを取得するnumeratorメソッドを使用することで純粋な日付の差分のみを取得出来ます。 require 'date' d1 = Date.new(2021, 4, 30) d2 = Date.new(2021, 5, 5) p (d2 - d1).numerator # 5 フォーマット・パース 日付 ⇛ 文字列 strftime おそらく年月日操作で一番使用頻度が高いメソッド require 'date' day = Date.new(2021, 5, 1) p day.strftime('%Y年%m月%d日') # "2021年05月01日" # 0サプレス p day.strftime('%Y年%-m月%-d日') # "2021年5月1日" p day.strftime('%Y/%m/%d') # "2021/05/01" # 0サプレス p day.strftime('%Y/%-m/%-d') # "2021/5/1" 文字列 ⇛ 日付 strptime require 'date' p Date.strptime("2021年4月30日", '%Y年%m月%d日') parse require 'date' p Date.parse('20210430') #<Date: 2021-04-30 ((2459335j,0s,0n),+0s,2299161j)> p Date.parse('2021/4/30') #<Date: 2021-04-30 ((2459335j,0s,0n),+0s,2299161j)> p Date.parse('2021-4-30') #<Date: 2021-04-30 ((2459335j,0s,0n),+0s,2299161j)>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

attribute, read_attribute, _read_attribute の違い

環境 / Environment Rails 6.1.3.1 Ruby 3.0.1 OS: macOS attribute とは attribute は Rails での ActiveRecord モデルオブジェクト のメソッドで、 属性名を引数として渡すとその値を返してくれます。 ただしこれは モデルの内部でのみ使えるプライベートメソッドとなっています。 そして実態は _read_attribute へのエイリアスで、 _read_attribute はパブリックメソッドになっています。 Rails Console での実行例 >> u = User.new => #<User:0x00007fb142bdd288 ... >> u.name = 'a' => "a" >> u.name => "a" >> u._read_attribute('name') => "a" >> u._read_attribute(:name) => nil 文字列を渡すと値が返りますが、シンボルでは値が nil となります。 u.name のように、 カラム名をメソッドとして実行した場合にも この _read_attribute が実行されます。 read_attribute とは read_attribute は、 _read_attribute と同様に属性値を返すメソッドですが、 _read_attribute よりも複雑な処理を行います。 引数を文字列に変換する処理を行っているため、シンボルを引数とした場合でも正しく属性値を返します。 Rails Console での実行例 >> u = User.new => #<User:0x00007fb142bdd288 ... >> u.name = 'a' => "a" >> u.read_attribute('name') => "a" >> u.read_attribute(:name) => "a" 文字列を渡した場合でも、シンボルを渡した場合でも、同じ結果となっています。 定義 紹介した3つのメソッドは、 ActiveRecord で定義されています。 read.rb module ActiveRecord module AttributeMethods module Read def read_attribute(attr_name, &block) name = attr_name.to_s name = self.class.attribute_aliases[name] || name name = @primary_key if name == "id" && @primary_key @attributes.fetch_value(name, &block) end # This method exists to avoid the expensive primary_key check internally, without # breaking compatibility with the read_attribute API def _read_attribute(attr_name, &block) # :nodoc @attributes.fetch_value(attr_name, &block) end alias :attribute :_read_attribute private :attribute 実はその昔... 昔の Rails (たとえば version 3.0.1) では このような違いはありませんでした。 attribute も read_attribute も ActiveRecord 6.1.3.1 と同じように read.rb で定義されていましたが、 シンボルを渡した場合でも文字列を渡した場合でも、 同じ結果を返していました。 そのとき _read_attribute は存在しませんでした。 read.rb def read_attribute(attr_name) attr_name = attr_name.to_s attr_name = self.class.primary_key if attr_name == 'id' if !(value = @attributes[attr_name]).nil? if column = column_for_attribute(attr_name) if unserializable_attribute?(attr_name, column) unserialize_attribute(attr_name) else column.type_cast(value) end else value end else nil end end def attribute(attribute_name) read_attribute(attribute_name) end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】devise_auth_tokenを用いたOmniauthのSNS認証でパスワードのバリデーションを突破する方法

Omniauthを用いたSNS認証を実装している際にパスワードがバリデーションに引っかかる事象が発生。 具体的には パスワードが英大文字、英小文字、数字の混合であること パスワードが6文字以上20文字以下であること これらのバリデーションに引っかかった。 その時の解決法をメモ 結論…set_random_passwordメソッドをオーバーライドし、修正 より、devise_auth_tokenの一次ソースを参照していた際にset_random_passwordメソッドを発見。 こいつがパスワードを自動生成していたようだ。 元のメソッドでは def set_random_password # set crazy password for new oauth users. this is only used to prevent # access via email sign-in. p = SecureRandom.urlsafe_base64(nil, false) @resource.password = p @resource.password_confirmation = p end このようになっているが、このpって値が上記バリデーションに引っかかる仕様となっていた。 これをオーバーライドし以下のように修正。英小文字、英大文字、数字を必ず含む形式にしている。 def set_random_password # パスワードのバリデーション突破のためにオーバーライド random_password = SecureRandom.alphanumeric(10) + [*'A'..'Z'].sample(1).join + [*'a'..'z'].sample(1).join + [*'0'..'9'].sample(1).join @resource.password = random_password @resource.password_confirmation = random_password end これにて解決
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【EBSボリューム不足】Railsチュートリアルでつまづいたところ

Cloud9でRailsチュートリアルを進めていたところエディタの挙動がおかしくなった なぜかファイルが保存できない テストの結果もおかしい ページも開かない IDEを開いたり閉じたりしていたらこのようなメッセージが表示されていた どうやらディスクの容量が足りないらしい ターミナル $ df -h Filesystem Size Used Avail Use% Mounted on udev 3.9G 0 3.9G 0% /dev tmpfs 796M 824K 796M 1% /run /dev/xvda1 9.7G 9.7G 0 100% / tmpfs 3.9G 0 3.9G 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup /dev/loop1 56M 56M 0 100% /snap/core18/1988 /dev/loop0 13M 13M 0 100% /snap/amazon-ssm-agent/495 /dev/loop2 100M 100M 0 100% /snap/core/10958 /dev/loop3 34M 34M 0 100% /snap/amazon-ssm-agent/3552 /dev/loop4 56M 56M 0 100% /snap/core18/1997 /dev/loop5 100M 100M 0 100% /snap/core/10908 tmpfs 796M 4.0K 796M 1% /run/user/1000 EC2のコンソールでEBSのボリュームを変更する [EC2] > [インスタンス] > [インスタンスID] > [ストレージ] > [ボリュームID] > [アクション] > [ボリュームの変更] ターミナル # 変更前 $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT xvda 202:0 0 10G 0 disk └─xvda1 202:1 0 10G 0 part / # 変更後 $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT xvda 202:0 0 50G 0 disk └─xvda1 202:1 0 10G 0 part / パーティションの拡張 パーティションの空き容量が全くない場合 パーティションの拡張を試みるも… ターミナル $ sudo growpart /dev/xvda 1 mkdir: cannot create directory ‘/tmp/growpart.15786’: No space left on device FAILED: failed to make temp dir 操作に必要な一時ファイルを/tmp に作成することもできないようです。 sample_appをバックアップ ディスクの空きを作るため、一度sample_appを削除します。 バックアップを取りたいのでトグルツリーを右クリックして[Download] ファイルのダウンロードが完了したら、トグルツリーを右クリックして[Delete] sample_appが削除されます。 df -hコマンドでディスクの容量を確認。 少しだけ容量に空きができました。 一体何に9.5Gも使用しているのでしょうか…。 ターミナル $ df -h Filesystem Size Used Avail Use% Mounted on . . # ↓少しだけ容量が減った /dev/xvda1 9.7G 9.5G 218M 98% / . . パーティションを拡張させます。(Ubuntu) ターミナル $ sudo growpart /dev/xvda 1 CHANGED: partition=1 start=2048 old: size=20969439 end=20971487 new: size=104855519,end=104857567 $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT xvda 202:0 0 50G 0 disk └─xvda1 202:1 0 50G 0 part / ファイルシステムの拡張 resize2fsコマンドでファイルシステムを拡張させます ターミナル $ sudo resize2fs /dev/xvda1 resize2fs 1.44.1 (24-Mar-2018) Filesystem at /dev/xvda1 is mounted on /; on-line resizing required old_desc_blocks = 2, new_desc_blocks = 7 The filesystem on /dev/xvda1 is now 13106939 (4k) blocks long. ファイルシステムが拡張されました $ df -h Filesystem Size Used Avail Use% Mounted on . . /dev/xvda1 49G 9.5G 39G 20% / . . Sample_appを復元する ダウンロードして逃しておいたsample_app.tar.gzをドラッグアンドドロップしてアップロードする sample_app.tar.gzを解凍する ターミナル tar -zxvf sample_app.tar.gz .tar.gz ディスクの空き容量が増え、削除したsample_appも元に戻りました。 これで学習に戻ることができます。 参考 railsTutorial AWS Cloud9 空き容量不足 ターミナルが動作しない時の解決方法 EC2 EBS ボリュームサイズ拡張のやりかた EC2にてubuntuのEBSの容量が不足した時にやったこと Linux tar.gz tar 圧縮 解凍
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]deviseを日本語化する

はじめに 自作のRailsアプリケーションでdeviseを使ったので備忘録として残しておきます。 今回はdeviseを日本語化していきたいと思います。 deviseを日本語化する デフォルトではエラーメッセージなどが英語なので日本語にします。 まずconfig/application.rbを以下のように編集します config/application.rb config.i18n.default_locale = :ja config/localesフォルダ内のdevise.ja.ymlというファイルを作成します。 作成したファイルに以下を記入してください。 config/locales/devise.ja.yml ja: errors: messages: not_found: "は見つかりませんでした" # not_found: "not found" already_confirmed: "は既に登録済みです" # already_confirmed: "was already confirmed" not_locked: "は凍結されていません" # not_locked: "was not locked" devise: failure: unauthenticated: 'ログインしてください。' # unauthenticated: 'You need to sign in or sign up before continuing.' unconfirmed: '本登録を行ってください。' # unconfirmed: 'You have to confirm your account before continuing.' locked: 'あなたのアカウントは凍結されています。' # locked: 'Your account is locked.' invalid: 'メールアドレスかパスワードが違います。' # invalid: 'Invalid email or password.' invalid_token: '認証キーが不正です。' # invalid_token: 'Invalid authentication token.' timeout: 'セッションがタイムアウトしました。もう一度ログインしてください。' # timeout: 'Your session expired, please sign in again to continue.' inactive: 'アカウントがアクティベートされていません。' # inactive: 'Your account was not activated yet.' sessions: signed_in: 'ログインしました。' # signed_in: 'Signed in successfully.' signed_out: 'ログアウトしました。' # signed_out: 'Signed out successfully.' passwords: send_instructions: 'パスワードのリセット方法を数分以内にメールでご連絡します。' # send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' updated: 'パスワードを変更しました。' # updated: 'Your password was changed successfully. You are now signed in.' confirmations: send_instructions: '登録方法を数分以内にメールでご連絡します。' # send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' confirmed: 'アカウントを登録しました。' # confirmed: 'Your account was successfully confirmed. You are now signed in.' registrations: signed_up: 'アカウント登録を受け付けました。確認のメールをお送りします。' # signed_up: 'You have signed up successfully. If enabled, a confirmation was sent to your e-mail.' updated: 'アカウントを更新しました。' # updated: 'You updated your account successfully.' destroyed: 'アカウントを削除しました。またのご利用をお待ちしております。' # destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' unlocks: send_instructions: 'アカウントの凍結解除方法を数分以内にメールでご連絡します。' # send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' unlocked: 'アカウントを凍結解除しました。' # unlocked: 'Your account was successfully unlocked. You are now signed in.' mailer: confirmation_instructions: subject: 'アカウントの登録方法' # subject: 'Confirmation instructions' reset_password_instructions: subject: 'パスワードの再設定' # subject: 'Reset password instructions' unlock_instructions: subject: 'アカウントの凍結解除' # subject: 'Unlock Instructions' もう一つconfig/localesフォルダ内にja.ymlを作っていない方作成し以下を作成してください。 config/locales/ja.yml ja: activerecord: attributes: user: confirmation_sent_at: パスワード確認送信時刻 confirmation_token: パスワード確認用トークン confirmed_at: パスワード確認時刻 created_at: 作成日 current_password: 現在のパスワード current_sign_in_at: 現在のログイン時刻 current_sign_in_ip: 現在のログインIPアドレス email: Eメール encrypted_password: 暗号化パスワード failed_attempts: 失敗したログイン試行回数 last_sign_in_at: 最終ログイン時刻 last_sign_in_ip: 最終ログインIPアドレス locked_at: ロック時刻 password: パスワード password_confirmation: パスワード(確認用) remember_created_at: ログイン記憶時刻 remember_me: ログインを記憶する reset_password_sent_at: パスワードリセット送信時刻 reset_password_token: パスワードリセット用トークン sign_in_count: ログイン回数 unconfirmed_email: 未確認Eメール unlock_token: ロック解除用トークン updated_at: 更新日 models: user: ユーザー devise: confirmations: confirmed: メールアドレスが確認できました。 new: resend_confirmation_instructions: アカウント確認メール再送 send_instructions: アカウントの有効化について数分以内にメールでご連絡します。 send_paranoid_instructions: メールアドレスが登録済みの場合、本人確認用のメールが数分以内に送信されます。 failure: already_authenticated: すでにログインしています。 inactive: アカウントが有効化されていません。メールに記載された手順にしたがって、アカウントを有効化してください。 invalid: "%{authentication_keys}またはパスワードが違います。" last_attempt: もう一回誤るとアカウントがロックされます。 locked: アカウントはロックされています。 not_found_in_database: "%{authentication_keys}またはパスワードが違います。" timeout: セッションがタイムアウトしました。もう一度ログインしてください。 unauthenticated: ログインもしくはアカウント登録してください。 unconfirmed: メールアドレスの本人確認が必要です。 mailer: confirmation_instructions: action: メールアドレスの確認 greeting: "%{recipient}様" instruction: 以下のリンクをクリックし、メールアドレスの確認手続を完了させてください。 subject: メールアドレス確認メール email_changed: greeting: こんにちは、%{recipient}様。 message: メールアドレスの(%{email})変更が完了したため、メールを送信しています。 message_unconfirmed: メールアドレスが(%{email})変更されたため、メールを送信しています。 subject: メール変更完了 password_change: greeting: "%{recipient}様" message: パスワードが再設定されました。 subject: パスワードの変更について reset_password_instructions: action: パスワード変更 greeting: "%{recipient}様" instruction: パスワード再設定の依頼を受けたため、メールを送信しています。下のリンクからパスワードの再設定ができます。 instruction_2: パスワード再設定の依頼をしていない場合、このメールを無視してください。 instruction_3: パスワードの再設定は、上のリンクから新しいパスワードを登録するまで完了しません。 subject: パスワードの再設定について unlock_instructions: action: アカウントのロック解除 greeting: "%{recipient}様" instruction: アカウントのロックを解除するには下のリンクをクリックしてください。 message: ログイン失敗が繰り返されたため、アカウントはロックされています。 subject: アカウントのロック解除について omniauth_callbacks: failure: "%{kind} アカウントによる認証に失敗しました。理由:(%{reason})" success: "%{kind} アカウントによる認証に成功しました。" passwords: edit: change_my_password: パスワードを変更する change_your_password: パスワードを変更 confirm_new_password: 確認用新しいパスワード new_password: 新しいパスワード new: forgot_your_password: パスワードを忘れましたか? send_me_reset_password_instructions: パスワードの再設定方法を送信する no_token: このページにはアクセスできません。パスワード再設定メールのリンクからアクセスされた場合には、URL をご確認ください。 send_instructions: パスワードの再設定について数分以内にメールでご連絡いたします。 send_paranoid_instructions: メールアドレスが登録済みの場合、パスワード再設定用のメールが数分以内に送信されます。 updated: パスワードが正しく変更されました。 updated_not_active: パスワードが正しく変更されました。 registrations: destroyed: アカウントを削除しました。またのご利用をお待ちしております。 edit: are_you_sure: 本当によろしいですか? cancel_my_account: アカウント削除 currently_waiting_confirmation_for_email: "%{email} の確認待ち" leave_blank_if_you_don_t_want_to_change_it: 空欄のままなら変更しません title: "%{resource}編集" unhappy: 気に入りません update: 更新 we_need_your_current_password_to_confirm_your_changes: 変更を反映するには現在のパスワードを入力してください new: sign_up: アカウント登録 signed_up: アカウント登録が完了しました。 signed_up_but_inactive: ログインするためには、アカウントを有効化してください。 signed_up_but_locked: アカウントがロックされているためログインできません。 signed_up_but_unconfirmed: 本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。 update_needs_confirmation: アカウント情報を変更しました。変更されたメールアドレスの本人確認のため、本人確認用メールより確認処理をおこなってください。 updated: アカウント情報を変更しました。 updated_but_not_signed_in: あなたのアカウントは正常に更新されましたが、パスワードが変更されたため、再度ログインしてください。 sessions: already_signed_out: 既にログアウト済みです。 new: sign_in: ログイン signed_in: ログインしました。 signed_out: ログアウトしました。 shared: links: back: 戻る didn_t_receive_confirmation_instructions: アカウント確認のメールを受け取っていませんか? didn_t_receive_unlock_instructions: アカウントのロック解除方法のメールを受け取っていませんか? forgot_your_password: パスワードを忘れましたか? sign_in: ログイン sign_in_with_provider: "%{provider}でログイン" sign_up: アカウント登録 minimum_password_length: "(%{count}字以上)" unlocks: new: resend_unlock_instructions: アカウントのロック解除方法を再送する send_instructions: アカウントのロック解除方法を数分以内にメールでご連絡します。 send_paranoid_instructions: アカウントが見つかった場合、アカウントのロック解除方法を数分以内にメールでご連絡します。 unlocked: アカウントをロック解除しました。 errors: messages: already_confirmed: は既に登録済みです。ログインしてください。 confirmation_period_expired: の期限が切れました。%{period} までに確認する必要があります。 新しくリクエストしてください。 expired: の有効期限が切れました。新しくリクエストしてください。 not_found: は見つかりませんでした。 not_locked: はロックされていません。 not_saved: one: エラーが発生したため %{resource} は保存されませんでした。 other: "%{count} 件のエラーが発生したため %{resource} は保存されませんでした。" 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]deviseで使えるヘルパーメソッド一覧

はじめに 個人でRailsアプリケーションを作った際にdeviseを使ったので備忘録として残しておきます。 deviseのヘルパーメソッドの種類 ※下記のuserの部分はモデル名なので例えばpostがモデル名の場合はpostに置き換えてください ヘルパーメソッド 説明 current_user 現在ログインしているユーザのレコードを取得する user_signed_in? ユーザーがサインインしていればtrue、サインアウトしていればfalseが返ってくる user_session ユーザーのsession情報にアクセスする configure_permitted_parameters モデルにストロングパラメーターを追加します。デフォルトはメールアドレスとパスワードのみ適応されています。 authenticate_user! ログインしていないユーザーをログインページにリダイレクトさせます。before_action :authenticate_user! のように使います。  補足 authenticate_user! onlyやexceptionでアクションを設定することが多い、例を以下に示す。 before_action :authenticate_user! , only: [:show, :edit, :update, :destroy] or before_action :authenticate_user! , except: [:show, :edit, :update, :destroy] configure_permitted_parameters 第一引数は下記のように指定できる ・sign_up: 新規登録時 ・sign_in: ログイン時 ・account_update: 更新時 下記に使用例を示します。 before_action :configure_permitted_parameters, if: :devise_controller? def configure_permitted_parameters # サインアップ時にusernameのストロングパラメータを追加 devise_parameter_sanitizer.permit(:sign_up, keys: [:username, :image]) # アカウント編集の時にnameとprofileのストロングパラメータを追加 devise_parameter_sanitizer.permit(:account_update, keys:[:username,:image,:profile]) end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails new ~ でLoadError: cannot load such file -- sqlite3/2.7/sqlite3_nativeのエラーが出る時の解決

環境 Windows10 Ruby+Devkit 2.7.3-1(x64) rails 6 生じた問題 「rails new アプリ名」でアプリを作成した際に「LoadError: cannot load such file - sqlite3/2.7/sqlite3_native」というエラーがでてきました。 LoadError: 126: 指定されたモジュールが見つかりません。   -C:/Ruby27-x64/lib/・・・ <中略> Caused by: LoadError: cannot load such file -- sqlite3/2.7/sqlite3_native 解決策 原因はsqlite3のバージョンがRubyのバージョンに対応していないことだったらしいです。 以下の手順で解決しました! $ gem uninstall -a sqlite3 $ gem install sqlite3 --platform=ruby 参考記事 sqlite3起因でrails server失敗した場合
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails+sendgrid+sidekiqでメールを送信

何をしたか Railsでsendgridとsidekiqを使用してメール送信をしたため、書きました。 SendGridのAPIキーを取得 下記のSendGridのAPIキーの取得方法を参考にAPIキーを取得します。 https://sendgrid.kke.co.jp/docs/Tutorials/A_Transaction_Mail/manage_api_key.html envファイルに取得したAPIキーを追加 .env SENDGRID_API_KEY=xxxxxxxx SendGridを導入 sendgrid-ruby というGemをinstall Gemfile gem 'sendgrid-ruby' lib配下のファイルを読み込めるようにapplication.rbに下記コードを追加 config/application.rb config.autoload_paths += %W(#{config.root}/lib) lib配下に下記のsend_grid.rbを追加します。 lib/mail/send_grid.rb class Mail::SendGrid def initialize(settings) @settings = settings end def deliver!(mail) from = SendGrid::Email.new(email: mail.from.first) to = SendGrid::Email.new(email: mail.to.first) subject = mail.subject content = SendGrid::Content.new(type: 'text/plain', value: mail.body.raw_source) sg_mail = SendGrid::Mail.new(from, subject, to, content) sg = SendGrid::API.new(api_key: @settings[:api_key]) response = sg.client.mail._('send').post(request_body: sg_mail.to_json) puts response.status_code end end initializers配下に下記のsendgrid.rbを追加 config/initializers/sendgrid.rb ActionMailer::Base.add_delivery_method :sendgrid, Mail::SendGrid, api_key: ENV.fetch('SENDGRID_API_KEY', '') development.rbに下記コードを追加 config/environments/development.rb config.action_mailer.delivery_method = :sendgrid envファイルに送信元メールアドレス(FROM_MAIL_ADDRESS)と送信先メールアドレス(TO_MAIL_ADDRESS)を追加 .env FROM_MAIL_ADDRESS=xxx@gmail.com TO_MAIL_ADDRESS=xxx@gmail.com test_mailer.rbを追加 app/mailers/test_mailer.rb class TestMailer < ApplicationMailer def test_mail mail( from: ENV.fetch('FROM_MAIL_ADDRESS', ''), to: ENV.fetch('TO_MAIL_ADDRESS', ''), subject: 'テストメール' ) end end test_mail.text.erbを作成 メールの本文になります。 ここは任意で変えても問題ないです。 app/views/test_mailer/test_mail.text.erb メールテスト この時点でも下記コードを実行するとメールが送信されると思います。 TestMailer.test_mail.deliver_later このメール送信のジョブをsidekiqで実行できるようにしていきます。 sidekiqの導入 Gemfileに下記を追加 Gemfile gem 'sidekiq' gem 'sinatra', require: false redisを追加 今回はdockerを使用していたため、redisコンテナを作成しました。 docker-compose.yml : : redis: image: "redis:latest" ports: - "6379:6379" command: redis-server --appendonly yes volumes: - redis:/data volumes: : : redis: application.rbに下記を追加 config/application.rb config.active_job.queue_adapter = :sidekiq initializers配下にsidekiq.rbを追加 config/initializers/sidekiq.rb Sidekiq.configure_server do |config| config.redis = { url: 'redis://redis:6379' } end Sidekiq.configure_client do |config| config.redis = { url: 'redis://redis:6379' } end routes.rbに下記を追加 config/routes.rb require 'sidekiq/web' mount Sidekiq::Web, at: "/sidekiq" sidekiq.ymlを作成 config/sidekiq.yml :verbose: false :pidfile: ./tmp/pids/sidekiq.pid :logfile: ./log/sidekiq.log :concurrency: 10 :queues: - default - mailers sidekiqを起動します。 今回railsのサービス名をwebにしているため、docker-compose run webとなっています。 $ docker-compose run web bundle exec sidekiq -C config/sidekiq.yml -d 先ほどと同様にしてメールを送信します。 TestMailer.test_mail.deliver_later http://localhost:3000/sidekiq/にアクセスすると完了の箇所が1になっていると思います。 これでRails+sendgrid+sidekiqでメールの送信ができました。 参考 Rails: SendGrid(Web API)とAction Mailerでメールを送信する 【Rails6】Active Job + Sidekiqを動かしてみた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む