20210604のRubyに関する記事は13件です。

find, find_by, whereの違いについて

「find, find_by, whereの違いについて」 ①「findメソッド」は主キーと呼ばれるidを指定した検索の仕方。 List.find(params[:id]) id以外のカラムを指定してデータを取得したい場合もあります。  そのような場合には「findメソッド」では解決できないので、別の「find_byメソッド」を使ってデータを取得しなければいけません。 ②find_by 主なイメージは、「主キー(id)以外のカラムを指定しても、見つかった1レコードを返せる」。 List.find_by(title: "ruby",body: "rails") 1.find_by()の()内に記述できるのは、数値(id)だけでなく、文字(id以外のカラム)も可能 2.id以外のカラムも指定できるので、複数のレコードが見つかる場合もありますが、その時は、一番最初に見つかったレコード1件を取得 3.検索に該当するデータがなかった場合、nilを返す 3.where 主なイメージは、「主キー(id)以外のカラムも指定できる、且つ、複数のレコードも返せる」 List.where(title: "ruby",body: "rails") 1.where()の()内に記述できるのは、数値(id)だけでなく、文字(id以外のカラム)も可能 2.該当する複数のレコードを取得 3.検索に該当するデータがなかった場合、空の配列を返す 以上、find, find_by, whereの違いについてでした
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル(第6版) 第11章 アカウントの有効化

