- 投稿日:2019-08-29T23:06:36+09:00
Railsアプリでセキュアなパスワードを扱う
Railsアプリでセキュアなパスワードを使用する方法 +α 。
gem追加
Gemfileを修正し、
bundle install
します。
Railsアプリをrails new
で作成後に特に消していなければ、Blowfish暗号を利用できるbcrypt
というgemがコメントアウトされている状態になっているはずなので、これをコメントアウトします。## 省略 ## - # gem 'bcrypt', '~> 3.1.7' + gem 'bcrypt', '~> 3.1.7' ## 省略 ##$ bundle install実装
セキュアなパスワードは has_secure_password メソッドを、生成されるmodelファイルで呼び出すことで使用できます。
このメソッドを使うためには、モデル内にpassword_digest
という属性が含まれている必要があるためモデル作成時には注意。モデル作成
$ rails g model User name:string email:string password_digest:string
modelファイル編集
app/models/user.rb
に下記を追加します。
before_save
... オブジェクトの保存直前に実行has_secure_password
... 前述validates
... バリデーション処理class User < ApplicationRecord before_save { email.downcase! } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i.freeze # 半角英数字大文字小文字をそれぞれ1文字以上含む VALID_PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]{8,16}+\z/.freeze validates :name, presence: true, length: { in: 1..30 } validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 8 }, format: { with: VALID_PASSWORD_REGEX } endMigration
$ rails db:migrate invoke active_record create db/migrate/20190829085116_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml確認
User.new
で作成したオブジェクトをsave
メソッドで保存。
ハッシュ化されたパスワードが登録されていることを確認します。$ rails c Loading development environment (Rails 5.2.3) irb(main):001:0> user = User.new(name: "kohbis", email: "tEst@test.com", password: "Passw0rd") => #<User id: nil, name: "kohbis", email: "tEst@test.com", password_digest: "$2a$12$pPsDOoJErpVXsdtLWNoGvOTdmUFBSGk/nf492aA0N.1...", created_at: nil, updated_at: nil> irb(main):002:0> user.save (0.9ms) BEGIN User Exists (1.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = 'tEst@test.com' LIMIT 1 User Create (0.5ms) INSERT INTO `users` (`name`, `email`, `password_digest`, `created_at`, `updated_at`) VALUES ('kohbis', 'test@test.com', '$2a$12$pPsDOoJErpVXsdtLWNoGvOTdmUFBSGk/nf492aA0N.18p.sIGQ/Ce', '2019-08-29 12:39:14', '2019-08-29 12:39:14') (2.2ms) COMMIT => true irb(main):003:0> User.all User Load (0.9ms) SELECT `users`.* FROM `users` LIMIT 11 => #<ActiveRecord::Relation [#<User id: 1, name: "kohbis", email: "test@test.com", password_digest: "$2a$12$pPsDOoJErpVXsdtLWNoGvOTdmUFBSGk/nf492aA0N.1...", created_at: "2019-08-29 12:39:14", updated_at: "2019-08-29 12:39:14">]>最後にvalidationが効いているかも確認しておきます。
$ rails c Loading development environment (Rails 5.2.3) irb(main):001:0> user = User.new(name: "", email: "@not.address", password: "password") (0.6ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483 => #<User id: nil, name: "", email: "@not.address", password_digest: "$2a$12$5PovSBWfHCPDOKQcD3lCAuOC7YepZvIwIaocF01tEI5...", created_at: nil, updated_at: nil> irb(main):002:0> user.valid? User Exists (0.5ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = '@not.address' LIMIT 1 => false irb(main):003:0> user.errors.messages => {:name=>["can't be blank", "is too short (minimum is 1 character)"], :email=>["is invalid"], :password=>["is invalid"]}以上。
- 投稿日:2019-08-29T23:04:42+09:00
fishでrbenvを使う方法
bashからfishに乗り換えたのだけれども、Rubyのバージョンが上手く切り替わらなかったのでメモ
最初のRubyバージョン
~/D/G/rails_demo ruby -v Thu Aug 29 22:37:17 2019 ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18]rbenv initを一回行う
~/D/G/rails_demo rbenv init Thu Aug 29 22:38:56 2019 # Load rbenv automatically by appending # the following to ~/.config/fish/config.fish: status --is-interactive; and source (rbenv init -|psub)念の為以下コマンドを実行する
~/D/G/rails_demo status --is-interactive; and source (rbenv init -|psub)ruby -v を実行してバージョンが切り替わっていたらOK
! ~/D/G/rails_demo ruby -v Thu Aug 29 22:38:57 2019 ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18]
- 投稿日:2019-08-29T22:12:07+09:00
is_a?(Arry)メソッド
is_a?(Arry)メソッド
レシーバのオブジェクトが引数クラスのインスタンスであればtrue、そうでなければfalseを返します。
この場合だとarryならtrueを返す
https://ref.xaio.jp/ruby/classes/object/kind_ofコンソールで試した
[1] pry(main)> arr = [1,2,3] => [1, 2, 3] [2] pry(main)> arr.is_a?(Array) => true [3] pry(main)> arr.is_a?(Hash) => false [4] pry(main)> b = {unko: 3} => {:unko=>3} [5] pry(main)> b.is_a?(Hash) => true [6] pry(main)> b.is_a?(Array) => false
- 投稿日:2019-08-29T21:00:13+09:00
オススメ! Evryday Rails - RSpecによるRailsテスト入門を購入しました!
タイトルの通りEvryday Rails - RSpecによるRailsテスト入門を購入しました。
購入しようと思った理由は、RSpecを書いてみたくて、いままで自分で調べながらやっていたけど、情報がバラバラで自分ではいまいち理解できずモヤモヤしていたから。今日は3章までやってみたが、とにかく分かりやすい!モヤモヤしてたのがス~と消えてく感じです。これから頑張っていきます!
余談
Windows 10 Home
購入してさっそく勉強を始めようとして、Google Chromeをダブルクリックしたらなぜか開かない
昨日まで正常に動いたのに。Microsoft Edgeを開いたらこれは正常に起動した。調べていろいろ試したけど変化が無かったので、結局Chromeを削除し、再インストールしたら起動するようになりました。なぜ急に壊れた???
- 投稿日:2019-08-29T19:56:03+09:00
Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #13 パスワード再設定編
こんな人におすすめ
- プログラミング初心者でポートフォリオの作り方が分からない
- Rails Tutorialをやってみたが理解することが難しい
前回:#12 ActionMailer, アクティベーション編
次回:準備中こんなことが分かる
- パスワードを再設定させる方法
- トークンとダイジェストを生成、認証する方法
- メーラーの使い方
- RSpecでコントローラのインスタンス変数を用いる方法
今回の流れ
- パスワード再設定のイメージをつかむ
- ビューを作る
- トークンとダイジェストを生成しURL入りメールを送信する
- URLの情報が正しいか確認し再設定する
パスワード再設定のイメージ
パスワード再設定は#12で紹介したアクティベーションと似ています。
ぜひ比較しながらご覧ください。
アクティベーションの時はビューが必要ありませんでした。
しかし今回は2つのビューと4つのアクションが必要です。今回も先にコントローラとリソースを生成します。
bash$ rails g controller PasswordResets new create edit update
config/routes.rb# 中略 resources :password_resets, only: [:new, :create, :edit, :update]パスワード再設定までをつくる手順
以下はパスワード再設定をつくるときの手順です。
- パスワード再設定用のビューを作る(new)
- トークンとダイジェストを生成し、メールを送信する(create)
- メール内のURLにトークンとメールアドレスを忍ばせる
- URLをクリックしたらURL内の情報が有効か確認する(edit)
- 確認した情報や入力したパスワードが正しければ再設定が完了する(update)
パスワード再設定用のビューを生成(new)
それでは始めていきましょう。
始めに先にビューを完成させてしまいます。
整えるビューは3つです。
- ログイン画面にパスワード再設定リンク(メール入力)を追加する
- パスワード再設定画面(メール入力)を作る
- パスワード再設定画面(パスワード入力)を作る
ログイン画面にリンクを追加
ログイン画面のビューからパスワード再設定にアクセスできるように編集します。
app/views/sessions/new.html.erb<% provide(:title, "ログイン") %> <div class="container form-container login-container"> <div class="row"> <div class="col"> <div class="form-logo-img"> <%= link_to image_tag('lantern_lantern_logo.png', width: 100), root_path, class: "logo-img" %> </div> <h1 class="form-title">ログイン</h1> <%= form_with(scope: :session, url: login_path, local: true) do |form| %> <div class="form-group"> <%= form.email_field :email, class: 'form-control', placeholder: "メールアドレス" %> </div> <div class="form-group"> <%= form.password_field :password, class: 'form-control', placeholder: "パスワード" %> </div> <div class="form-group form-check"> <%= form.check_box :remember_me, class: 'form-check-input' %> <%= form.label :remember_me, '次から保存(ログイン省略)', class: 'form-check-label' %> </div> <div class="form-group"> <%= form.submit "ログイン", class: 'btn btn-info btn-lg form-submit' %> </div> <% end %> <p class="form-go-to-signup-or-login">新しくはじめる方は<%= link_to "こちら", signup_path %></p> <p class="form-go-to-password-reset">パスワードを忘れた方は<%= link_to "こちら", new_password_reset_path %></p> </div> </div> </div>パスワード再設定画面(メール入力)を作る
続いてメールを送るまでのパスワード再設定画面を作ります。
app/views/password_resets/new.html.erb<% provide(:title, "パスワード再設定依頼") %> <div class="container form-container password_reset-container"> <div class="row"> <div class="col"> <div class="form-logo-img"> <%= link_to image_tag('lantern_lantern_logo.png', width: 100), root_path, class: "logo-img" %> </div> <h1 class="form-title">パスワード再設定</h1> <%= form_with(scope: :password_reset, url: password_resets_path, local: true) do |form| %> <div class="form-group"> <%= form.email_field :email, class: 'form-control', placeholder: "メールアドレス" %> </div> <div class="form-group"> <%= form.submit "送信", class: 'btn btn-info btn-lg form-submit' %> </div> <% end %> <p class="form-go-to-signup-or-login">思い出した方は<%= link_to "こちら", login_path %></p> </div> </div> </div>パスワード再設定画面(パスワード入力)を作る
ここはちょっとクセがあります。
なぜならPATCHであるapdateアクションは、何をキーにしてユーザを判別すれば良いのか考える必要があるからです。メール送信時(GET / #edit)はURL内のメールアドレスをキーにしました。
同じようにするにはフォームでメールアドレスを送信してほしいものです。
しかしユーザ側からすると手間になります。そこで隠しフィールドを使います。
<%= hidden_field_tag :email, @user.email %>こんな風に記述すると見えない形でパラメータを送ってくれます。
(params[:email]の形で取得できます)
これを用いつつ、ビューを完成させましょう。app/views/password_resets/edit.html.erb<% provide(:title, "パスワード再設定") %> <div class="container form-container password_reset-container"> <div class="row"> <div class="col"> <div class="form-logo-img"> <%= link_to image_tag('lantern_lantern_logo.png', width: 100), root_path, class: "logo-img" %> </div> <h1 class="form-title">パスワード再設定</h1> <%= form_with(model: @user, url: password_reset_path(params[:id]), local: true) do |form| %> <%= render 'shared/error_messages' %> <%= hidden_field_tag :email, @user.email %> <div class="form-group"> <%= form.password_field :password, class: 'form-control', placeholder: "パスワード" %> </div> <div class="form-group"> <%= form.password_field :password_confirmation, class: 'form-control', placeholder: "パスワード(再入力)" %> </div> <div class="form-group"> <%= form.submit "送信", class: 'btn btn-info btn-lg form-submit' %> </div> <% end %> </div> </div> </div>トークンとダイジェストを生成しメールを送信する(create)
続いてはトークンとダイジェストの生成からメール送信までを記述しましょう。
手順は以下の通りです。
- ダイジェスト用の属性を与える
- トークンとダイジェストを生成、認証するメソッドを確認する
- 再設定用トークンとダイジェストを生成、メールを送信するメソッドをつくる
ダイジェスト用の属性を与える
その前にマイグレーションで属性を与えます。
以前の記事と異なるのは、再設定の有効期限を記す属性も加えるという点です。bash$ rails g migration add_reset_to_users reset_digest:string reset_sent_at:datetime $ rails db:migrateトークンとダイジェストを生成、認証するメソッドを確認する
トークンやダイジェストの生成、認証するメソッドは#9ですでに紹介しています。
すでに#9をお読みの方は飛ばしてください。
それ以外の方は以下のメソッドを追加してください。app/models/user.rbclass User < ApplicationRecord # 中略 class << self # ダイジェストを生成する def digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # トークンを生成する def new_token SecureRandom.urlsafe_base64 end end # トークンとダイジェストを認証する def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end再設定用トークンとダイジェストを生成、メールを送信するメソッドをつくる
次はメール送信までに必要なメソッドを用意しましょう。
必要なメソッドを列挙します。
- create_reset_digest → トークンとダイジェストをまとめて生成
- send_password_reset_email → メールを送信
トークンは仮属性なのでattr_accessorによる記述もお忘れないように。
class User < ApplicationRecord attr_accessor :remember_token, :activation_token, :reset_token # 中略 def create_reset_digest self.reset_token = User.new_token update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now) end def send_password_reset_email UserMailer.password_reset(self).deliver_now end private # 中略 endついcreate_reset_digestをcreate_activation_digestのすぐ下に記述したくなります。
しかしここはprivateメソッドです。
なぜprivateを使い分けているのでしょうか。理由はこうです。
- create_activation_digestはこのUserクラス内でしか使いません
- よって余計なスコープを広げないために、privateメソッドを使用します
- でもcreate_reset_digestはコントローラで使用します
- よってcreate_reset_digestはUserクラスを超えるのでprivateには置けません
使い分けられるよう、心がけましょう。
createアクションからメール送信などを行う
それではcreate_reset_digestなどをコントローラで使いましょう。
メール送信に関する部分はcreateアクションに記述します。app/controllers/password_resets_controller.rbclass PasswordResetsController < ApplicationController def new end def create @user = User.find_by(email: params[:password_reset][:email].downcase) if @user @user.create_reset_digest @user.send_password_reset_email flash[:info] = "再設定用のURLを入力したメールに送信しました" redirect_to root_url else flash[:danger] = "お使いのメールアドレスは登録されていません" render 'new' end end def edit end def update end endメールの設定を行う
createアクションでメール送信を行いました。
続いてはメール内容を整えます。メーラーの生成に関しては#12でご確認ください。
お読みでない方用に、必要なコードのみ紹介します。
#12をお読みの方はこのコードのみ飛ばしてくださいbash$ rails g mailer UserMailer account_activation password_reset
app/mailers/application_mailer.rbclass ApplicationMailer < ActionMailer::Base default from: 'noreply@example.com' layout 'mailer' end本番環境(〇〇は本番環境で自分のアプリを開いたときのURL)
config/environments/production.rb# 中略 config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp host = '〇〇.herokuapp.com' config.action_mailer.default_url_options = { host: host } ActionMailer::Base.smtp_settings = { :address => 'smtp.sendgrid.net', :port => '587', :authentication => :plain, :user_name => ENV['SENDGRID_USERNAME'], :password => ENV['SENDGRID_PASSWORD'], :domain => 'heroku.com', :enable_starttls_auto => true }この後からはお読みの方も必要な手順です。
間違えないよう注意していただけたら幸いです。メールの送信先とタイトルをつくる
送信先とタイトルはUserMailerクラスの仕事です。
記述しましょう。mailers/user_mailer.rbclass UserMailer < ApplicationMailer # 中略 def password_reset(user) @user = user mail to: user.email, subject: "【重要】Lantern Lanternよりパスワード再設定のためのメールを届けました" end endメール内のURLにトークンとメールアドレスを忍ばせる
メールの本文はViewの仕事です。
Viewはhtml版とtext版で2つあるので注意しましょう。
こんな風に記述するとURLにトークンとメールアドレスを仕込むことができます。app/views/user_mailer/password_reset.html.erb<h1>Lantern Lantern</h1> <p><%= @user.name %>さんへ</p> <p>下記の『パスワードを再設定する』をクリックしてパスワードを再設定してください。</p> <%= link_to "パスワードを再設定する", edit_password_reset_url(@user.reset_token, email: @user.email) %>app/views/user_mailer/password_reset.text.erb<%= @user.name %>さんへ Lantern Lanternです。下記のリンクからパスワードを再設定してください。 <%= edit_password_reset_url(@user.reset_token, email: @user.email) %>どうやってURLを指定したのか。
#12でも説明しましたがこちらでも解説します。
URLの指定にはこのようなコードを使用しました。edit_password_reset_url(@user.reset_token, email: @user.email)まずはルートを調べてURLのパターンを確認しましょう。
bash$ rails routes | grep edit_password_reset edit_password_reset GET /password_resets/:id/edit(.:format)『:id』と書かれたこの部分。
ここがedit_password_reset_urlの第1引数に対応します。
だからここにトークンを記述しています。では第2引数は何をしているのでしょう。
第2引数はハッシュを使用するとクエリパラメータを付与します。
具体的には最後(/editの直後)にemailキーとメールアドレスを組み込みます。結果としてこんなURLが生成されます。
http://www.example.com/password_resets/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com以上がURLのからくりです。
メールのプレビューをつくる
先ほど作ったメールがどんな感じに作られたか。
確認するためにプレビューが欲しいところです。
まずはspec内にこのような記述をしましょう。spec/mailers/previews/user_mailer_preview.rbclass UserMailerPreview < ActionMailer::Preview # 中略 # https://〇〇/rails/mailers/user_mailer/password_reset def password_reset user = User.first user.reset_token = User.new_token UserMailer.password_reset(user) end end加えてテスト環境の設定が必要です。
(#12を済ませた方は必要ありません)
#12にも書いていますが、一応ここでも簡易的に紹介します。config/environments/development.rb# Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :test host = '〇〇' config.action_mailer.default_url_options = { host: host, protocol: 'https' } config.action_mailer.perform_caching = false'〇〇'のところは自分の環境によって変更する必要があります。
具体的にはdevelopment環境のURLを挿入します。
(わからない方は#12をご覧ください)これが終わると以下のURLにアクセスしましょう。
するとプレビューが表示されます。https://〇〇/rails/mailers/user_mailer/password_reset情報が正しいか確認し再設定する(edit, update)
URLがクリックされたらeditアクションを呼び出します。
ここで行うことは3つあります。
- Userモデルを特定する
- 存在するか、トークンは正しいのかを確かめる
- URLが期限切れでないかを確かめる
この3つに関してはupdateアクションでも同じことを行います。
ということはこうした方がスッキリします。
- 上記3つをメソッド化する
- editとupdate時に呼び出す
アクションの直前にメソッドを呼び出すにはbefore_actionを使います。
一方updateアクションはフォームにパスワードが入力された時の処理を書きます。
今回は3つのケースに対応します。
- 入力されていない時
- 無効なパスワードの時
- 正しい時
こちらはupdateアクションのみの振る舞いなので、直接書き込みます。
では、これらを踏まえて実装しましょう。app/controllers/password_resets_controller.rbclass PasswordResetsController < ApplicationController before_action :get_user, only: [:edit, :update] before_action :valid_user, only: [:edit, :update] before_action :check_expiration, only: [:edit, :update] def new end def create @user = User.find_by(email: params[:password_reset][:email].downcase) if @user @user.create_reset_digest @user.send_password_reset_email flash[:info] = "再設定用のURLを入力したメールに送信しました" redirect_to root_url else flash[:danger] = "お使いのメールアドレスは登録されていません" render 'new' end end def edit end def update if params[:user][:password].empty? @user.errors.add(:password, :blank) render 'edit' elsif @user.update_attributes(user_params) log_in @user @user.update_attribute(:reset_digest, nil) flash[:success] = "パスワードの再設定が完了しました" redirect_to @user else render 'edit' end end private def user_params params.require(:user).permit(:password, :password_confirmation) end def get_user @user = User.find_by(email: params[:email]) end def valid_user unless @user && @user.activated? && @user.authenticated?(:reset, params[:id]) flash[:danger] = "無効なURLです。再度メールアドレスを入力してください" redirect_to new_password_reset_url end end def check_expiration if @user.password_reset_expired? flash[:danger] = "パスワード再設定URLの有効期限が過ぎています。再度メールアドレスを入力してください" redirect_to new_password_reset_url end end end1つ言い忘れたことがあります。
それはcheck_expiration内にあるpassword_reset_expired?メソッドです。期限切れかどうかを判別する処理に関しては、別途Userモデルにメソッドを用意した上で実装しています。
そちらのメソッドを紹介します。
app/models/user.rbclass User < ApplicationRecord # 中略 def password_reset_expired? reset_sent_at < 2.hours.ago end private # 中略これでパスワード再設定に関する全ての実装が完了しました。
テストを書く
最後にパスワード再設定に関するテストを完成させます。
いくつかあるので順に見ていきます。メーラーテストを書く
まずはメーラーのテストです。
メールの内容についてテストします。
なおメール本文に関してはデコードを行って検証しています。spec/mailers/user_mailer_spec.rbrequire "rails_helper" RSpec.describe UserMailer, type: :mailer do let(:user) { create(:user) } # 中略 describe "password_reset" do it "renders mails" do user.reset_token = User.new_token mail = UserMailer.password_reset(user) expect(mail.subject).to eq "【重要】Lantern Lanternよりパスワード再設定のためのメールを届けました" expect(mail.to).to eq ["michael@example.com"] expect(mail.from).to eq ["noreply@example.com"] expect(mail.body.encoded.split(/\r\n/).map{|i| Base64.decode64(i)}.join).to include "Michael Example" expect(mail.body.encoded.split(/\r\n/).map{|i| Base64.decode64(i)}.join).to include user.reset_token expect(mail.body.encoded.split(/\r\n/).map{|i| Base64.decode64(i)}.join).to include CGI.escape(user.email) end end endパスワード変更のテストを書く
メーラー以外のテストを書きます。
まずはRequest specを生成するところから始めましょう。bash$ rails g rspec:request password_resets
今回テストする内容は以下の通りです。
createアクション
- 無効なメールアドレス
- 有効なメールアドレス
editアクション
- 無効なメールアドレス
- 無効なユーザ
- 無効なトークン
- 有効なメールアドレス、ユーザ、トークン
updateアクション
- 無効なパスワード
- 空のパスワード
- 期限切れのトークン
- 有効なパスワード
では記述します。
spec/requests/password_resets_spec.rbrequire 'rails_helper' RSpec.describe "PasswordResets", type: :request do let(:user) { create(:user) } describe "POST /password_resets" do it "is invalid email address" do get new_password_reset_path expect(request.fullpath).to eq '/password_resets/new' post password_resets_path, params: { password_reset: { email: "" } } expect(flash[:danger]).to be_truthy expect(request.fullpath).to eq '/password_resets' end it "is valid email address" do get new_password_reset_path expect(request.fullpath).to eq '/password_resets/new' post password_resets_path, params: { password_reset: { email: user.email } } expect(flash[:info]).to be_truthy expect(flash[:danger]).to be_falsey follow_redirect! expect(request.fullpath).to eq '/' end end describe "GET /password_resets/:id/edit" do it "is invalid email address" do post password_resets_path, params: { password_reset: { email: user.email } } user = assigns(:user) get edit_password_reset_path(user.reset_token, email: "") expect(flash[:danger]).to be_truthy follow_redirect! expect(request.fullpath).to eq '/password_resets/new' end it "is invalid user" do post password_resets_path, params: { password_reset: { email: user.email } } user = assigns(:user) user.toggle!(:activated) get edit_password_reset_path(user.reset_token, email: user.email) expect(flash[:danger]).to be_truthy follow_redirect! expect(request.fullpath).to eq '/password_resets/new' user.toggle!(:activated) end it "is invalid token" do post password_resets_path, params: { password_reset: { email: user.email } } user = assigns(:user) get edit_password_reset_path('wrong token', email: user.email) expect(flash[:danger]).to be_truthy follow_redirect! expect(request.fullpath).to eq '/password_resets/new' end it "is valid information" do post password_resets_path, params: { password_reset: { email: user.email } } user = assigns(:user) get edit_password_reset_path(user.reset_token, email: user.email) expect(flash[:danger]).to be_falsey expect(request.fullpath).to eq "/password_resets/#{user.reset_token}/edit?email=#{CGI.escape(user.email)}" end end describe "PATCH /password_resets/:id" do it "is invalid password" do post password_resets_path, params: { password_reset: { email: user.email } } user = assigns(:user) get edit_password_reset_path(user.reset_token, email: user.email) patch password_reset_path(user.reset_token), params: { email: user.email, user: { password: "foobaz", password_confirmation: "barquux" } } expect(request.fullpath).to eq "/password_resets/#{user.reset_token}" end it "is empty password" do post password_resets_path, params: { password_reset: { email: user.email } } user = assigns(:user) get edit_password_reset_path(user.reset_token, email: user.email) patch password_reset_path(user.reset_token), params: { email: user.email, user: { password: "", password_confirmation: "" } } expect(request.fullpath).to eq "/password_resets/#{user.reset_token}" end it "has expired token" do post password_resets_path, params: { password_reset: { email: user.email } } user = assigns(:user) user.update_attribute(:reset_sent_at, 3.hours.ago) get edit_password_reset_path(user.reset_token, email: user.email) patch password_reset_path(user.reset_token), params: { email: user.email, user: { password: "foobaz", password_confirmation: "foobaz" } } expect(flash[:danger]).to be_truthy follow_redirect! expect(request.fullpath).to eq '/password_resets/new' end it "is valid information" do post password_resets_path, params: { password_reset: { email: user.email } } user = assigns(:user) get edit_password_reset_path(user.reset_token, email: user.email) patch password_reset_path(user.reset_token), params: { email: user.email, user: { password: "foobaz", password_confirmation: "foobaz" } } expect(flash[:success]).to be_truthy expect(is_logged_in?).to be_truthy follow_redirect! expect(request.fullpath).to eq "/users/1" end end endこのテストではassignsを使用するところがあります。
# 該当箇所 user = assigns(:user) get edit_password_reset_path(user.reset_token, email: "")これによりFactoryBotのuserではなく、コントローラ内インスタンス変数のuserをuserとして取得し直しています。なぜでしょう。
理由は以下の通りです。
- edit_password_reset_pathの引数に当たるreset_tokenは、attr_accessorによって生成された仮属性です。
- 仮属性なのでletで生成したFactoryBotのuserにはreset_tokenが存在しません。よってエラーになります。
- したがってreset_tokenが代入されたPasswordResetsコントローラのインスタンス変数userを使用する必要があります。
というわけで、コントローラのインスタンス変数のuserが必要です。
そこでassignsを使用します。
assignsはコントローラのインスタンス変数を取得します。そのためにはgem 'rails-controller-testing'が必要です。
(とてもためらいましたが)導入しましょう。Gemfilegroup :development, :test do + gem 'rails-controller-testing' endbash$ bundle installこれで問題なく動作します。
テストを走らせてみましょう。bash$ rails spec
問題なければ、以上でテストは終了です。
追記:ですます調に統一しました
今回から語尾を「ですます」に統一しました。
情報が統一されて見やすいかなと。
分かりやすい記事になるよう努めます。やさしい記事の書き方↓
これであなたのQiita記事もランキング入り!?@jnchitoによる編集リクエスト解説(解説動画付き)
- 投稿日:2019-08-29T19:02:09+09:00
【TwitterAPI】「贅沢な名だねえ。今からお前の名前は○○だ。」【Ruby】
動機
- 2019/8/16に金曜ロードショーで「千と千尋の神隠し」が放送された
- 前に少し流行ったupdate_name(ツイッター上で、リプライに反応して自分のTwitterネームを変更するもの)がUserStreamの廃止に伴い実装が困難になっていて、どうにかREST APIだけで再現できないか考えてた
- この記事を見て、似たようなものをRubyで書いてみようと思ってた
環境と使ったもの
- macOS 10.14.5
- Ruby 2.5.1
- TwitterAPI … Twitter Developersに登録済みであることが前提です。
仕様
- 「いいかい、今から@fyhcu の名前は○○だ。」というリプライを、最新50件のリプライから正規表現で見つける。台詞は少し変えてます。(できるだけ簡単な文章にしたかったので)
- Twitterの制限上、名前が50文字以上になるものは設定できないのでこれを除外。さらに、以前に候補に上がったものも除外。候補になったかどうかはいいねしているかどうかで判断します。
- 候補となったツイートを配列に格納し、そのツイートをいいねする。(これで次動いた時の候補に挙がらなくなります)
- 格納した候補からランダムに一つ選ぶ。
- 名前をそのワードに変更。変更したことをツイート。
結果
実装
メイン部分
main.rbrequire_relative '../Privatekey/oauth_twitter' require_relative 'isfavorited' require 'timers' #定期実行用 timers = Timers::Group.new timers.every(600){ #600秒 = 10分ごとに処理 reply_array = [] #リプライを格納する配列 random = 0 #改名候補ツイートから無作為に選ぶため reg1 = /^いいかい、今から@fyhcu の名前は(.*)だ。$/ #湯婆婆 @client.mentions(count: 60).each do |reply| if (reg1 =~ reply.text) #湯婆婆になっているリプライを探す puts "-----------------------------------------------------------" puts reply.text #名前が50文字以上に設定されているツイートは除外 if ($1.length >= 50) next #次の繰り返しに移動 end #既に自分がふぁぼってるツイートは除外 if isfavorited(reply.id) == true puts "<<<<<<<この返信は既にいいねされています>>>>>>>>" next end reply_array << $1 #改名候補を格納 @client.favorite(reply.id) #ツイートをいいね end end puts "-----------------------------------------------------------" time_f = Time.now.strftime("%F %X") #実行時間を記録 if reply_array.size > 0 #改名候補がある時のみ random = rand(reply_array.size) #ランダムに数字を一つ選ぶ aftername = reply_array[random] #変更後の名前を決定 puts "#{aftername}に改名決定です" @client.update_profile(:name => aftername) #改名 tweet = "湯婆婆によって名前が#{aftername}に変えられましたた。" @client.update tweet #ログに書き込み File.open("../log/yubaba-app-log.txt", "a") do |text| text.puts(" => #{aftername}に改名 [#{time_f}]") end else #改名候補がない時(配列に何も格納されていない時) puts "改名候補がありませんでした。" #ログに書き込み File.open("../log/yubaba-app-log.txt", "a") do |text| text.puts(" => 変化なし [#{time_f}]") end end } 1000.times{ #1000回実行(この実装は良くなさそう) timers.wait puts Time.now.strftime("****************** %Rに実行しました ******************") }湯婆婆構文は正規表現を使って拾っています。正規表現で一致し、かつ特定の条件を満たしたリプライの変更後の名前部分を配列に格納しています。正規表現のグルーピング
()
と後方参照$1,$2,...
がとても役に立ちました。次以降の候補に入らないように、@client.favorite(id)
でいいねしています。UserStreamが動いていたあの頃のupdate_nameに近付けたいので、10分毎に定期実行をかけています。Timerを使っていますが、多分良くない気がするので近いうちにHeroku Schedulerとか使えたらなと思います。また、main.rbを実行すると、ターミナル上に正規表現によって拾われたツイートとそれがいいねされているかどうかが表示されます。実行時に改名の有無にかかわらずログを書くようにもしました。
※このコードのままだと一定時間内に2回同じ名前に変更されると2回目以降改名した旨のツイートか重複によりツイートできない状況になってしまいます。例外処理でなんとかできれば…。(気が向いたら実装します)
isfavoriteメソッド
「自分があるツイートに対していいねしているか」をbooleanで返すメソッドがなかったので自作しました。
JSON.parse
が便利でした。isfavorited.rbrequire_relative '../Privatekey/oauth_twitter' require 'json' #JSON扱うためのgem require 'oauth' # ツイートに対して、自分がいいねしているか調べるメソッド def isfavorited(tweet_id) consumer = OAuth::Consumer.new( @client.consumer_key, @client.consumer_secret, ) endpoint = OAuth::AccessToken.new(consumer, @client.access_token, @client.access_token_secret) responce = endpoint.get("https://api.twitter.com/1.1/statuses/show/#{tweet_id.to_s}.json") result = JSON.parse(responce.body) fav = result["favorited"] end
https://api.twitter.com/1.1/statuses/show/#{tweet_id.to_s}.json"
は、ツイート(ID指定)の個別情報を取得できるエンドポイントで、レスポンスはJSONです。GET statuses/show/:id - TwitterDevelopers
レスポンスボディの中にある
favorited: true(false)
が自身がいいねしたかどうかを示しています。これを引っ張り出してきて最終的にbooleanをそのまま返すようにしてます。実際、この2行があるだけで結構いろんなことができるなあと思っています。responce = endpoint.get("https://api.twitter.com/1.1/statuses/show/#{tweet_id.to_s}.json") result = JSON.parse(responce.body)感想
意外と簡単に実装できました。いっぱい遊んでもらえたので満足です。これからもくだらないものを実装してみようと思います。
- 投稿日:2019-08-29T18:46:52+09:00
Railsでbundle installの権限エラーが出た
はじめに
こんにちは^^
こちらの記事では、Railsのコマンド入力でbundle install
をした場合に、Errno::EACCES: Permission denied @ rb_sysopen... Make sure that `gem install mysql2 -v '0.4.10' --source 'https://rubygems.org/'` succeeds before bundling.などのエラーが出て、上記の
gem install mysql2...
を入力し、再度bundle install
をすると、また別のgem installコマンドを入力してくださいと何度も何度も何度も・・・何度gem installとbundle installをすれば良いんだという。。。
これを一度のbundle install
コマンドで手っ取り早くインストールを可能にする方法をご紹介します。前提:Mac, rbenvでRailsインストール
原因は、、、?
そもそも一度のbundle installでgemが一気にインストールされないのは、
rbenv
の所有者が異なることが原因です。$ ls -la /Users/ユーザー名/.rbenv/versions/2.6.2などとコマンドを打ってみる(Macの場合)と所有者がrootになっていると思います。ですので、所有者を現ログインユーザーに変更すれば一気にインストールできるかと思います^^
ls -laコマンドは指定したファイルやフォルダの詳細情報を見ることができるコマンドです。
よく聞くrwx
の権限が見れたり所有者が見れたり、サイズが見れたり作成日が見れたりもします。コマンド入力
所有者をコマンドによって変更したいと思います。(所有者を変更すると環境によっては不具合が出ることがあるかと思いますので、変更する際はしっかりとご確認ください。)
chownは所有者を変更するコマンドで、-Rは再帰(下の方にある構造にも所有者変更を反映させる)を設定するものになります。
Railsのインストールにrbenvを使用していることが前提となってしまいますが、ここの所有者を変更することが大事になります^^
Ralisをインストールした時のパスは適宜変更していただければと思います。sudo chown -R ユーザー名 /Users/ユーザー名/.rbenv/versions/2.4.2終わりに
以上、Railsでbundle installの権限エラーが出た場合の対処法をご紹介しました。
プログラミングを勉強をしているのに、コマンドでつまづくのは嫌、、、なんでここで。。。という風に思うことは多々あります。コマンドラインの知識はいずれ必要になってくるものなので、今は一旦意味わからずコマンドを打っても致し方ないと思いますが、あとでしっかり勉強しましょう!
もちろん業務で意味わからないコマンドを打つのは厳禁です。。。
よくつい打ってしまったというコマンドでたまに聞くのはrm -rf /*
とかrm -rf .
とかですかね、、、
- 投稿日:2019-08-29T16:20:40+09:00
change_column の rollback エラー
migration の change メソッドは change_column をサポートしてない。なので、rollback した時にエラーになる。change_column を使いたいときは、明示的に up メソッドと down メソッドを使う必要がある。
class ChangeColumnToUser< ActiveRecord::Migration # 変更内容(db:migrate時に実行される) def up change_column :users, :name, :string #nameカラムのデータ型をstringへ変更 end # 変更前の状態(db:rollback時に実行される) def down change_column :users, :name, :text end endchangeメソッドがサポートしているメソッドは以下。
add_column add_foreign_key add_index add_reference add_timestamps change_column_default (:fromと:toの指定は省略できない) change_column_null create_join_table create_table disable_extension drop_join_table drop_table (ブロックを渡さなければならない) enable_extension remove_column(型を指定しなければならない) remove_foreign_key(2番目のテーブルを指定しなければならない) remove_index remove_reference remove_timestamps rename_column rename_index rename_tablesqlが絡んだりする複雑なマイグレーションを書くときは reversible を使う。
class ExampleMigration < ActiveRecord::Migration[5.0] def change create_table :distributors do |t| t.string :zipcode end reversible do |dir| dir.up do # CHECK制約を追加 execute <<-SQL ALTER TABLE distributors ADD CONSTRAINT zipchk CHECK (char_length(zipcode) = 5) NO INHERIT; SQL end dir.down do execute <<-SQL ALTER TABLE distributors DROP CONSTRAINT zipchk SQL end end add_column :users, :home_page_url, :string rename_column :users, :email, :email_address end end
- 投稿日:2019-08-29T16:15:56+09:00
deviseでログイン機能を実装する
Railsのgemであるdeviseについて、導入からログイン機能の実装までを投稿します。
deviseとは
ユーザーの新規登録、ログイン、ログアウトなど、認証に必要な機能を追加することができるgemです。
deviseのインストール
Gemfileにdeviseを追加
gem 'devise'gemをインストールします。
$ bundle install以下のコマンドで、関連ファイルをインストールします。
$ rails g devise:installこれにより、config/initializers/devise.rb、create config/locales/devise.en.ymlというファイルが作成されます。
deviseの設定
まず、config/environments/development.rbに、URLを追記します。
config/environments/development.rbconfig.action_mailer.default_url_options = { host: 'localhost', port: 3000 }これは、ユーザーの新規登録などで使われる、認証メールに記載されるURLを設定しています。なので、メールによる認証を行わない場合はスキップして構いません。
また、エラーメッセージを表示させるために、app/views/layouts/application.html.erbに追記します。
app/views/layouts/application.html.erb<p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p>これを、<%= yield %>の直前に追記します。
ユーザーモデルの作成
認証用のユーザーモデルを作成するには、通常の「rails g model モデル名」ではなく、「rails g devise モデル名」を使います。
$ rails g devise user作成されたユーザーモデルは、以下のとおりです。
app/models/user.rbclass User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable enddeviseの中に記載されているものは、モジュールと呼ばれるもので、それぞれ以下のような機能があります。
・database_authenticatable DBに保存されるパスワードが正しいかの検証と暗号化
・registerable サインアップ処理
・recoverable パスワードのリセット
・rememberable クッキーにログイン情報を保持
・trackable サインイン回数・時刻・IPアドレスを保存
・validatable メールアドレスとパスワードのバリデーション
・confirmable メール送信による登録確認
・lockable 一定回数ログインに失敗した際のアカウントロック
・timeoutable 一定時間でセッションを削除する
・omniauthable OmniAuthサポートデフォルトでは、すべてのモジュールが設定されているわけではないので、必要に応じて追記しましょう。
また、作成されたマイグレーションファイルについても見ていきます。
db/migrate/xxx_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration def change create_table(:users) do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at ## Trackable t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip ## Confirmable # t.string :confirmation_token # t.datetime :confirmed_at # t.datetime :confirmation_sent_at # t.string :unconfirmed_email # Only if using reconfirmable ## Lockable # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts # t.string :unlock_token # Only if unlock strategy is :email or :both # t.datetime :locked_at t.timestamps end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true # add_index :users, :confirmation_token, unique: true # add_index :users, :unlock_token, unique: true end endデフォルトでコメントアウトされているモジュールを、上のapp/models/user.rbで追加した場合、マイグレーションファイルのコメントも外すようにしましょう。
モジュールの編集が終わったら、マイグレーションを実行し、テーブルを作成します。
$ rails db:migrateこの時点で、/users/sign_upにアクセスし、Sign upの画面が表示されれば無事成功です。
あとは、必要に応じて、ビューを編集していきましょう。
ビューの作成
$ rails g devise:viewsこれにより、モデルに対応するいくつかのビューが作成されます。registrations/new.html.erb(新規登録画面)、sessions/new.html.erb(ログイン画面)、registrations/edit.html.erb(ユーザー編集画面)など、必要に応じてレイアウトを編集しましょう。
コントローラの作成
$ rails g devise:controllers usersコントローラを編集したい場合、このコマンドを実行します。また、ルーティングの設定もしましょう。例えば、registrations_controller.rb、sessions_controller.rbを使う場合は以下のようにします。
config/routes.rbdevise_for :users, controllers: { registrations: 'users/registrations', sessions: 'users/sessions', }参考サイト
- 投稿日:2019-08-29T15:38:25+09:00
rails6/windows10/ubuntu/mySQL
windows10でruby on rails使えるようにします。
まずubuntuを入れます。(省略)
rbenvでRubyをインストールします。(省略)
node.jsとかBundlerを入れます。(省略)# mkdir rails_app # cd rails_app # bundle initGemfileが出来るので編集
\# gem "rails"
の行のコメントアウトを外す# bundle install --path vendor/bundle# bundle exec rails new . -B -d mysql --skip-turbolinks --skip-test→ERROR
Could not find gem 'mysql2 (>= 0.4.4)' in any of the gem sources listed in your Gemfile.が出るMYSQLを導入します。
https://dev.mysql.com/downloads/repo/apt/
で、パッケージをダウンロードします。
下のコマンドでインストールします。
インストール中にバージョン選択を求められます。# sudo dpkg -i mysql-apt-config_0.8.13-1_all.deb # sudo apt-get install mysql-server # sudo apt-get install libmysqld-devMySQLを起動します。
# sudo /etc/init.d/mysql startbundle installした後、DBを作成。
# bundle exec rake db:createRAILS_ENV=development environment is not defined in config/webpacker.yml, falling back to production environment Access denied for user 'root'@'localhost' Couldn't create 'rails_app_development' database. Please check your configuration. rake aborted! Mysql2::Error: Access denied for user 'root'@'localhost' :こんなのがでるので
# bundle # bundle exec rails webpacker:installもう一度bundle exec rake db:create
warning Skipping preferred cache folder "/home/user/.cache/yarn" because it is not writable. warning Selected the next writable cache folder in the list, will be "/tmp/.yarn-cache-1000". Access denied for user 'root'@'localhost' Couldn't create 'rails_app_development' database. Please check your configuration. rake aborted! Mysql2::Error: Access denied for user 'root'@'localhost' :権限がないので、ユーザー作って権限を与えます
sql> CREATE USER 'user'@'localhost' IDENTIFIED BY 'password'; sql> GRANT ALL ON rails_app_development.* TO 'user'@'localhost';config/database.ymlの設定と合わます
rails_app_testでも権限ないとでるようなので、
もう一回権限付与します。するとbundle exec rake db:create出来ました# bin/rails sでサーバーがちゃんと起動したので問題ないはず
http://localhost:3000/
に繋ぎますおわり
いろいろ試しましたが、上の手順で良さそうです
嵌りに嵌りましたubuntu version18.04
rbenv 2.6.3
Rails 6.0.0
10.1.41-MariaDBその他
MySQLが起動しない# sudo /etc/init.d/mysql start→ERROR
下を実行
# sudo apt clean # sudo apt install mariadb-common mariadb-server mariadb-client→ mysql -u root する
ERROR 1698 (28000): Access denied for user 'root'@'localhost'
こんなのがでる→ sudo mysql -u root だと入れた
Ubuntu特有の初期設定らしい
- 投稿日:2019-08-29T15:38:25+09:00
rails6サーバー起動まで手順
windows10でruby on rails使えるようにします。
まずubuntuを入れます。(省略)
rbenvでRubyをインストールします。(省略)
node.jsとかBundlerを入れます。(省略)$ mkdir rails_app $ cd rails_app $ bundle initGemfileが出来るので編集
# gem "rails"
の行のコメントアウトを外す$ bundle install --path vendor/bundle$ bundle exec rails new . -B -d mysql --skip-turbolinks --skip-test→ERROR
Could not find gem 'mysql2 (>= 0.4.4)' in any of the gem sources listed in your Gemfile.が出るMYSQLを導入します。
https://dev.mysql.com/downloads/repo/apt/
で、パッケージをダウンロードします。
下のコマンドでインストールします。
インストール中にバージョン選択を求められます。$ sudo dpkg -i mysql-apt-config_0.8.13-1_all.deb $ sudo apt-get install mysql-server $ sudo apt-get install libmysqld-devMySQLを起動します。
$ sudo /etc/init.d/mysql startbundle installした後、DBを作成。
$ bundle exec rake db:createRAILS_ENV=development environment is not defined in config/webpacker.yml, falling back to production environment Access denied for user 'root'@'localhost' Couldn't create 'rails_app_development' database. Please check your configuration. rake aborted! Mysql2::Error: Access denied for user 'root'@'localhost' :こんなのがでるので
$ bundle $ bundle exec rails webpacker:installもう一度bundle exec rake db:create
warning Skipping preferred cache folder "/home/user/.cache/yarn" because it is not writable. warning Selected the next writable cache folder in the list, will be "/tmp/.yarn-cache-1000". Access denied for user 'root'@'localhost' Couldn't create 'rails_app_development' database. Please check your configuration. rake aborted! Mysql2::Error: Access denied for user 'root'@'localhost' :権限がないので、ユーザー作って権限を与えます
sql> CREATE USER 'user'@'localhost' IDENTIFIED BY 'password'; sql> GRANT ALL ON rails_app_development.* TO 'user'@'localhost';config/database.ymlの設定と合わます
rails_app_testでも権限ないとでるようなので、
もう一回権限付与します。するとbundle exec rake db:create出来ました$ bin/rails sでサーバーがちゃんと起動したので問題ないはず
http://localhost:3000/
に繋ぎますおわり
いろいろ試しましたが、上の手順で良さそうです
嵌りに嵌りましたubuntu version18.04
rbenv 2.6.3
Rails 6.0.0
10.1.41-MariaDB-追記-
postgresqlだったらすんなり行けました。
はぁ・・・その他
UbuntuでCドライブ配下に移動
$ cd /mnt/cMySQLが起動しない
$ sudo /etc/init.d/mysql start→ERROR
下を実行
$ sudo apt clean $ sudo apt install mariadb-common mariadb-server mariadb-clientmysql -u root する ERROR 1698 (28000): Access denied for user 'root'@'localhost' こんなのがでる
→ sudo mysql -u root だと入れた
Ubuntu特有の初期設定らしい
- 投稿日:2019-08-29T15:19:34+09:00
Rubyのsort_byで特定の順番に並び替え
Rubyのsort_byで特定の順番に並び替え
昇順や降順に並び替えるのは、よくありますが、
プロデューサーの意向で特定の順番に並び替えたい時があります。例えば、人気順に並び替えたい時などです。
id 順ではなく、とにかく指定した順番に並び替えたい
例えば、こんな配列があって、
@fruites = [ [ 1, 'apple' ], [ 2, 'orange' ], [ 3, 'banana' ], [ 4, 'melon' ], [ 5, 'peach' ], [ 6, 'kiwi' ], [ 7, 'cherry' ], ]こんな id順で指定されたとします
FRUITS_ORDER = [3, 2, 7, 5, 4, 1, 6]sort_by に、並び替えたいid順におけるindex(位置情報)を渡してあげると、その通りに並び替えられます
@fruites.sort_by{|fruit| FRUITS_ORDER.index(fruit[0])} => [[3, "banana"], [2, "orange"], [7, "cherry"], [5, "peach"], [4, "melon"], [1, "apple"], [6, "kiwi"]]
- 投稿日:2019-08-29T13:41:11+09:00
H30秋基本情報技術者試験問3システム(随時加筆
2019年3,4月の間はエンジニアの下で、Rubyを中心に色々学んだ。
その中で、H30秋基本情報技術者試験問3のコンサートに則したサイトを実際に作っていた。5月以降も勉強しながら、少しずつ機能等を追加していたが、アウトプットしていなかった。
いつかまたRubyonRailsで似たことをするとき用に書き残しておこう。参照
自分のサイト
コンサート問題のGithubレポジトリ
GithubPages自分の関連アウトプット
21日目:H30秋基本情報技術者試験の問3データベース
プログラミングを2か月間、セブで学んできたトランザクション(Paymentコントローラ)
エンジニアの下で学び、仕組みを理解し、アウトプット18日目:トランザクションって
を書いてはいたが、深くまで理解しておらず、実装時にてこずった。
コンサートチケットの支払い時の、ポイント使用・追加あたりの、Paymentコントローラ内に実装。
※※※なお、Userテーブルに所持金カラムを追加してないのでトランザクションの流れ
- ユーザはポイントUser.pointを持っている。
- 購入時にUser.pointの一部/全部を支払額Sale.amountに充てることができる。
- 使用ポイントSale.used_pointが更新される
- 支払額から使用したUser.pointを引いたものが、決済額Payment.amountとなる。
- 決済額Payment.amountのうち、既定の割合が付与ポイントPayment.added_pointとなる。
- ユーザのポイント残高は、(支払前の)
User.point - Sale.used_point + Payment.added_point
で更新される。支払い完了の条件
- User.point、Sale.used_point、Payment.added_pointは全て0以上(>=0)
- モデル側のバリデーション
validates :point, numericality: { greater_than_or_equal_to: 0 }
利用- User.point >= Sale.used_point
- Falseとなる操作は悪意しかないので、トランザクション外のif文で
実装自体
User.point、Sale.used_point、Payment.added_pointは全て0以上(>=0)
それぞれのモデル.rbvalidates :point, numericality: { greater_than_or_equal_to: 0 } # User validates :used_point, numericality: { greater_than_or_equal_to: 0 } # Sale validates :added_point, numericality: { greater_than_or_equal_to: 0 } # Paymentusers_controller.rbdef payment @user = 割愛 @sale = 割愛 @concert = 割愛 @payment = Payment.new(sale_id: @sale.id, date: Date.current) respond_to do |format| if current_user.point < params_used_point # 所持ポイントを超過している旨の警告文 (以下、ポイントをPと略す) else # 所持P範囲内で支払おうとしている場合 begin ActiveRecord::Base.transaction do if @sale.amount <= params_used_point # 使用Pが販売額を超えてる時。 @sale.update!(used_point: @sale.amount) @payment.update(amount: 0, added_point: 0) # 支払成功。決済額が0なので、追加Pもゼロ else @sale.update!(used_point: params_used_point) @payment.update!(amount: pay_amount, added_point: 追加P計算関数) end @user.update!(point: ユーザP更新関数) # 支払い完了と表示 end rescue StandardError => e # トランザクション失敗したら logger.error e logger.error e.backtrace.join("\n") @sale = 割愛 @concert = 割愛 format.html { render :confirm, notice: 'エラー' } end end end endまあ、駄目な部分もあると思う。が、まあ、
Githubセキュリティアラート
nokogiriに関するセキュリティアラートが来ていた。
nokogiri。。。Gemfileには書いてない。Gemfile.lockの方のみ。確か、Gemfile.lockには、Gemfileには書いてなくても依存関係にあるものは、書かれるのだから、今回はその他gemがnokogiriに依存しているのだろう。
今回は
bundle update
が適当だろう。
gem update
- gem コマンドは Gemfile や Gemfile.lock とは無関係に動作
- 被インストールgemについて,より新しいバージョンがあれば最新版をインストール
bundle update
- Gemfile と Gemfile.lock に基づいて動作
terminalbundle update nokogiri git add -i git commit -m 'update nokogiri' git push origin master'セキュリティアラート消えた。
- 投稿日:2019-08-29T12:51:48+09:00
#ruby でインタンス変数を削除・未定義状態にする ( remove_instance_variable :@some_variable )
Use #ruby to delete an instance variable and make it undefined (remove_instance_variable: @some_variable)
remove_instance_variable :@some_variable
例
[11] pry(main)> @a = :b => :b [12] pry(main)> defined? @a => "instance-variable" [13] pry(main)> remove_instance_variable :@a => :b [14] pry(main)> defined? @a => nilOriginal by Github issue
- 投稿日:2019-08-29T12:15:52+09:00
Rails6 のちょい足しな新機能を試す72(enum _scopes 編)
# はじめに
(多分)Rails 6 に追加された新機能を試す第72段。 今回は、
enum _scopes
編です。
Rails 6 では、 モデルでenum
を使うときに scope を定義しないようにできるオプション:_scopes
が追加されました。
Ruby 2.6.3, Rails 6.0.0.rc2 で確認しました。Rails 6.0.0.rc2 はgem install rails -v 6.0.0rc2 --prerelease
でインストールできます。(Rails6がリリースされましたが、動作確認当時は、Rails 6.0.0.rc2 が最新でした。悪しからず
)
$ rails --version Rails 6.0.0.rc2今回は、 Child モデルを作成して
rails console
を使って確認します。プロジェクトを作る
rails new rails_sandbox --database postgresql cd rails_sandboxChild モデルを作る
name と generation の2つの属性をもつ Child モデルを作ります。
generation は enum にするため、 integer にします。bin/rails g model Child name generation:integerenum を定義する
Child モデルに enum を定義します。
app/models/child.rbclass Child < ApplicationRecord enum generation: %i[baby toddler preschool gradeschool teen young_adult] endseed データを作成する
seed データを作成します。
db/seeds.rbChild.create(name: 'Andy', generation: :baby) Child.create(name: 'Bob', generation: :toddler) Child.create(name: 'Cindy', generation: :preschool)マイグレーションを実行し seed データを登録する
bin/rails db:create db:migrate db:seedrails console で確認する
rails console
で確認します。
Child.baby
を実行してみます。irb(main):001:0> Child.baby Child Load (0.4ms) SELECT "children".* FROM "children" WHERE "children"."generation" = $1 LIMIT $2 [["generation", 0], ["LIMIT", 11]] => #<ActiveRecord::Relation [#<Child id: 1, name: "Andy", generation: "baby", created_at: "2019-08-04 08:57:11", updated_at: "2019-08-04 08:57:11">]>ちゃんと動作しています。
Child.not_baby
を実行してみます。irb(main):002:0> Child.not_baby Child Load (0.6ms) SELECT "children".* FROM "children" WHERE "children"."generation" != $1 LIMIT $2 [["generation", 0], ["LIMIT", 11]] => #<ActiveRecord::Relation [#<Child id: 2, name: "Bob", generation: "toddler", created_at: "2019-08-04 08:57:11", updated_at: "2019-08-04 08:57:11">, #<Child id: 3, name: "Cindy", generation: "preschool", created_at: "2019-08-04 08:57:11", updated_at: "2019-08-04 08:57:11">]>こちらもちゃんと動作しました。
_scopes を使う
ここからが本番です。 Child モデルを変更して _scopes を使ってみます。
app/models/child.rbclass Child < ApplicationRecord enum generation: %i[baby toddler preschool gradeschool teen young_adult], _scopes: false endrails console で確認する
修正を反映させるため
reload!
します。irb(main):003:0> reload! Reloading... => true
Child.baby
,Child.not_baby
を試すとNoMethodError
が発生します。irb(main):004:0> Child.baby Traceback (most recent call last): 1: from (irb):4 NoMethodError (undefined method `baby' for #<Class:0x0000555a2cfb4808>) irb(main):005:0> Child.not_baby Traceback (most recent call last): 2: from (irb):5 1: from (irb):5:in `rescue in irb_binding' NoMethodError (undefined method `not_baby' for #<Class:0x0000555a2cfb4808>)scope が定義されないが enum は使える
scope は定義されませんが、 それ以外の enum の機能は使えます。
Child オブジェクトの作成
irb(main):008:0> dave = Child.new(name: 'Dave', generation: :teen) => #<Child id: nil, name: "Dave", generation: "teen", created_at: nil, updated_at: nil>
baby?
メソッド やteen?
メソッドirb(main):009:0> dave.baby? => false irb(main):010:0> dave.teen? => trueデータベースへの保存
irb(main):011:0> dave.save (0.2ms) BEGIN Child Create (0.4ms) INSERT INTO "children" ("name", "generation", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "Dave"], ["generation", 4], ["created_at", "2019-08-04 09:15:05.113455"], ["updated_at", "2019-08-04 09:15:05.113455"]] (7.1ms) COMMIT => trueデータベースから検索して、 dave の generation を確認
irb(main):012:0> dave = Child.last Child Load (0.6ms) SELECT "children".* FROM "children" ORDER BY "children"."id" DESC LIMIT $1 [["LIMIT", 1]] => #<Child id: 4, name: "Dave", generation: "teen", created_at: "2019-08-04 09:15:05", updated_at: "2019-08-04 09:15:05"> irb(main):013:0> dave.generation => "teen"試したソース
試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try072_enum_disable_scopes参考情報
- 投稿日:2019-08-29T11:10:26+09:00
ActiveRecord == GORM
- 投稿日:2019-08-29T10:58:25+09:00
rails console 上で実行時間を計測する
Rails Tutorial 5日目。
現在第8章を勉強中。知りたいこと
find_by と find_by_x ってどっちが速いの?
nameを使ってユーザーオブジェクトを検索してみてください。また、 find_by_nameメソッドが使えることも確認してみてください (古いRailsアプリケーションでは、古いタイプのfind_byをよく見かけることでしょう)。
find_by が古いタイプと言いながら、このあとの演習でも find_by がたくさん出てくるから、やっぱり古くないのではないか???(互換性があるから update されてないだけ?)追記:@scivola さんがコメントで勘違いを指摘してくださいました。ありがとうございます!
古いタイプの find_by == find_by_x ということだったのか~!やりたいこと
find_by と find_by_x ってどっちが速いのか実行時間を計測してみたい。
ほかに知らないこと
関数定義
rails_consoledef calc_find_by(count:, id:) # SQL の出力を抑える old_logger = ActiveRecord::Base.logger ActiveRecord::Base.logger = nil # Benchmark モジュールを使う Benchmark.bm 10 do |r| # find_by(id: id) を計測 # 結果出力時のタイトル設定 r.report "find_by" do count.times do User.find_by(id: id) end end # find_by_id(id) を計測 r.report "find_by_id" do count.times do User.find_by_id(id) end end end # SQL 出力抑制を元に戻す ActiveRecord::Base.logger = old_logger end実行結果
id : 3 は nil。
10000回>> calc_find_by count:10000, id: 3 user system total real find_by 0.956429 0.092523 1.048952 ( 1.050382) find_by_id 0.957591 0.100028 1.057619 ( 1.059075)100000回>> calc_find_by count:100000, id: 3 user system total real find_by 9.854176 0.629422 10.483598 ( 10.498670) find_by_id 9.824362 0.667503 10.491865 ( 10.506455)id : 2 は nil ではない。
10000回>> calc_find_by count:10000, id: 2 user system total real find_by 1.278680 0.063538 1.342218 ( 1.344218) find_by_id 1.267526 0.059938 1.327464 ( 1.328699)100000回>> calc_find_by count:100000, id: 2 user system total real find_by 12.492160 0.747277 13.239437 ( 13.251859) find_by_id 12.435650 0.863175 13.298825 ( 13.311519)そこまで大きく差が出るほど早さに影響しているわけではなさそう。
↓の通り、SQL 文は一緒だもんね。>> User.find_by(id: 3) User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]] => nil >> User.find_by_id(3) User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]] => nil
- 投稿日:2019-08-29T10:51:18+09:00
イナズマ ワーク4日目
correct_num = rand(10) trial_number = 1 while true puts "0~9の数字を当ててください" input = gets.to_i if correct_num > input puts "もっと大きな数です" trial_number += 1 elsif correct_num < input puts "もっと小さな数です" trial_number += 1 else puts "正解です" puts "#{trial_number}回で当たりました!" break end end
- 投稿日:2019-08-29T10:25:59+09:00
インクリメンタルサーチ+複数語検索を作る
Ruby 2.5.1
Rails 5.2.3今回作るもの
インクリメンタルサーチ + 複数語検索
スペースがある場合複数のキーワードで検索することができ、後の方の検索語でヒットするものから上部に表示します。
画面がチカチカしないように、ゆっくり表示します。画像は現在作っている、色の図鑑みたいなアプロケーションです。
検索が好きな方、インクリメンタルサーチをつけたい方のお役に立てば幸いです。
Gyazo.gifの無料枠で3つ検索するの撮り直しにコード書くよりも時間がかかっています。
ビューの準備
入力フォームと、検索結果を表示するための箱を用意してください。
フォームに入力された値を送って、帰ってきた結果を表示
$(function(){ $(".入力欄のクラス").on("keyup",function(){ var input = $(".入力欄のクラス名").val(); $(".検索結果表示部分のクラス").empty(); //非同期通信で、フォームのキーワードを送ります。 $.ajax({ type: 'GET', url: '/search/new', data: { keyword: input }, dataType: 'json', }) .done(function(colors){ //検索結果をcolorsという名前の配列として受け取ります。もし、検索結果がある場合は以下の処理を行います。 if (colors.length !== 0) { $('.検索結果表示部分のクラス').empty(); //検索結果として帰ってきた配列それぞれに対して以下の処理を行います。 colors.forEach(function(color){ //検索結果として表示させたいHTMLを記述してください。 var html = `<div class="result"> <div class="result__text"> ${color.name} <br> (${color.red},${color.green},${color.blue}) </div> <a class="result__cell" style="background-color:rgb(${color.red},${color.green},${color.blue});" href=""></a> </div>` //HTMLを検索結果表示部分に差し込みます。(画面がチカチカするの防ぐため、200ミリ秒でフェードインしながら) $('.検索結果表示部分のクラス').append(html).hide().fadeIn(200); ; }); } else { //検索した結果、0件だった場合 $(".検索結果表示部分のクラス").empty(); //検索結果がないことを示すHTMLを記述します。 var html = `<div class="result"> <div class="result__text"> お探しの色は見つかりませんでした </div> <a class="result__cell" href="/color/new"></a> </div>` $('.検索結果表示部分のクラス').append(html); } }) }); });コントローラ
def new #フォームから送られてきたキーワードをスペースで区切って配列に keywords = params[:keyword]&.split(/[[:blank:]]+/) #検索結果を入れるための空の配列を用意 @colors = [] #複数に分割したキーワードで検索 keywords.each do |keyword| next if keyword == "" #キーワードが空であれば、すぐ次の検索をかける #nameのカラムから探す @colors += Color.where('name LIKE(?)', "%#{keyword}%") end #重複するものがあれば消去 @colors.uniq! #後の検索語でヒットしたものから、表示配列の順番をひっくり返す @colors.reverse! respond_to do |format| format.html format.json end endインクリメンタルサーチは、せっかちさんのための検索です。きっと複数語検索をかけているということは、1語目の検索結果に満足していないということです。ですので、リバースをかけることにしました。
*別途、jbuilderは用意する必要があります。
!について
!をつけると破壊的なメソッドになるという説明がありますが、どういうことでしょうか。簡単な例で実験してみましょう。
下記の例でjoinは配列の出力時の改行を消すために付記したものです。#!なしの場合 numbers = [1,2,3,4,5] puts numbers.reverse.join #=> 54321 puts numbers.join #=> 12345 #!の場合 numbers = [1,2,3,4,5] puts numbers.reverse!.join #=> 54321 puts numbers.join #=> 54321今回の場合ですと、!がついたreverseの場合はレシーバ(.の左側)自体に変更が及んでいます。イメージとしては下記のような形です。
numbers = [1,2,3,4,5] numbers = numbers.reverse puts numbers.join #=> 54321!の有無でどのような違いになるのか、勘違いしてしまうと大変なので、使用する場合にはそれぞれのメソッドのリファレンスをご覧になることをおすすめします。
終わりに
検索フォーム適当に作って、ページ遷移させて適当に値引っ張ってくるだけで終わることもできるのですが、色々と工夫ができます。こういうの考えている時ってサーバーサイド楽しいな〜と感じます。是非是非、色々遊んでみてください!
ありがとうございました!SQLの軽量化や正規表現について
非常に有意義なコメントをいただいたので是非お読みになってください!
せっかくなので本文はそのまま改善の余地のある見本の形で残しておくことにします!
- 投稿日:2019-08-29T06:49:15+09:00
Nuxt.js + GraphQL + Ruby on Railsで作ったアプリにJWT認証を追加する方法
本記事ではフロントエンドに Nuxt.js(Vue.js)、バックエンドに Ruby on Rails 、APIに GraphQL を採用したアプリケーションに、JWTトークンによる認証 を追加する方法についてまとめます。
題材
サンプルとして以下チュートリアルで作成したToDoリストを使用します。
Nuxt.js + GraphQL + Ruby on Railsで作るToDoアプリチュートリアル(前編)
Nuxt.js + GraphQL + Ruby on Railsで作るToDoアプリチュートリアル(後編)JWTでの認証フロー
今回実装するJWTでの認証フローを図にまとめました。
トークンの発行・保存・付与・検証がめんどくさそうに見えるかもしれませんが、フロントエンド側はAuth Moduleが、バックエンド側はknockがトークンをいい感じに処理してくれるので、安心してください。
実装(バックエンド)
ユーザモデルを定義する
認証単位となるモデル(User)を生成します。
$ bundle exec rails g model user email:string password_digest:string $ bundle exec rails db:migrateUserはパスワードをハッシュ化して管理するので、モデルに
has_secure_password
を宣言します。app/models/user.rbclass User < ApplicationRecord has_secure_password endまた、Gemfileの
bcrypt
のコメントを外します。Gemfile# ・・・中略・・・ # Use Active Model has_secure_password gem 'bcrypt', '~> 3.1.7 # ・・・中略・・・テスト用のUserを
seed.rb
に追記します。db/seeds.rb# ・・・中略・・・ User.create( email: 'test@example.com', password: 'xxxxxxxx', password_confirmation: 'xxxxxxxx' )$ bundle exec rails db:seedknockをインストールする
Gemfileにknockを追加し、インストールします。
Gemfilegem 'rack-cors' gem 'graphql' gem 'knock' # ★追加$ bundle installRails6だとknockのautoloadに失敗するのでinitializerで明示的にrequireします。
config/initializers/eager_load_knock.rbrequire 'knock/version' require 'knock/authenticable'ジェネレータを実行します。
$ rails generate knock:install
ログイン/ログアウトのエンドポイントを準備
ジェネレータでControllerを生成します。
$ rails generate knock:token_controller user
各Controllerにて認証処理を行えるようにするため、
ApplicationController
にてmoduleをincludeします。app/controllers/application_controller.rbclass ApplicationController < ActionController::API include Knock::Authenticable end
GraphqlController
のbefore_actionとして認証処理を追加します。app/controllers/graphql_controller.rbclass GraphqlController < ApplicationController before_action :authenticate_user # ・・・中略・・・ end今回、セッションは使わないのとCORS設定済みであることを考慮して、CSRF対策を解除します。
config/application.rb# ・・・中略・・・ module RailsNuxtGrapshqlTodoapp class Application < Rails::Application config.load_defaults 6.0 config.api_only = true config.action_controller.default_protect_from_forgery = false end end動作確認(バックエンド)
サーバ起動してInsomniaを使ってリクエストを送信してみます。
$ bundle exec rails s未認証の状態でGraphQL Queryを送信しても、
401
が返ってきます。
/user_token
へリクエストすると、JWTトークンが返ってきます。Bearerの設定でTOKEN欄に上記JWTトークンを記載します。
この状態で再度GraphQL Queryを送信すると、
200
が返ってきました。
JWT認証が機能していますね。実装(フロントエンド)
Auth Moduleをインストールする
npmでインストールします。
Auth Moduleはstore/index.js
が存在していないとエラーを出すので、空のファイルを作成しておきます。$ npm install @nuxtjs/auth @nuxtjs/axios $ touch store/index.js
nuxt.config.js
のmodules
,axios
,auth
,apollo
を追記します。// ・・・中略・・・ modules: [ '@nuxtjs/vuetify', '@nuxtjs/pwa', '@nuxtjs/eslint-module', '@nuxtjs/apollo', '@nuxtjs/axios', '@nuxtjs/auth' ], axios: { baseURL: 'http://localhost:3000/' }, auth: { strategies: { local: { endpoints: { login: { url: 'user_token', method: 'post', propertyName: 'jwt' }, user: false, logout: false } } } }, // ・・・中略・・・ apollo: { clientConfigs: { default: { httpEndpoint: 'http://localhost:3000/graphql', getAuth: () => '' } } } // ・・・中略・・・ログイン画面を準備
/login
に相当する画面およびログイン処理を実装します。pages/login.vue<template> <v-container> <v-row> <v-col cols="6" offset="3"> <v-card> <v-card-title>Login</v-card-title> <v-card-text> <v-form> <v-container> <v-row> <v-col cols="6"> <v-text-field v-model="email" label="Email" required /> </v-col> </v-row> <v-row> <v-col cols="6"> <v-text-field v-model="password" type="password" label="Password" required /> </v-col> </v-row> <v-row> <v-col> <v-btn @click="login()"> Login </v-btn> </v-col> </v-row> </v-container> </v-form> </v-card-text> </v-card> </v-col> </v-row> </v-container> </template> <script> export default { middleware({ store, redirect }) { window.console.log(store.state.auth.loggedIn) if (store.state.auth.loggedIn) { return redirect('/') } }, data() { return { email: '', password: '' } }, methods: { async login() { try { await this.$auth.loginWith('local', { data: { auth: { email: this.email, password: this.password } } }) await this.$apolloHelpers.onLogin(this.$auth.getToken('local').match(/^Bearer[ ]+([^ ]+)[ ]*$/i)[1]) this.$router.push('/') } catch (e) { window.console.log(e) } } } } </script>無名middlewareを用いることで、既にlogin済みの状態でこのページを開くと、'/' でリダイレクトするようにしています。
methodsの
login()
がLOGINボタンを押したときの処理です。
Auth Moduleでログインをした後で、Apollo ModuleへJWTトークンをセットしています。ログアウトボタンを追加
ログイン中の場合のみ、メニューバーにログアウトボタンを表示します。
layouts/default.vue<template> <v-app> <v-app-bar app> <v-toolbar-title v-text="title" /> <div class="flex-grow-1" /> <span v-if="loggedIn" @click="logout()">Logout</span> </v-app-bar> <v-content> <nuxt /> </v-content> <v-footer center> <v-layout justify-center> <span>© 2019 Yuhei Okazaki. All Rights Reserved.</span> </v-layout> </v-footer> </v-app> </template> <script> export default { data() { return { title: 'Tasks' } }, computed: { loggedIn() { return this.$auth.loggedIn } }, methods: { async logout() { try { await this.$auth.logout() await this.$apolloHelpers.onLogout() this.$router.push('/login') } catch (e) { window.console.log(e) } } } } </script>methodsの
logout()
がLOGOUTを押したときの処理です。
Auth Moduleでログアウトをした後で、Apollo Moduleもログアウトしています。未認証時のリダイレクトを追加
このままだと、ログインしていない状態でもタスク一覧画面を開けてしまうので、ログインしていないときには
/login
へ飛ばすようmiddlewareを設定します。pages/index.vue// 中略 export default { middleware: 'auth' // 中略 }動作確認(全体)
冒頭の画像のように、ログインしたときのみタスク一覧が表示されます。
まとめ
本記事ではバックエンドにRuby on Rails、フロントエンドにNuxt.js、APIにGraphQLを採用したアプリケーションにJWT認証を追加しました。
新規登録画面やタスクとユーザの紐付け等、未実装の処理は多数あるものの、認証というアプリケーションを実装するときの最初の壁は越えられたかと思います。knockやAuth Module、Apollo Moduleを用いたことで、トークン操作を意識せず簡単に認証追加できたので、ぜひお試しください。
- 投稿日:2019-08-29T01:22:39+09:00
Ruby on Rails チュートリアル:第1章
この章でやったこと、学んだこと
- Ruby on Railsチュートリアルとは何か
- 開発の準備(環境構築やRailsのインストール等)
- Hello_appの作成
- Gitによるバージョン管理
この章の内容の中で、覚えておきたいこと
$ git checkout -b 新しいブランチ名
で簡単にブランチを作成してmasterブランチから切り替えることができる。$ git branch
でブランチの一覧と現在のブランチを確認できる。- 作成したブランチで作業が完了したら、
$ git commit -a -m "コミットするメッセージ"
を実行。- ファイルの変更が終わったら、
$ git checkout master
でmasterブランチに移動し、$ git merge ブランチ名
でmasterブランチにこの変更をマージ (merge) する。- 必須ではないが、使い終わったブランチを削除しておきたい場合は、
$ git branch -d 削除したいブランチ名
を実行する。この章を終えて感じたこと
Gitはある程度理解したつもりでいたが、Branchの使い方の理解が足りなかった。Git以外はほぼ理解できていたので、スラスラと進めることができた。第2章からも頑張りたいと思う。