第11章 現時点のアプリケーションでは、新規登録したユーザーが最初からすべての機能にアクセスできるようになっている。 今回は、アカウント有効化を差し込み、メールアドレスが持ち主かどうかを確認する。 流れ ①有効化トークンやダイジェストに関連付けた状態にする ②有効化トークンを含めたリンクをユーザーにメール送信 ③ユーザーがそのリンクをクリックすると有効化できるようにする アカウント有効化のステップ 1.ユーザーの初期状態は「有効化されていない」(unactivated)にしておく。 2.ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する。 3.有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく3 。 4.ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。 5.ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」(activated)に変更する。 以前やったパスワードや記憶トークンの仕組みと似ている。 検索キー string digest authentication email password password_digest authenticate(password) id remember_token remember_digest authenticated?(:remember, token) email activation_token activation_digest authenticated?(:activation, token) email reset_token reset_digest authenticated?(:reset, token) 参照:railsチュートリアル AccountActivationsリソース 第8章でやったセッション機能を使い、アカウントの有効化作業を「リソース」としてモデル化する。(Active Recordのモデルとは関係ないため、両者を関連付けしない。代わりに、必要なデータをUserモデルに追加する) 有効化用のリンクにアクセスし有効化のステータスを変更するなら、RESTに準拠すればPATCHリクエストとupdateアクションになるべき。 しかし、有効化リンクはめーづで送られ、リンクをクリック= =ブラウザでクリックした時と同じであり、この場合ブラウザから発行されるのはGETリクエストになる。 なので、ユーザーからのGETリクエストを受けるため、updateのところeditアクションに変更する。 AccountActivationsコントローラ AccountActivationsコントローラーを生成する $ rails generate controller AccountActivations editアクションへの名前付きルートを扱うため、ルーティングにアカウント有効化用のresources行を追加する。 config/routes.rb resources :account_activations, only: [:edit] HTTPリクエスト URL Action 名前付きルート GET /account_activation/トークン/edit edit edit_account_activation_url(token) 参照:railsチュートリアル これで名前付きルートの edit_account_activation_url(activation_token, ...) が使えるようになった。 AccountActivationのデータモデル 有効化のメールには一意の有効化トークンが必要。 一瞬、送信メールと同じ文字列をデータベースに保管しておけばよくねと思うが、万が一データベースの内容が漏れた時大変なことになる。 新規登録されたユーザーアカウントのトークンを盗み、不正にそのトークンを使われてしまう危険性がある。 なので、仮想的な属性を使ってハッシュ化した文字列をデータベースに保存し、次のように仮想属性の有効化トークンにアクセス user.activation_token 下のようなコードでユーザー認証できるようになる。 user.authenticated?(:activation, token) 参照:railsチュートリアル 上の画像のようにデータモデルを追加するので、マイグレーションを実行する。 $ rails generate migration add_activation_to_users activation_digest:string activated:boolean activated_at:datetime admin属性と同様にactivated属性のデフォルト論理値をfalseにする。 db/migrate/[timestamp]_add_activation_to_users.rb add_column :users, :activated, :boolean, default: false あとはマイグレートする $ rails db:migrate Activationトークンのコールバック 以前メールアドレスを保存する前に、メールアドレスを小文字に変換するとかでbefore_saveコールバックを使った。before_saveは、オブジェクトが保存される直前、オブジェクト作成時や更新時にコールバックが呼び出される。 今回は、オブジェクトが生成された時だけ、コールバックを呼び出したいのでbefore_createコールバックが必要になる。 before_create :create_activation_digest このコードはメソッド参照と呼ばれ、Railsはcreate_activation_digestメソッドを探して、ユーザー作成前に実行する。 create_activation_digestメソッドはUserモデル内でしか使わないので、privateキーワードを指定する。 private def create_activation_digest # 有効化トークンとダイジェストを作成および代入する end before_createコールバックの目的は、トークンとそれに対応するダイジェストを割り当てるため。割り当ては下のように self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) 記憶トークンや記憶ダイジェストのために作ったメソッドを使いまわしている。 ここでrememberメソッドと見比べる # 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end update_attributeの使い方に違いがあるが、記憶トークンやダイジェストは既にデータベースにいるユーザーのために作成されるのに対し、before_createコールバックはユーザーが作成される前に呼び出される。 User.newで新しいユーザーが定義されると、activation_token属性やactivation_digest属性が得られる。 後者のactivation_token属性は既にデータベースとの関連付けが出来上がっているため、ユーザーが保存される時に同時に保存される。 Userモデルに実装するとこんな感じ app/models/user.rb attr_accessor :remember_token, :activation_token before_save :downcase_email before_create :create_activation_digest private # メールアドレスをすべて小文字にする def downcase_email self.email = email.downcase end # 有効化トークンとダイジェストを作成および代入する def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end サンプルユーザーの生成とテスト サンプルデータとfixtureを更新する。 サンプルとユーザーを事前に有効化しておく。 db/seeds.rb # メインのサンプルユーザーを1人作成する User.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", admin: true, activated: true, activated_at: Time.zone.now) # 追加のユーザーをまとめて生成する 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password, activated: true, activated_at: Time.zone.now) test/fixtures/users.yml michael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %> admin: true activated: true activated_at: <%= Time.zone.now %> archer: name: Sterling Archer email: duchess@example.gov password_digest: <%= User.digest('password') %> activated: true activated_at: <%= Time.zone.now %> lana: name: Lana Kane email: hands@example.gov password_digest: <%= User.digest('password') %> activated: true activated_at: <%= Time.zone.now %> malory: name: Malory Archer email: boss@example.gov password_digest: <%= User.digest('password') %> activated: true activated_at: <%= Time.zone.now %> <% 30.times do |n| %> user_<%= n %>: name: <%= "User #{n}" %> email: <%= "user-#{n}@example.com" %> password_digest: <%= User.digest('password') %> activated: true activated_at: <%= Time.zone.now %> <% end %> データベースを初期化&サンプルデータを再度生成し、変更を反映する。 $ rails db:migrate:reset $ rails db:seed アカウント有効化のメール送信 Action Mailerライブラリを使って、Userのメイラーを追加する。 これをUsersコントローラのcreateアクションで有効化リンクをメール送信するために使う。 メイラー構成は、コントローラのアクションと似てて、メールテンプレートをビューと同じ要領で定義できる。ここに、有効化トークンとメールアドレスのリンクを含める。 送信メールのテンプレート メイラーは、rails generateで生成する。 $ rails generate mailer UserMailer account_activation password_reset これで、account_activationメソッドとpassword_resetメソッドの原型ができた。 生成したメイラーごとに、ビューのテンプレートは2つずつ生成され、1つはテキスト用メールのテンプレート、2つ目はHTMLメール用のテンプレートになる。 テキストビュー app/views/user_mailer/account_activation.text.erb UserMailer#account_activation <%= @greeting %>, find me in app/views/user_mailer/account_activation.text.erb app/views/user_mailer/account_activation.html.erb <h1>UserMailer#account_activation</h1> <p> <%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb </p> 生成されたメイラーの動作を解説する。 app/mailers/application_mailer.rb class ApplicationMailer < ActionMailer::Base default from: "from@example.com" layout 'mailer' end デフォルトのfromアドレスがあり、メールのフォーマットに対応するメイラーレイアウトが使われている。 app/mailers/user_mailer.rb class UserMailer < ApplicationMailer def account_activation @greeting = "Hi" mail to: "to@example.org" end def password_reset @greeting = "Hi" mail to: "to@example.org" end end @greetingというインスタンス変数があり、ビューでコントローラのインスタンス変数を利用できるのと同じで、メイラービューで利用できる。 fromアドレスのデフォルト値を更新する。 default from: "from@example.com" から default from: "noreply@example.com" にする。 アカウント有効化リンクをメール送信する。 app/mailers/user_mailer.rb def account_activation(user) @user = user mail to: user.email, subject: "Account activation" end この時点でaccount_activationに引数を与えたので、テストはパスしない。 メールには、挨拶文にユーザー名とカスタムの有効化リンクを追加する。 流れとしては、Railsサーバーでユーザーをメールアドレス検索し、有効化トークンを認証できるようにするので、リンクにメールアドレスとトークンを両方含める。 edit_user_url(user) は https://www.example.com/users/1/edit となる。(urlは絶対パス) これに対応するアカウント有効化リンクのURLはこんな感じになる。 https://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit ハッシュの部分はnew_tokenメソッドで生成されたもので、Base64でエンコードされている。 ハッシュの部分は、/users/1/editの「1」のユーザーIDと同じ役割になる。 このトークンは、AccountActivationsコントローラのeditアクションでは、paramsハッシュでparams[:id]として参照可能。 クエリパラメータ クエリパラメータを使用し、URLにメールアドレスを組み込める。 クエリパラメータは、URLの末尾に疑問符「?」に続けてキーと値のペアを記述する。 account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com メールアドレスの「@」記号はURLにおいて「%40」となる。 これはエスケープという手法で、URLで扱えない文字を扱えるようにするためのもの。 クエリパラメータを設定するのに、名前付きルートに対して次のハッシュを追加する。 edit_account_activation_url(@user.activation_token, email: @user.email) 名前付きルートでクエリパラメータを定義すれば、Railsが特殊文字を自動でエスケープする。 コントローラーでparams[:email]とすれば、自動的にエスケープを解除する。 (頭がいい) アカウント有効化のテキストビューとHTMLビュー テキストビュー↓ app/views/user_mailer/account_activation.text.erb Hi <%= @user.name %>, Welcome to the Sample App! Click on the link below to activate your account: <%= edit_account_activation_url(@user.activation_token, email: @user.email) %> HTMLビュー↓ app/views/user_mailer/account_activation.html.erb <h1>Sample App</h1> <p>Hi <%= @user.name %>,</p> <p> Welcome to the Sample App! Click on the link below to activate your account: </p> <%= link_to "Activate", edit_account_activation_url(@user.activation_token, email: @user.email) %> 送信メールのプレビュー テンプレートを作ったので、メールプレビューをやってみる。 これは、メールを実際に送信しなくても、その場でプレビューすることができる。 config/environments/development.rb config.action_mailer.raise_delivery_errors = false host = 'example.com' # ここをコピペすると失敗します。自分の環境のホストに変えてください。 # クラウドIDEの場合は以下をお使いください config.action_mailer.default_url_options = { host: host, protocol: 'https' } # localhostで開発している場合は以下をお使いください # config.action_mailer.default_url_options = { host: host, protocol: 'http' } 今回はcloud9なので、ホスト名は自分のcloud9のURLにすればOK 続いて、Userメイラープレビューファイルの更新が必要。 account_activationの引数に有効なUserオブジェクトを渡すため、user変数が開発用データベースの最初のユーザーになるよう定義する。 それをUserMailer.account_activationの引数として渡す。 test/mailers/previews/user_mailer_preview.rb class UserMailerPreview < ActionMailer::Preview def account_activation user = User.first user.activation_token = User.new_token UserMailer.account_activation(user) end end このときに、user.activation_tokenの値にも代入している。理由としてテンプレートでは、アカウント有効化のトークンが必要なので、代入している。また、activation_tokenは仮の属性なので、データベースのユーザーはこの値を持っていない。 プレビューがみれた。 プレビューを見る際には、URLの末尾に「/rails/mailers/user_mailer/account_activation」を付ければOK 送信メールのテスト メールプレビューのテストを作成し、プレビューをダブルチェックする。 Rialsによってテスト例が自動生成されてるので、これを利用する。 test/mailers/user_mailer_test.rb require 'test_helper' class UserMailerTest < ActionMailer::TestCase test "account_activation" do mail = UserMailer.account_activation assert_equal "Account activation", mail.subject assert_equal ["to@example.org"], mail.to assert_equal ["from@example.com"], mail.from assert_match "Hi", mail.body.encoded end test "password_reset" do mail = UserMailer.password_reset assert_equal "Password reset", mail.subject assert_equal ["to@example.org"], mail.to assert_equal ["from@example.com"], mail.from assert_match "Hi", mail.body.encoded end end だが、この時点でテストは失敗する。 テストでは、assert_matchという強力なメソッドが使われている。 下の方法だと正規表現で文字列をテストできる。 assert_match 'foo', 'foobar' # true assert_match 'baz', 'foobar' # false assert_match /\w+/, 'foobar' # true assert_match /\w+/, '$#!*+@' # false test/mailers/user_mailer_test.rb require 'test_helper' class UserMailerTest < ActionMailer::TestCase test "account_activation" do user = users(:michael) user.activation_token = User.new_token mail = UserMailer.account_activation(user) assert_equal "Account activation", mail.subject assert_equal [user.email], mail.to assert_equal ["noreply@example.com"], mail.from assert_match user.name, mail.body.encoded assert_match user.activation_token, mail.body.encoded assert_match CGI.escape(user.email), mail.body.encoded end end 上のテストでは、assert_matchメソッドで、名前、有効化トークン、エスケープ済メールアドレスがメール本文にあるかみている。 CGI.escape(user.email) というのを使えばテストユーザーのメールアドレスをエスケープできる。 テストをパスさせるため、ドメイン名を正しく設定する。 config/environments/test.rb config.action_mailer.default_url_options = { host: 'example.com' } これでテストは成功する。 ユーザーのcreateアクションを更新 createアクションにユーザー登録を行うコードを数行追加すれば、メイラーをアプリケーションで使える。 app/controllers/users_controller.rb class UsersController < ApplicationController def create @user = User.new(user_params) if @user.save UserMailer.account_activation(@user).deliver_now flash[:info] = "Please check your email to activate your account." redirect_to root_url  else render 'new' end end end 変更前は、ユーザーのプロフィールページにリダイレクトしてたが、今はリダイレクト先をルートURLに変更している redirect_to @userからredirect_to root_urlに変更。 リダイレクト先の変更とユーザーは以前のようにログインしないようになったので 、アプリケーションは正しく動作してもテストは失敗する。 一旦テストはコメントアウトする。 test/integration/users_signup_test.rb # assert_template 'users/show' # assert is_logged_in? 実際にサインアップでユーザーを登録して、サーバーログを確認すればメールの例が出てくる。 登録後リダイレクトされ、ホームページににアカウント有効化の確認メッセ―ジが出ている。 アカウントを有効化する やること ・AccountActivationsコントローラのeditアクションを書く。 ・アクションへのテストを書く ・テスト確認後、AccountActivationsコントローラからUserモデルにコードを移す(リファクタリング) authenticated?メソッドの抽象化 有効化トークンとメールはそれぞれ ・params[:id] ・params[:email] で参照できる。 ユーザーを検索して認証は以下のコードだった。 user = User.find_by(email: params[:email]) if user && user.authenticated?(:activation, params[:id]) authenticated?メソッドは、アカウント有効化のダイジェストと、渡されたトークンが一致するかどうかをチェックしている。 しかし、これは記憶トークン用なので、今は動作しない。 # トークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end Userモデル属性のremember_digestは、self.remember_digestに書き換えられる。 rememberの箇所を変数にして扱いたい。 理由として、状況に応じて呼び出すメソッドを切り替えたいから。 self.FOOBAR_digestのように これを実装するには、受け取ったパラメータに応じて呼び出すメソッドを切り替える方法を取る。 これは、「メタプログラミング」と呼ばれている。これは、プログラムでプログラムを作成するということ。 ここで重要なことは、sendメソッドの機能。 これは、渡されたオブジェクトに対しメッセージを送ることで、呼び出すメソッドを動的に決めることが可能。 例↓ $ rails console >> a = [1, 2, 3] >> a.length => 3 >> a.send(:length) => 3 >> a.send("length") => 3 sendを通して渡したシンボル(:length)や文字列("length)は、どちらもlengthメソッドと同じ結果になった。 どちらもオブジェクトにlengthメソッドを渡しているので、等価になる。 他の例↓ >> user = User.first >> user.activation_digest => "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae" >> user.send(:activation_digest) => "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae" >> user.send("activation_digest") => "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae" >> attribute = :activation >> user.send("#{attribute}_digest") => "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae" ①シンボル:activationと等しいattribute変数を定義 ②文字列の式展開を使い、引数を組み立ててsendに渡す。 このステップでも同じ。 また、Rubyではシンボルの方が一般的に使われる。 なお、シンボルと文字列どちらでも上のようなコードは文字列に変換される。 "activation_digest" →シンボルは式展開されると文字列になる。 authenticated?メソッドを書き換える。 def authenticated?(remember_token) digest = self.send("remember_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(remember_token) end ①各引数を一般化 ②文字列の式展開も利用 結果↓ def authenticated?(attribute, token) digest = self.send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end 2番目の引数tokenの名前を一般化した。 また、このコードはモデル内の為selfは省略可能 結果↓ def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end これで、呼び出す時は、下のように呼び出せば従来の振る舞いができる。 user.authenticated?(:remember, remember_token) Userモデルに実装するとこんな感じ app/models/user.rb class User < ApplicationRecord . . . # トークンがダイジェストと一致したらtrueを返す def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end . . . end この時点ではテストが失敗する。 理由は、current_userメソッドとnilダイジェストのテストの両方において、authenticated?が古いまま。 現時点で、引数は2つではなく1つの状態になっている。 current_user内のauthenticated?メソッドを抽象化 app/helpers/sessions_helper.rb # 現在ログイン中のユーザーを返す(いる場合) def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(:remember, cookies[:remember_token]) log_in user @current_user = user end end end Userテスト内のauthenticated?メソッドを抽象化 test/models/user_test.rb test "authenticated? should return false for a user with nil digest" do assert_not @user.authenticated?(:remember, '') end これでテストは成功する。 editアクションで有効化 authenticated?メソッドを抽象化できたので、これでやっとこさeditアクションを書ける。 editアクションの処理 ・paramsハッシュで渡されたメールアドレスに対応するユーザーを認証する。 ユーザーが有効化どうかの確認は↓ if user && !user.activated? && user.authenticated?(:activation, params[:id]) !user.activated?に関して、論理値を追加している。これは、既に有効になっているユーザーを再度有効化しないために必要な物。 もしこのコードがなければ、攻撃者がユーザーの有効化リンクを後から盗み出してクリックするだけで、本当のユーザーとしてログインできてしまう。 上の論理値に基づきユーザー認証を行うには、ユーザー認証後にactivated_atタイムスタンプを更新する。 user.update_attribute(:activated, true) user.update_attribute(:activated_at, Time.zone.now) このコードをeditアクションに入れる。 app/controllers/account_activations_controller.rb class AccountActivationsController < ApplicationController def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.update_attribute(:activated, true) user.update_attribute(:activated_at, Time.zone.now) log_in user flash[:success] = "Account activated!" redirect_to user else flash[:danger] = "Invalid activation link" redirect_to root_url end end end もし仮にトークンが無効だった場合には、ルートURLにリダイレクトされる。 しかしこの時点では、ユーザーのログイン方法を変更してないので、ユーザー有効化には意味がない。 ユーザーが有効である場合にのみログインできるようにする。 これを実装するには、user.activated?がtrueの場合にのみログインを許可、そうでなければルートURLにリダイレクトさせ警告を表示する。 app/controllers/sessions_controller.rb def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) if user.activated? log_in user params[:session][:remember_me] == '1' ? remember(user) : forget(user) redirect_back_or user else message = "Account not activated. " message += "Check your email for the activation link." flash[:warning] = message redirect_to root_url end else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end 登録した際のメール(サーバーログ)に <a href="https://fc20e7ef873e419a9249c4e46d05a152.vfs.cloud9.us-east-2.amazonaws.com/account_activations/eQyFGNJ9HGuy0Z3GYnNC7g/edit?email=4%40example.com">Activate</a> URLに有効化トークンがある。 上記のURLを開いた結果 改良点として、有効化されていないユーザーが表示されないようにする必要がある。これは後で行う。 演習 > user = User.find(108) User Load (16.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 108], ["LIMIT", 1]] => #<User id: 108, name: "aaa", email: "4@example.com", created_at: "2021-06-03 13:23:58", updated_at: "2021-06-03 13:24:07", password_digest: [FILTERED], remember_digest: nil, admin: false, activation_digest: "$2a$12$XZt0LzqR3yWf5Ce3OzymFe/dyvKfIdvwAx5qW5BQjRk...", activated: true, activated_at: "2021-06-03 13:24:07"> > user.activated => true 有効化のテストとリファクタリング アカウント有効化の統合テストを追加する。 第7章で書いたテストに若干の手を加える。 test/integration/users_signup_test.rb require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest def setup ActionMailer::Base.deliveries.clear end test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post users_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } end assert_template 'users/new' assert_select 'div#error_explanation' assert_select 'div.field_with_errors' end test "valid signup information with account activation" do get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } } end assert_equal 1, ActionMailer::Base.deliveries.size user = assigns(:user) assert_not user.activated? # 有効化していない状態でログインしてみる log_in_as(user) assert_not is_logged_in? # 有効化トークンが不正な場合 get edit_account_activation_path("invalid token", email: user.email) assert_not is_logged_in? # トークンは正しいがメールアドレスが無効な場合 get edit_account_activation_path(user.activation_token, email: 'wrong') assert_not is_logged_in? # 有効化トークンが正しい場合 get edit_account_activation_path(user.activation_token, email: user.email) assert user.reload.activated? follow_redirect! assert_template 'users/show' assert is_logged_in? end end テストは長いが今までやってきたことがほとんど。 また、重要な部分は下の一行 assert_equal 1, ActionMailer::Base.deliveries.size これは配信されたメッセージが1つかどうかを確認している。 配列deliberiesは変数の為、setupメソッドでこれを初期化しておかないと、他でもテストでメール配信された時にエラーが発生する。 assignsメソッドというものがある。 これは、assignsメソッドに対応するアクション内のインスタンス変数にアクセスできるようになる。 例えば、Usersコントローラーのcreateアクションでは@userというインスタンス変数が定義されており、テストでassigns(:user)と書けば、インスタンス変数にアクセスできるようになる。 この時点でテストはパスするので、ユーザー操作の一部をコントローラからモデルに移動するリファクタリングを行う。 手順 ・activateメソッドを作成、ユーザーの有効化属性を更新する ・send_activation_emailメソッドを作成、有効化メールを送信する。 Userモデルにユーザー有効化メソッドを追加する。 app/models/user.rb # アカウントを有効にする def activate update_attribute(:activated, true) update_attribute(:activated_at, Time.zone.now) end # 有効化用のメールを送信する def send_activation_email UserMailer.account_activation(self).deliver_now end user.という記法を使っていないが、Userモデルにそのような変数はない。これがあるとエラーになる。 また、userをselfに切り替えるという方法もあるが、selfはモデル内では必須ではない。そして、Userメイラー内の呼び出しでは、@userがselfに変更されている。 -user.update_attribute(:activated, true) -user.update_attribute(:activated_at, Time.zone.now) +update_attribute(:activated, true) +update_attribute(:activated_at, Time.zone.now) ユーザーモデルオブジェクトからメールを送信する。 app/controllers/users_controller.rb def create @user = User.new(user_params) if @user.save @user.send_activation_email flash[:info] = "Please check your email to activate your account." redirect_to root_url else render 'new' end end ユーザーモデルオブジェクト経由でアカウントを有効化する app/controllers/account_activations_controller.rb def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.activate log_in user flash[:success] = "Account activated!" redirect_to user else flash[:danger] = "Invalid activation link" redirect_to root_url end end 演習 activateメソッドでupdate_attributeを2回呼び出している。これは各行で1回ずつデータベースに問い合わせしているということになる。 これをupdate_columsに変更する。これは呼び出しを1回にできる。 # アカウントを有効にする def activate update_columns(activated: true, activated_at: Time.zone.now) end 現在/usersのユーザーindexページを開くとすべてのユーザーが表示される。なので、有効でないユーザーは非表示にする。 app/controllers/users_controller.rb def index @users = User.where(activated: true).paginate(page: params[:page]) # 演習で変更 # @users = User.paginate(page: params[:page]) end def show @user = User.find(params[:id]) redirect_to root_url and return unless @user.activated? end ここまでの演習課題で変更したコードをテストするために、/usersと/users/:idの両方に対する統合テストを作成 users.yml non_activated: name: Non Activated email: non_activated@example.gov password_digest: <%= User.digest('password') %> activated: false activated_at: <%= Time.zone.now %> users_controller_test.rb set @non_activated_user =users(:non_activated) end test "should not allow the not activated attribute" do log_in_as (@non_activated_user) assert_not @non_activated_user.activated? get users_path assert_select "a[href=?]", user_path(@non_activated_user), count: 0 get user_path(@non_activated_user) assert_redirected_to root_url end 本番環境でのメール送信 production環境で実際にメールを送信できるようにする。 手順 ・まず、無料のサービスを利用してメール送信の設定を行う ・アプリケーションの設定とデプロイを行う 本番環境からメールを送信するには、「Mailgun」というHerokuアドオンを利用しアカウント検証をする。 (Herokuのstarterというプランだとメール数が最大400通までOKらしい) Mailgunアドオンを使うには、production環境のSMTPに情報を記入する。 本番Webサイトのアドレスをhost変数に定義する。 config/environments/production.rb Rails.application.configure do . . . config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp host = '<あなたのHerokuサブドメイン名>.herokuapp.com' config.action_mailer.default_url_options = { host: host } ActionMailer::Base.smtp_settings = { :port => ENV['MAILGUN_SMTP_PORT'], :address => ENV['MAILGUN_SMTP_SERVER'], :user_name => ENV['MAILGUN_SMTP_LOGIN'], :password => ENV['MAILGUN_SMTP_PASSWORD'], :domain => host, :authentication => :plain, } . . . end Gitのトピックブランチをmasterにマージ $ rails test $ git add -A $ git commit -m "Add account activation" $ git checkout master $ git merge account-activation リモートリポジトリにプッシュ&Herokuにデプロイ $ rails test $ git push $ git push heroku $ heroku run rails db:migrate MailgunのHerokuアドオン追加コマンド $ heroku addons:create mailgun:starter Herokuの環境変数を表示したい場合には、下のコマンドを実行する。 $ heroku config:get MAILGUN_SMTP_LOGIN $ heroku config:get MAILGUN_SMTP_PASSWORD 注意事項 user_nameやpasswordといった情報は、絶対にソースコードに直接書かない事! そのような情報は環境変数「ENV」に設定し、アプリケーションに読み込ませるようにする。 今回は、そのような変数はMailgunのアドオンが自動的に設定しているが、第13章では環境変数を自分で設定することになる。 受信メールの認証コマンド。MailgunダッシュボードのURLが表示されるのでブラウザを開く。 $ heroku addons:open mailgun サインアップからアカウント作成を押してメール確認したら、、、 アカウントが有効になった。 最後に 本章はアカウント有効化を実装した。 WEBアプリケーションで大切な機能なので、他でも使えるように覚えていきたい。 第12章ではパスワード再設定機能を追加する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby 問題11 文字数を算出するプログラム

はじめに paizaの問題もDランクですが解けるようになってきました。 ここでのアウトプットの成果が見える形で現れてきました。 本日もアウトプットしていきます。 問題 対象となる文字列から特定の文字列の数を算出し、その数を出力するプログラムを実装してください。 対象となる文字列の中から,"na"という文字列の数を取得する 上記で取得した数を出力する def count_na(str) # 処理を記述 end # 呼び出し例(引数には対象となる文字列を指定します) count_na('bananaman nanase nanairo') ヒント scanメソッドを使用します   公式リファレンス ※scanメソッドは、対象の要素から引数を指定した文字列を数え、配列として返すメソッド "nanananinunenonani".scan("na") => ["na", "na", "na", "na"] 解答 def count_na(str) puts str.scan("na").length end count_na('bananaman nanase nanairo') => 6 count_naメソッドの仮引数に文字列が格納されています。 まずはscanメソッドde"na"という文字列だけ返してみます。 def count_na(str) puts str.scan("na") end count_na('bananaman nanase nanairo') => na na na na na na "na"の文字列が6個返す事が出来ました。 今回はその数だけを返したいので、lengthメソッドを使いました。 最後に 正規表現を使う場合がほとんどですが、今回は問題を解くために使いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rubyアルゴリズム_001]データの入出力(gets,puts)

getsメソッド Rubyのデータ入力を行えるメソッド。 ## 入力された値を変数dogに代入する。 ## 代入された値は「文字列」となる。末尾に「改行(\n)」がつく。 dog = gets ## 入力された値を変数dogに代入する。 ## chompメソッドをつけることで、末尾の「改行(\n)」を除ける。 dog = gets.chomp ## 入力された値を変数numberに代入する。 ## to_iメソッドをつけることで、「数値」として代入できる。 number = gets.to_i pustメソッド Rubyのデータ出力を行えるメソッド。 ## 変数dogに代入された値を出力する。 puts dog ## ダブルクォーテーションで囲った文字列を出力する。 puts "いぬ" ## 変数dogと文字列を組み合わせて出力する。 puts "#{dog}いぬ"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

4つの数字の組み合わせの和から最大値を求める[Ruby]

問題 1 から 9 までの数字の中から4つの数字を選択する。 4つの数字は同じ数値が重複する事もある。 4つの数字をあらゆる並べ方を試し、和の最大値(最大スコア)を求めます。 例えば、 1 の 2, 9, 3, 8 の 4 枚を使う場合、 以下の 12 通りの和の 175 が最大となる。 4つの数値の並べ方は 4! = 24 通り存在しますが、足し算は順序に依存しないため、12 通りのみ考慮すればよいことに注意してください。 たとえば、9, 2, 3, 8 の順で並べた場合のスコアは 92 + 38 = 130 となります。 例 ・92 + 38 = 130 ・92 + 83 = 175 ・93 + 28 = 121 ・93 + 82 = 175 ・98 + 23 = 121 ・98 + 32 = 130 ・39 + 28 = 67 ・38 + 29 = 67 ・82 + 39 = 121 ・89 + 23 = 112 ・89 + 32 = 121 ・83 + 29 = 112 条件 1 ≦ a, b, c, d ≦ 9 例 入力例1 2 9 3 8 出力例1 175 入力例2 7 8 7 7 出力例2 164 回答 input_line = gets.split(" ").sort { |a,b| a.to_i <=> b.to_i }.reverse puts "#{input_line[0]}#{input_line[2]}".to_i + "#{input_line[1]}#{input_line[3]}".to_i 回答(refactoring) input_line = gets.split(" ").sort { |a,b| a.to_i <=> b.to_i }.reverse puts [input_line[0], input_line[2]].join.to_i + [input_line[1], input_line[3]].join.to_i 学び sortメソッド / sort_byメソッド a = [2,3,1,4,5,1] p a.sort #=> [1, 1, 2, 3, 4, 5] s = ["aaaaa","b","cc"] p s.sort_by {|array| array.size} #=> ["b", "cc", "aaaaa"] joinメソッド 配列の要素を連結して文字列を返すjoinメソッド array = ["1", "5", "4"] p array.join # => "154" p array.join("-") # => "1-5-4" おわりに 最終的な出力の際に puts [input_line[0], input_line[2]].join.to_i + [input_line[1], input_line[3]].join.to_i としているが、[input_line[0], input_line[2]]この部分は、もう少しスマートに書ける気がする。 配列の中から指定したindexの要素を取り出し新たな配列を作り出すメソッドがあれば、もっとスマートに書けるようになるのではないか、見つからなかったけど,,,
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

<input type="datetime-local">にcapybaraで値を入力する方法

実行環境 macOS 10.15.7 (19H1217) Ruby 2.6.3p62 Rails 6.0.3.7 RSpec 3.10 Capybara 3.35.3 <input type="datetime-local"> <input> 要素の datetime-local 型は、ユーザーが簡単に日付と時刻、つまり年、月、日と時、分を入力することができる入力コントロールを生成します。ユーザーのローカルタイムゾーンが使用されます。 ↑に例が載っています。 Capybaraで<input type="datetime-local"> に値を入力する Capybaraを使ってsystem specを書いている状況を想定します。 例えば次のようなspecを書いたとすると期待どおり動くでしょうか。 sometime.html ... <input type="datetime-local" id="sometime-input"> ... spec/system/sometime_spec.rb ... sometime = DateTime.now find('input[id="sometime-input"]').set(sometime) ... 動く"時"もあるかもしれませんが,多くの人は動かないと思います。 <input type="datetime-local">のstep属性 動かない原因はstep属性にありました。 <input type="datetime-local">にstep属性の値を明記しない場合,デフォルト値の"60"が使用されます。 これは,デフォルトでは,秒以下の単位は入力できないということです。 先程の例では,step属性の値を明記していない(秒単位の入力はできない)にも関わらず,DateTime.nowの値を入力しようとしたため,DateTime.nowの値がキリのいい値でない(12時30分20秒など秒の単位が00でない)場合は入力できなかったということです。 Re: Capybaraで<input type="datetime-local"> に値を入力する 以上で見てきた問題点を解消する方法は複数あります。 以下ではその例を2つ紹介します。 <input type="datetime-local">そのものを秒単位で入力できるようにする sometime.html ... <input type="datetime-local" id="sometime-input" step="1"> ... このようにすれば入力できます。 テスト時に秒単位を入力しない 例えば, spec/system/sometime_spec.rb ... sometime = DateTime.now.beginning_of_hour find('input[id="sometime-input"]').set(sometime) ... のようにして,テスト時に秒単位を捨てると入力できます。 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby]自作で破壊的メソッドを作ってみた

はじめに 自作で破壊的メソッドを作ってみたので備忘録として残しておきます。 破壊的メソッドとは 結論から言うと、インスタンス自身の内容を変えてしまうものを破壊的メソッドと呼びます。 非破壊的メソッド「reject」 array = [1,2,3] p array.reject {|item| item == 2} #=> [1, 3] p array #=> [1, 2, 3] 破壊的メソッド「delete」 array = [1,2,3] p array.delete(2) #=> [1, 3] p array #=> [1, 3] 上記の例では「非破壊的メソッド」であるArrary#rejectを用いた場合にはarrayそのものの値は変更していないが、「破壊的メソッド」であるArray#deleteを用いた場合にはarrayそのものの値が変更されている事がわかるとおもいます。 作ってみる たとえば文字列の先頭に「foo」を付け足す破壊的メソッド「foo!」を定義する場合以下のようになります。 class String  def foo!  self.replace("foo" + self)  end end たったこれだけですが、以上を参考にいろいろな破壊的メソッドを作れそうです。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】カスタムバリデーションを作成してAncestyに階層制限をかける

はじめに 現在、共同開発にてドキュメント作成アプリを開発中です。 その中でAncestryを用いた階層構造を実装しており、機能としてはドキュメント作成時にタイトル部分に/ を入力して、文章を区切ることによりディレクトリが作成できるようになっています。 今回はディレクトリの階層を5階層までにしたかったので、バリデーションをかけようと思います。しかし、デフォルトのバリデーションオプションでは難しそうでした。 そこで色々調べていくとActiveModel::EachValidator を使うことで、独自のバリデーションを追加することが分かり便利だなと思ったため、備忘録として使い方を残しておきます。 カスタムバリデーターの作成 カスタムバリデーターとは ActiveModel::ValidatorやActiveModel::EachValidator を使って自作のバリデーションを作成することです。 独自のバリデーションルールを追加しようとする時に、modelに書くと可読性が悪くなり、更には管理も大変になってしまいます。それを防ぐためにバリデートクラスと呼ばれる、バリデーションを自作するためのクラスを継承したクラスを作ります。 今回は使いやすいということで、ActiveModel::EachValidator を継承したクラスを作成して、そこに独自のバリデーションルールを書いていきます。 ファイルの作成 app/配下にvalidatorsディレクトリを作成します。 app/validators/<検証名>validator.rbの形式でファイルを作成 ActiveModel::EachValidator を継承したクラスを作成 app/validators/ancestry_validator.rb class AncestryValidator < ActiveModel::EachValidator end ここで指定したクラス名が、modelでカスタムバリデーターを呼び出す際に使われます。 これでカスタムバリデーションを実装する準備ができたため、後はこのファイルにロジックを書いてあげれば完了です。 バリデーションルール作成 まずはカスタムバリデーション用にRailsが用意してくれている、validate_eachメソッドを追加しましょう。 app/validators/ancestry_validator.rb class AncestryValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) end end validate_each メソッドの3つの引数 validate_each メソッドの3つの引数の中身にはそれぞれ、以下の値が入ります。 record ここにはオブジェクトが入ります。 attribute これには属性が入ります。 今回で言えば:ancestyが返ってきます。 value ユーザーが入力した値が入ります。 文字数検証などに使います。 以上のことを踏まえた上で、カスタムバリデーションを設定していきましょう! カスタムバリデーション設定 今回は5階層以降のディレクトリを作成したくないため、以下のように設定しました。 class AncestryValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) if value.count("/") > 3 record.errors.add(attribute, "ディレクトリは6つ以上作成できません!") end end end / の数により何階層目かを判断して、3つ以上になると6階層目になるためバリデーションに引っかかるようにしています。 record.errors.add(属性, メッセージ) 上記のようにすることでバリデーションを発生させて、メッセージを追加することができます。 属性を指定することで、今回なら「ancestryディレクトリは6つ以上作成できません!」と表示されるようになります。 呼び出し 最後に設定したバリデーションを使えるように、対象のモデルでカスタムバリデーションを呼び出す記述を追加しましょう! validates :ancestry, ancestry: true この記述により、ancestryカラムに対してAncestryValidatorクラスのカスタムバリデーションを適応させることができます。 まとめ 今回はActiveModel::EachValidator を使った、独自のバリデーションを作成する方法について紹介しました。 初めて自作のバリデーションを作成しましたが、これを使いこなせるようになれば何でも検証できて便利だなと思ったので、ぜひ使いこなせるようになりたいと思います!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フォロー・フォロワー機能

Rails5使用の、フォロー・フォロワー機能についてのまとめです。 UserとBookを作成しており、 1対多 となっています。 modelとcontrollerの作成 rails g controller Relationships rails g model relationchip follower_id:integer foolowed_id:integert カラム確認後 rails db:migrate ※ follower する   followed される モデルのアソシエーション relationship.rb belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" class_name: 関連先のモデル参照する際の名前の変更 user.rb has_many :relationships,class_name: "Relationship", foreign_key: "follower_id",dependent: :destroy has_many :passive_relationships,class_name: "Relationship", foreign_key: "followed_id",dependent: :destroy has_many :followings, through: :relationships,source: :followed has_many :followers, through: :passive_relationships,source: :follower def follow(user_id) unless self == user_id self.relationships.find_or_create_by(followed_id: user_id.to_i, follower_id: self.id) end end def unfollow(user_id) relationships.find_by(followed_id: user_id).destroy end def following?(user) followings.include?(user) end has_manyで条件を記載 has_many :relationships, class_name:"Relationship" ↑relationshipsをRelationship名に変更 foreign_key(入口):"follower_id" user.idがfollowerなのかfollowedなのか指定 has_many~~~~~~~through(経由)~~~~~source(出口) userがfollower_idからrelationshipsを経由してfollowerに出る 簡潔にすると、フォローしている人の一覧 コントローラーとルーティング [controller] relationships_controller.rb def create current_user.follow(params[:user_id]) redirect_to request.referer end def destroy current_user.unfollow(params[:user_id]) redirect_to request.referer end def followings user = User.find(params[:user_id]) @users = user.followings end def followers user = User.find(params[:id]) @users = user.followers end [routes] routes.rb resources :users do resouce :relationships, only: [:create, :destroy] get 'relationships/followers' => 'relationships#followers', as: 'followers' get 'relationships/followings' => 'relationships#followings', as: 'followings' end userとネストします。 get で followers,followingsのuser一覧表示をさせる viewの作成 <td>フォロー数: <%= user.followings.count %></td> <td>フォロワー数: <%= user.followers.count %></td> <td> <% unless current_user == user %> <% if current_user.following?(user) %> <%= link_to "フォロー外す", user_relationships_path(user.id), method: :delete %> <% else %> <%= link_to "フォロー", user_relationships_path(user.id), method: :POST %> <% end %> <% end %> </td> user.rbでfollowの表示は自分には不要(フォローするボタン) unless self == user_id と記載済み viewに <% unless current_user == user %> と記載することで 自分のプロフィールにはフォローリンクがなくなります。 フォロー数、誰がフォローしているのか <tr> <th>follows</th> <th><%= link_to user.followings.count, user_followings_path(user.id) %></th> </tr> <tr> <th>followers</th> <th><%= link_to user.followers.count, user_followers_path(user.id) %></th> </tr> link_toでcount数にリンクがつき、 数字を押すと誰がフォローしているのかのviewページに飛ぶ user_followings_pathは rails/info で root の確認 user.idは "誰が" userのidを拾う [count数のリンク先のview] <tr> <% if @user.exists? %> <th>name</th> <th></th> </tr> <tbody> <% @users.each do |user| %> <tr> <td><%= user.name %></td> <td>フォロー数: <%= user.followings.count %></td> <td>フォロワー数: <%= user.followers.count %></td> <td><%= link_to "Show", user %></td> <tr> <% end %> </tbody> <% else %> <th>ユーザーはいません</th> <% end %> exists?メソッド => 指定した条件のレコードがデータベースに存在するかどうかを 真偽値で返すメソッド。 存在すれば、trueを、しなければfalseを返す。 each文で繰り返し処理をし、 user.nameで誰がフォローしているのか user.nameさんは何人フォローしていて、何人フォロワーがいるのか link_to "Show"でuser.nameさんのプロフィール画面へ遷移させる。 以上でフォロー・フォロワー機能の実装が終わりました。 足りない部分や、違うところがありましたら教えてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby] ハッシュについてまとめる

はじめに 前回配列についてまとめてみて、個人的に配列とハッシュは組み合わせると力を発揮するセットのように思っていますので、前回の続きではないですが、今回はハッシュについてまとめたいと思います。 ハッシュとは ハッシュはデータとその名前の2つを1セットの要素として持つ値です。 ハッシュではデータをバリュー、その名前をキーと言います。 ハッシュも配列のように複数のデータを持つことができますが、配列は順番でデータの管理をしていたのに対し、ハッシュはキーで管理します。 またこの管理方法をキーバリューストアと言います。 なぜハッシュを使うのか 配列でデータを管理をするには限界があります。 例えば、身長を管理している配列があるとします。 そこにはたくさんの身長が入っていますが、誰の身長かはわからないですよね。 そこで名前も保存しようとして、そこに名前を放り込むと、余計ごちゃごちゃになってしまうのが明白です。 height = [ 160, 165, 162, 170, "花子", "太郎" ] ↑めちゃくちゃです 笑 ハッシュはこう言うデータを扱うときに力を発揮します。 ハッシュを生成 ハッシュの生成方法は{}を使います 変数 = {} これで空のハッシュは完成です。配列同様初めから要素を持って生成させることも可能です。 ハッシュはキーとバリューの2つで1セットなので記述の仕方も少々違います。 変数 = { キー1 => バリュー1, キー2 => バリュー2, キー3 => バリュー3} こんな感じで書きます。キーとバリューを =>(ロケットハッシュ)でセットにして記述します。 で、毎回思うのですが、紹介しておきながらあまりこの書き方しないんですね。 どの参考書でも結構この書き方をこってり説明してから、シンボルの説明があって、基本的にはこっち使いますって言われて、先に言って!て言いたくなります 笑 ただ、様々な書き方があると言うことは知っておかないと、そういうコードに出会ったときに戸惑ってしまうので、知っておいた方がいいのはいいでしょう。 そのシンボルってものなんですが、 見た目は文字列のようですが中身は数値になっている値だそうです。 シンボルを宣言するときは先頭に:コロンをつけます。 シンボルをハッシュに用いる場合、このように書きます。 hash = { name: "John"} ハッシュに具体的な情報を与えて生成してみます student = { "name" => "John", "age" => 10 } teacher = { name: "Mike", age: 25 } ハッシュに値を追加 配列で言えば、 <<を使って値を追加しました。 ハッシュの場合、配列で添字を指定するように[]の中にキーを記述して、バリューを代入します。 ハッシュ[追加するキー] = 値 先ほどのteacherに担当教科を示す値を追加します。 student = { name: "John", age: 10 } teacher = { name: "Mike", age: 25 } teacher[:subject] = "English" これでteacherに新たにsubjectというキーが追加されました。 指定したハッシュの値を取り出す ハッシュではキーを指定することでバリューを取り出します。 配列でいう添字の代わりにキーを指定する感じですね 今までのstudent,teacherの例で先生の名前を取り出したいときは puts teacher[:name] これでteacherの名前、Mikeが表示されます ハッシュの値を書き換える これも配列と似てますね。 添字の代わりにキーを指定します。 teacherの名前を変えましょう teacher[:name] = "Emma" puts teacher[:name] これでEmmaが表示されるはずです。 ハッシュの限界 ハッシュはキーでバリューが管理でき、これはこれで大変便利なんですが、ハッシュにも限界があります。 ハッシュを指定するために変数を用意するのですが、増えるとその分変数を用意しなくてはならないので、変数名が一貫せず管理しにくいのです。 そこでハッシュと配列の合わせ技で解消できます。 配列とハッシュの合わせ技 配列の中に複数のハッシュを入れてあげることで、取り出すときに添字とキーを指定してあげることで特定の値を取り出せるようになります。 students = [{ name: "太郎", height: 160}, { name: "花子", height: 154}, { name: "次郎", height: 166}] といった具合で組み合わせます。 もし花子の身長が欲しかったら students[2][:height]と記述することで取り出すことができるのです。 最後に 配列とハッシュの理解はデータを管理して扱う上でとても重要な役割を果たしています。高度なアルゴリズムを作れるようになるためにも今一度復習しておきたいものです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

flashメッセージを実装(非同期通信)

現在RubyとJavaScriptを用いて記事投稿サイトを作成しています。 今回はお気に入り登録/解除の際、flashメッセージを表示できるようにしていきたいと思います。 お気に入り機能の実装はこちら ■仕様等 ・お気に入りアイコンをクリックするとページ上部にメッセージが表示される ・画面スクロールに追従する ・連続でアイコンを押下した際は最新のメッセージが割り込む ・jQueryを使用しない ■完成後の動作とコード create.js.erb //①お気に入り登録が行われた場合に処理を実行する if(document.getElementById('article_<%= @article.id %>').innerHTML = '<%= escape_javascript( render 'shared/articles', article: @article ) %>'){ //②flashメッセージが既に存在する場合は、それを削除する if(document.getElementById('flash-message')){ document.getElementById('flash-message').remove(); } //③flashメッセージ(div要素)を生成する const flashMessage = document.createElement('div'); flashMessage.setAttribute('class', 'flash-message') flashMessage.setAttribute('id', 'flash-message') flashMessage.setAttribute("style", `top: ${window.scrollY}px; animation-name: flash-message-fade;` ); flashMessage.innerHTML = "お気に入りに登録しました" //④flashメッセージの挿入先を取得(body) const body = document.querySelector("body"); body.prepend(flashMessage) //⑤flashメッセージの表示位置をページスクロールに合わせる document.addEventListener('scroll', function(){ flashMessage.style.top = `${window.scrollY}px` }) //⑥flashメッセージを4秒後に消去する window.setTimeout(function(){ flashMessage.remove(); }, 4000); } ■お気に入り機能の実装 以前こちらで実装したものをそのまま使用しています。 ■flashメッセージの実装 ①お気に入り登録が行われた場合に処理を実行する create.js.erb if(document.getElementById('article_<%= @article.id %>').innerHTML = '<%= escape_javascript( render 'shared/articles', article: @article ) %>'){ if分の条件として、お気に入り登録の処理を記述しています。 ②flashメッセージが既に存在する場合は、それを消去する create.js.erb if(document.getElementById('flash-message')){ document.getElementById('flash-message').remove(); } flashメッセージ表示中に続けてお気に入りアイコン押下した際、 表示中のメッセージを消去して最新のメッセージを表示します。↓ ③flashメッセージ(div要素)を生成する create.js.erb const flashMessage = document.createElement('div'); flashMessage.setAttribute('class', 'flash-message') flashMessage.setAttribute('id', 'flash-message') flashMessage.setAttribute("style", `top: ${window.scrollY}px; animation-name: flash-message-fade;` ); flashMessage.innerHTML = "お気に入りに登録しました" style属性の値に指定している${window.scrollY}px;は、現在のスクロール位置を返します。 この要素とanimation-nameで指定しているアニメーションのCSSは以下の通りです。↓ index.css @keyframes flash-message-fade { 0% { display: inline; opacity: 0; transform: scaleY(-1); background-color: rgb(235, 247, 129); } 10% { opacity: 0.5; } 20% { opacity: 1; transform: scaleY(1) } 90% { opacity: 1; transform: scaleY(1); } 100% { opacity: 0; transform: scaleY(0); display: none; background-color: white; } } .flash-message { animation-duration: 3s; animation-fill-mode: forwards; opacity: 0; display: none; position: absolute; z-index: 2; width: 100%; display: flex; justify-content: center; align-items: center; height: 70px; padding-top: 1%; } ④flashメッセージの挿入先を取得(body) create.js.erb const body = document.querySelector("body"); body.prepend(flashMessage) bodyを取得し、生成したflashメッセージのdiv要素を挿入します。 ⑤flashメッセージの表示位置をページスクロールに合わせる create.js.erb document.addEventListener('scroll', function(){ flashMessage.style.top = `${window.scrollY}px` }) イベントにscrollを指定する事で、画面がスクロールされた際にイベント発火します。 flashメッセージのstyle属性のtopに、先程のwindow.scrollYを代入します。 これによりメッセージがページスクロールに追従する様になります。↓ ⑥flashメッセージを4秒後に消去する create.js.erb window.setTimeout(function(){ flashMessage.remove(); }, 4000); } window.setTimeout内に記述した処理は、指定した時間後に実行されます。 flashメッセージは表示から4秒後に消去されます。 create.js.erbの記述は以上です。 あとはdestroy.js.erb(お気に入り解除)にも①の条件と③のメッセージ内容を変更したものを記述します。 以上で完成です。 ■参考文献 https://developer.mozilla.org/ja/docs/Web/API/Window/scrollY
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyの例外とは? 例外処理の流れ(未経験エンジニア・駆け出しエンジニアの方におすすめ)

そもそも例外とは プログラミングにおいていわゆるエラーと呼ばれるものには2種類あります。 1つは、シンプルにエラー。もう一つはバグ。 この二つの違いは以下です。 □エラー → 開発者が想定していなかったエラー □バグ → 開発者が認識しているエラー ※エラーには構文(Syntax)エラーと論理エラーがありますが、ここでは詳細を割愛します。 もちろん、開発者はバグを治す必要がありますが、その中でも「ここでエラーが起こるかもしれないなぁ」と思いながらも、その可能性の低さや、エラーを完全に消去することの難しさから、違う対処法を取ることもあります。 それこそが、例外処理であり、そのエラーが発生した時の情報をログで残しつつ、救済処置を用意しておくことで、エラーが発生した時は例外処理で対処しつつ、今後そのエラーを減らしたり、対策したりするためにログで情報を残すことができます。 ここからはRubyの例外処理です。 Rubyの例外とは まず、Rubyの例外とはどのようなものなのか。 Rubyでは例外もオブジェクトである。 オブジェクトなので例外オブジェクトはメソッドを持っています。このメソッドを使うことで発生した例外の情報を抜き出すことができます。 具体的な情報の抜き出し方は後ほど説明します。 まずは、例外発生の具体例を見てみましょう。 example.rb def method_1 puts 'メソッド処理スタート!' 1 / 0 #ゼロ除算で例外を発生させる end 出力 メソッド処理スタート! ZeroDivisionError: divided by 0 # エラー発生!プログラムも止まる。 このZeroDivisionErrorが例外であり、例外オブジェクトと呼ばれるものです。 また、1 / 0 より前にあるputs はきちんと出力されていますね! また、例外はオブジェクトなので、以下のようなメソッドを持っています。 example.rb # error は例外が発生した時にできる例外オブジェクトというものです。 error.class # エラーオブジェクトのクラス error.message # エラーオブジェクトのクラス error.backtrace # バックトレース これらのメソッドで例外が発生した際の情報が得られます。情報は、エラーオブジェクトのクラスや、エラーオブジェクトのクラス、バックトレース(エラーが発生するまでにたどったコードたち)。 しかし、一つ目の例で見たコードだと普通にエラーを吐いてコードが止まりますね。(1 / 0の部分) このままではerrorオブジェクトはキャッチできないですし、まず本番コードだと普通にバグでアプリケーションが死んでしまいます笑 ってことで、ここからは例外処理というものを学びます! 例外処理は以下の記法で書けます。 例外処理 begin # 例外が起こりうる処理 rescue => error # 例外が発生した場合の処理 end これでbeginで発生したエラーのオブジェクトがerrorとしてキャッチでき、プログラムも止まりません。 さっきのゼロ除算を例に見ると以下になります。 example.rb def method_1 puts 'メソッド処理スタート!' begin 1 / 0 #ゼロ除算で例外を発生させる rescue => e # エラーキャッチ! 例外オブジェクトは、errorじゃなくてもどんな文字列でもおけです。eという記法が良く使われます。 e.class e.message e.backtrace end end 出力 メソッド処理スタート! ZeroDivisionError # エラーオブジェクトのクラス divided by 0 # エラーメッセージ (irb):2:in '/' # バックトレース (irb):2:in 'irb_binding' # バックトレース (略) これでプログラムを止めずにエラーの原因を特定する情報も手に入れることができますね! まとめ ここまで例外と例外処理について説明してきました。 しかし、例外処理は積極的に使うべきではありません。 フレームワークのRailsであればエラーが発生した時に、ユーザーをエラー画面に遷移させる例外処理が組み込まれています。 変にいじってしまうと元々の「レール」から外れてしまいかねません。RailsのベストプラクティスはRails側が用意したレールの上を走り続ける事です。 特に初心者の方は、「例外が発生したら即座に異常終了させよう」「フレームワークの共通処理に全部丸投げしよう」と考えましょう! 参考文献 書籍: プロを目指す人のためのRuby入門(伊藤 淳一)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyの例外とは?例外処理の流れ(未経験エンジニアにおすすめ)

はじめに 例外処理について学ぶタイミングってなかなかないですよね〜。 とはいえ、実務になると普通によく遭遇します。 ということで、これからエンジニアになりたい人、エンジニアになりたての人に向けて、例外とは何かというところから丁寧に解説した記事です。 ぜひご覧ください。 そもそも例外とは プログラミングにおいていわゆるエラーと呼ばれるものには2種類あります。 1つは、シンプルにエラー。もう一つはバグ。 この二つの違いは以下です。 □エラー → 開発者が想定していなかったエラー □バグ → 開発者が認識しているエラー ※エラーには構文(Syntax)エラーと論理エラーがありますが、ここでは詳細を割愛します。 前提として、開発者はバグを治す必要があります。 そこで、「ここでエラーが起こるかもしれないなぁ」と思う箇所に、エラーが発生した場合の対処法をコードに落とし込んでおいたり、エラーメッセージをログに残すことで、開発者がエラーの改修をすばやく行うことができるようになります。 それこそが、例外処理であり、そのエラーが発生した時の情報をログで残しつつ、救済処置を用意しておくことで、エラーが発生した時は例外処理で対処しつつ、今後そのエラーを減らしたり、対策したりするためにログで情報を残すことができます。 ここからはRubyの例外処理です。 Rubyの例外とは まず、Rubyの例外とはどのようなものなのか。 Rubyでは例外もオブジェクトである。 オブジェクトなので例外オブジェクトはメソッドを持っています。このメソッドを使うことで発生した例外の情報を抜き出すことができます。 具体的な情報の抜き出し方は後ほど説明します。 まずは、例外発生の具体例を見てみましょう。 example.rb def method_1 puts 'メソッド処理スタート!' 1 / 0 #ゼロ除算で例外を発生させる end 出力 メソッド処理スタート! ZeroDivisionError: divided by 0 # エラー発生!プログラムも止まる。 このZeroDivisionErrorが例外であり、例外オブジェクトと呼ばれるものです。 また、1 / 0 より前にあるputs はきちんと出力されていますね! また、例外はオブジェクトなので、以下のようなメソッドを持っています。 example.rb # error は例外が発生した時にできる例外オブジェクトというものです。 error.class # エラーオブジェクトのクラス error.message # エラーオブジェクトのクラス error.backtrace # バックトレース これらのメソッドで例外が発生した際の情報が得られます。情報は、エラーオブジェクトのクラスや、エラーオブジェクトのクラス、バックトレース(エラーが発生するまでにたどったコードたち)。 しかし、一つ目の例で見たコードだと普通にエラーを吐いてコードが止まりますね。(1 / 0の部分) このままではerrorオブジェクトはキャッチできないですし、まず本番コードだと普通にバグでアプリケーションが死んでしまいます笑 例外処理 ってことで、ここからは例外処理というものを学びます! 例外処理は以下の記法で書けます。 例外処理 begin # 例外が起こりうる処理 rescue => error # 例外が発生した場合の処理 end これでbeginで発生したエラーのオブジェクトがerrorとしてキャッチでき、プログラムも止まりません。 さっきのゼロ除算を例に見ると以下になります。 example.rb def method_1 puts 'メソッド処理スタート!' begin 1 / 0 #ゼロ除算で例外を発生させる rescue => e # エラーキャッチ! 例外オブジェクトは、errorじゃなくてもどんな文字列でもおけです。eという記法が良く使われます。 e.class e.message e.backtrace end end 出力 メソッド処理スタート! ZeroDivisionError # エラーオブジェクトのクラス divided by 0 # エラーメッセージ (irb):2:in '/' # バックトレース (irb):2:in 'irb_binding' # バックトレース (略) これでプログラムを止めずにエラーの原因を特定する情報も手に入れることができますね! まとめ ここまで例外と例外処理について説明してきました。 しかし、例外処理は積極的に使うべきではありません。 フレームワークのRailsであればエラーが発生した時に、ユーザーをエラー画面に遷移させる例外処理が組み込まれています。 変にいじってしまうと元々の「レール」から外れてしまいかねません。RailsのベストプラクティスはRails側が用意したレールの上を走り続ける事です。 特に初心者の方は、「例外が発生したら即座に異常終了させよう」「フレームワークの共通処理に全部丸投げしよう」と考えましょう! 参考文献 書籍: プロを目指す人のためのRuby入門(伊藤 淳一)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む