20211011のRubyに関する記事は12件です。

devise ログイン後の遷移先を指定する時に気になったことを深掘りしてみた。

はじめに deviseについて学習していたところ、ログイン後の遷移先を指定先についてのメソッドの挙動について気になったことがありましたので、詳しく調べてみました。 ログイン後の遷移先の指定方法 指定方法はいたって簡単で、application_controller.rbにafter_sign_in_path_for(resource)メソッドを定義するだけです。 app/controllers/application_controller.rb class ApplicationController < ActionController::Base def after_sign_in_path_for(resource) pages_show_path end end 上記の例の場合、ログイン後にルーティングで定義されているpages_show_pathに遷移するようになります。 どうしてこのメソッドでログイン後の遷移先を指定できるのか Deviseのgemをinstallすると、ApplicationControllerにユーザー認証用の基本的なメソッドを付与します。 つまり、after_sign_in_path_forメソッドをApplicationControllerに再定義することで、メソッドのオーバーライドをしているのです。 参考:Deviseのモヤモヤを解消して快適なRailsライフを送ろう! で、何が気になったのか 何が気になったのかというと、after_sign_in_path_forメソッドのデフォルトの挙動です。 deviseについて学習するために参考にさせていただいたQiitaの記事では、以下のように記載がありました。 ログインすると、デフォルトでは root_url に飛ばされます。 Devise側で定義されている、after_sign_in_path_forメソッドを確認したら、遷移先がroot_pathになるような処理がされているんだろうなと思い、実際に確認してみたところ、、、 lib/devise/controllers/helpers.rb def after_sign_in_path_for(resource_or_scope) stored_location_for(resource_or_scope) || signed_in_root_path(resource_or_scope) end ん?なんか思っていたのと違う。。。 メソッドの説明を確認してみます。 By default, it first tries to find a valid resource_return_to key in the session, then it fallbacks to resource_root_path, otherwise it uses the root path (デフォルトでは、セッション内の有効なresource_return_to keyキーを探します。resource_return_to keyキーが見つからなかった場合は、resource_root_pathにフォールバックし、それも見つからなかった場合は、root pathを使います。) 引用:Devise::Controllers::Helpers#after_sign_in_path_for どうやら、デフォルトでは、すぐにroot_pathを使うのではなく、まず、sessionの情報を使って遷移先を決定するようです。 説明文の感じだと、sessionの中身は、URLの情報が入っていそうですね。 ということで、デフォルトの挙動をコードベースで確認してみたくなったので調べてみました。 コードベースで確認してみる コードベースでも確認してみます。 lib/devise/controllers/helpers.rb def after_sign_in_path_for(resource_or_scope) stored_location_for(resource_or_scope) || signed_in_root_path(resource_or_scope) end ||は、左から順に評価し、一番最初に"true"になったものを返す演算子です。 つまり、処理はこんな感じです。 まず、stored_location_for(resource_or_scope)メソッドを実行。ここで実行結果が"true"であれば、処理終了。 実行結果が"false"だった場合、signed_in_root_path(resource_or_scope)を実行。 それでは、各メソッドの処理を確認していきましょう。 stored_location_for(resource_or_scope)メソッドについて 1.stored_location_for(resource_or_scope)メソッドの概要 まずコードを確認してみましょう。 lib/devise/controllers/store_location.rb # File 'lib/devise/controllers/store_location.rb', line 18 def stored_location_for(resource_or_scope) session_key = stored_location_key_for(resource_or_scope) if is_navigational_format? session.delete(session_key) else session[session_key] end end 処理としては、 session_keyという変数にstored_location_key_for(resource_or_scope)メソッドの戻り値を代入しています。 次に、is_navigational_format?の結果がtrue or falseで以下のように条件分岐をしています。 trueの場合:session[session_key]の情報を削除。 falseの場合:session[session_key]を戻り値として返す。 メソッドの説明を確認してみます。 Returns and delete (if it's navigational format) the url stored in the session for the given scope. Useful for giving redirect backs after sign up: (引数として与えられたスコープにかかるsessionに保存された、urlを返します。もし、それがナビゲーションのフォーマットであれば、削除します。これは、サインアップ後のリダイレクトバック先を指定するのに便利です。) 引用:Devise::Controllers::StoreLocation#stored_location_for sessionに保存されている情報がナビゲーションの情報であった場合は、sessionを削除。 sessionに保存されている情報がurlであった場合は、urlの情報を返す。 やはり、sessionに保存されている情報は、urlでしたね。 次に、sessionについて確認しましょう。 2.変数session_keyの値について 変数session_keyは、stored_location_key_for(resource_or_scope)メソッドの戻り値が代入されています。 それでは、stored_location_key_for(resource_or_scope)メソッドについて確認します。 devise/lib/devise/controllers/store_location.rb def stored_location_key_for(resource_or_scope) scope = Devise::Mapping.find_scope!(resource_or_scope) "#{scope}_return_to" メソッドは、resourceもしくは、scopeを引数にしています。今回は、Userモデルにdeviseを実装しているので、引数にはuserが入ります。 変数scopeには、DeviseのMappingクラスのfind_scope!メソッドの戻り値が代入されます。 最終的に文字列"#{scope}_return_to"を返します。 変数scopeの値について 変数scopeの値を理解するためには、以下の2つを理解する必要があります。 Devise::Mapping find_scope!(resource_or_scope) 1.DeviseのMappingクラスは、routes.rb内のdevise_forで設定された各リソースを元にマッピングオブジェクトを作成します。この時、マッピングオブジェクトの名前は、単数形の名詞になります。 ex.devise_for :users → user Class: Devise::Mapping 2.find_scope!(resource_or_scope)メソッドは、deviseのscopeをシンボルの形式で返してくれるメソッドです。scopeが見つからなかった場合は、エラーを吐きます。 devise/lib/devise/mapping.rb # Receives an object and find a scope for it. If a scope cannot be found, # raises an error. If a symbol is given, it's considered to be the scope. def self.find_scope!(obj) obj = obj.devise_scope if obj.respond_to?(:devise_scope) case obj when String, Symbol return obj.to_sym when Class Devise.mappings.each_value { |m| return m.name if obj <= m.to } else Devise.mappings.each_value { |m| return m.name if obj.is_a?(m.to) } end raise "Could not find a valid mapping for #{obj.inspect}" end 内部的には、devise_scopeメソッドで作成されたdeviseのscopeをシンボルの形式で返しています。 この2つ結果を踏まえると、最終的な戻り値は、 :user_return_to となります。 つまり、変数session_keyの値は、:user_return_toであり、 session[:user_return_to]となります。 sessionのキーとするためにシンボルにして値を返していたのですね。 session[:user_return_to]の値について ここまでで、session[:user_return_to]に、urlの情報が保存されていて、ログイン後にこのurlに遷移するところまでわかりました。 次に生じる疑問点は、sessionではどのurlを保存しているのかです。 1.store_location_for(resource_or_scope, location)メソッドについて stored_location_for(resource_or_scope)メソッドのすぐ下に以下のようなメソッドが定義されています。 devise/lib/devise/controllers/store_location.rb # Stores the provided location to redirect the user after signing in. # Useful in combination with the `stored_location_for` helper. # # Example: # # store_location_for(:user, dashboard_path) # redirect_to user_facebook_omniauth_authorize_path # def store_location_for(resource_or_scope, location) session_key = stored_location_key_for(resource_or_scope) path = extract_path_from_location(location) session[session_key] = path if path end Stores the provided location to redirect the user after signing in. Useful in combination with the stored_location_for helper. (ユーザーをサインイン後にリダイレクトさせるために引数で提供された場所を保存します。stored_location_for ヘルパーと一緒に使うと便利です。) ログイン後の遷移先の情報を保存してくれるメソッドのようです。 処理を確認すると、、、、 pathという変数にextract_path_from_location(location)の戻り値が代入されています。そしてその変数pathが存在する場合にpathの値をsession[session_key]に代入しています。 つまり、extract_path_from_location(location)の戻り値こそが、ログイン後のリダイレクト先ということになります。 それでは、extract_path_from_location(location)メソッドについて確認しましょう。 def extract_path_from_location(location) uri = parse_uri(location) if uri path = remove_domain_from_uri(uri) path = add_fragment_back_to_path(uri, path) path end end 処理を確認すると、 引数の値をparse_uriメソッドでuriとして生成し、変数uriに代入します。 変数uriの値が存在する場合、pathとして機能するように変数の値を加工しています。 ここでふと思いました、『あれ、でも引数には何が入るんだろう』と。 現時点では、アプリケーション側では、store_location_for(resource_or_scope, location)メソッドを定義していませんので、引数を設定することはありません。 『ということは、store_location_for(resource_or_scope, location)メソッドをアプリケーション側で定義する必要があるのか。』 ということで、もう少し調べてみることにしました。 調べてみると、deviseのwikiに以下の記事がありました。 How To: Redirect back to current page after sign in, sign out, sign up, update なるほど、やはり自分で定義してあげないといけないのか。と思った矢先に以下の記述に目が止まりました。 The following guides are already implemented in Devise 4.7 version(以下のガイダンスについては、Devise4.7バージョンで実装済みです。) 実装済み??ってことは、自分でメソッドを定義しなくても使えるってことなんでしょう。 2.store_location!メソッドについて githubには便利な機能があって、コード間を楽々移動できちゃいます。この機能を使ってstore_location_for(resource_or_scope, location)メソッドを参照しているメソッドを探します。すると、devise/lib/devise/failure_app.rbの243列目に参照しているメソッドがありました。 failure_app.rbは、ユーザーが認証に失敗した際の処理を記述したファイルです。 メソッドの説明を確認します。 devise/lib/devise/failure_app.rb # Stores requested URI to redirect the user after signing in. We can't use # the scoped session provided by warden here, since the user is not # authenticated yet, but we still need to store the URI based on scope, so # different scopes would never use the same URI to redirect. def store_location! store_location_for(scope, attempted_path) if request.get? && !http_auth? end Stores requested URI to redirect the user after signing in. We can't use the scoped session provided by warden here, since the user is not authenticated yet, but we still need to store the URI based on scope, so different scopes would never use the same URI to redirect.(サインイン後にユーザーをリダイレクトさせるためのリクエストされたURIを保存します。ここでは、wardenで提供されたscoped sessionを使用することはできません。なぜなら、ユーザーは、まだ認証されていないからです。しかし、異なるscopeが同じURIをリダイレクト先として使用しないようにするために、scopeに基づいたURIを保存しておく必要があります。) 処理としては、 request.get?の結果が"true"でかつhttp_auth?の結果が"false"であった場合にstore_location_for(scope, attempted_path)を実行するというものです。 1.メソッドの制御について request.get?は、リクエストがHTTP GETメソッドであれば"true"を返すメソッドです。 http_auth?は、ajaxによるリクエストであった場合に"true"を返すメソッドです。 devise/lib/devise/failure_app.rb # Choose whether we should respond in an HTTP authentication fashion, # including 401 and optional headers. # # This method allows the user to explicitly disable HTTP authentication # on AJAX requests in case they want to redirect on failures instead of # handling the errors on their own. This is useful in case your AJAX API # is the same as your public API and uses a format like JSON (so you # cannot mark JSON as a navigational format). def http_auth? if request.xhr? Devise.http_authenticatable_on_xhr else !(request_format && is_navigational_format?) end end このメソッドの制御については、deviseのwikiには以下のようにstorable_location?メソッドとして定義されていました。 private # Its important that the location is NOT stored if: # - The request method is not GET (non idempotent) # - The request is handled by a Devise controller such as Devise::SessionsController as that could cause an # infinite redirect loop. # - The request is an Ajax request as this can lead to very unexpected behaviour. def storable_location? request.get? && is_navigational_format? && !devise_controller? && !request.xhr? end def store_user_location! # :user is the scope we are authenticating store_location_for(:user, request.fullpath) end end この説明書きによると以下の場合には、ロケーション情報が保存されないとのことです。 リクエストがGETではないとき(常に同じ結果を返す(冪等)ものでないリクエスト) リクエストがDeviseのコントローラーによって制御されているもの(無限リダイレクトループを引き起こす) リクエストがAJAXによるものである場合(全く予期せぬ動作を引き起こす) おそらく、store_location!メソッドのメソッド制御の部分がこれにあたるのでしょう。 2.store_location_forの引数について メソッドの実行部分をもう一度見てみましょう。 store_location_for(scope, attempted_path) 引数には、以下の二つが設定されています。 scope attempted_path ●引数scopeについて scopeは、以下のように定義されています。 devise/lib/devise/failure_app.rb def scope @scope ||= warden_options[:scope] || Devise.default_scope end 処理としては、インスタンス変数scopeの値が存在しない場合、warden_options[:scope]を自己代入。それも無い場合は、Devise.default_scopeを自己代入するようです。 1. warden_options[:scope] wardenとは、rubyで作成された、ウェブアプリケーションに認証メカニズムを提供するために設計されたRackベースのミドルうウェアです。 Deviseは、wardenベースで作られたものです。 それでは、warden_optionsの定義を確認してみましょう。 devise/lib/devise/failure_app.rb def warden_options request.respond_to?(:get_header) ? request.get_header("warden.options") : request.env["warden.options"] end メソッドは三項演算子の形式になっています。 respond_to?メソッドで、request(レシーバー)にget_headerメソッドが定義されているかを確認し、定義されていれば、request.get_header("warden.options")を実行し、レシーバーに定義されていなかった場合は、request.env["warden.options"]を実行します。 次に、get_headerメソッドについて確認します。 lib/rack/request.rb # Get a request specific value for `name`. def get_header(name) @env[name] end メソッドは、rackで定義されています。 リクエストを引数で与えられた名前のついた特定の値にするメソッドのようです。 request.env["warden.options"]と結局同じになるようですね。 つまり、このメソッドは、 "warden.options"という名前で保存されたリクエストの情報を返すメソッドのようです。 このリクエストは、認証失敗時にユーザーが行なったリクエストのことです。 ここまでのことを踏まえると、Deviseは、未ログインユーザーが認証失敗時にリクエストした情報を、env["warden.options"]として保存していることがわかります。 長くなりましたが、warden_options[:scope]には、現在devise認証を用いているモデルのuserが入ることになります。 2. Devise.default_scopeは、 routes.rbで一番最初に定義されたdevise_for :以下の部分がデフォルト値で、config/initializers/divise.rbで設定している場合は、それを使用します。今回は、userモデルのみしかdeviseを実装していませんので、userとなります。 2. warden_options[:attempted_path] ここには、認証失敗時にユーザーがアクセスを試みたパスの情報が入っています。 devise/lib/devise/failure_app.rb def attempted_path warden_options[:attempted_path] end 参考:Deviseちょっとしたtips2つ 引数についてまとめます。 1. scope=Deviseでログイン失敗時に操作しようとしたモデル名(単数形) 2. attempted_path=ログイン失敗時にアクセスを試みたパス devise/lib/devise/failure_app.rb def store_location! store_location_for(scope, attempted_path) if request.get? && !http_auth? end rubyメソッドの引数は、値渡しです。 scopeとattempted_pathの定義された値がそのまま引数として渡されます。 Deviseでの認証失時、store_location_for(scope, attempted_path) では、以下のことが実行されます。 session[:user_return_to]=認証失敗時のパス情報 after_sign_in_path_forメソッドのデフォルト挙動(まとめ) lib/devise/controllers/helpers.rb def after_sign_in_path_for(resource_or_scope) stored_location_for(resource_or_scope) || signed_in_root_path(resource_or_scope) end after_sign_in_path_for(resource_or_scope)は、ログイン前に、認証に失敗しているか否かによって、ログイン後の遷移先が変わります。 具体的には、以下のようになります。 1. ログイン前に認証に失敗して、ログインページにリダイレクト後、ログインをした場合 →リダイレクト直前のページへ遷移。 2. ログインページからログインした場合 →リソースベースのルートページを定義していれば、そこへ。定義していなければ、ルートページへ遷移。 after_sign_in_path_forメソッドのデフォルトは、基本的には、ルートパスへの遷移です。しかし、ログインが必要なページ(仮にAページとします)に未ログイン状態でアクセスした場合については、ログインした後にAページに遷移させるということがわかりました。ちなみにこれをフレンドリーフォワーディングといいます。 参考:フレンドリーフォワーディング おわりに とてもシンプルに見えたコードでしたが、追っていくとなかなか複雑でした。 実際、コードリーディングをしてみましたが、知らないこと、わからなことばかりで大変な部分もありましたが、初学者の身からすると大変勉強になったと感じました。間違っている部分がございましたらご指摘いただけると幸いです。 参考 heartcombo/devise Rails ガイド How To: Redirect back to current page after sign in, sign out, sign up, update warden wiki [Rails] deviseの使い方(rails6版) Deviseのモヤモヤを解消して快適なRailsライフを送ろう! Deviseちょっとしたtips2つ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

deviseでユーザー機能実装済みのアプリに管理者機能を追加

やまだゆう(@yamaday0u)です。 今回は、Railsのdevise gemでログイン認証機能(ユーザー管理機能)を実装済みのアプリに、管理者機能を追加する実装例をご紹介します。 目標 この記事では、 deviseでログイン認証機能を実装済みのアプリに 新たなテーブルを追加せずに 管理者機能を実装すること を目標にします。 管理者とユーザーのテーブルを別々に作りたい場合は以下のブログ記事を参考にしてください。 【Rails】deviseを使用して管理者と会員を作成する方法(初心者向け)(Nulfasのブログ) 前提 devise gemですでにユーザー管理機能を実装済み = ユーザーを管理するusersテーブルが存在していること。 usersテーブルには最低限の以下のカラムが存在しています。 カラム名 データ型 name string email string encrypted_password string 実装のポイント 既存のusersテーブルにboolean型のadminカラムを追加して、adminカラムの値がtrueのユーザーを管理者とします。 手順 migrationファイルを作成 既存のusersテーブルにデータ型がboolaen型のadminカラムを追加するためにmigrationファイルを作成します。 ターミナル rails g migration add_admin_to_users admin:boolean migrationファイルの編集&実行 作成したmigrationファイルに以下の通り記述します。 ここでのポイントは、初期値をdefault: falseで指定しているところです。 これによりusersテーブルの既存のレコードにも初期値がfalseの状態でadminカラムが追加されます。 db/migrate/2021XXXXXXXXXX_add_admin_to_users.rb class AddAdminToUsers < ActiveRecord::Migration[6.0] def change add_column :users, :admin, :boolean, default: false end end ターミナルでマイグレートします。 ターミナル rails db:migrate 管理者ユーザーを作成 管理者ユーザー(のレコード)をseeds.rbファイルを用いて作成します。 ここでのポイントはもちろん、adminカラムの値をtrueにしていることです。 後の工程で、adminカラムの値がtrueであるか否かを利用してログイン後のマイページの表示を切り替えます。 また、ぼくの場合、githubにアプリのソースコードを公開しているので、.envというgemを使って、メールアドレスやパスワードは環境変数を定義して指定しました。 db/seeds.rb User.create!( name: "管理者", email: ENV['ADMIN_EMAIL'], password: ENV['ADMIN_PASSWORD'], password_confirmation: ENV['ADMIN_PASSWORD'], admin: true ) seedsファイルに記述した内容を実行します。 ターミナル rails db:seed これで、管理者ユーザーが作成、usersテーブルに登録されました。 Routeの定義 ぼくのポートフォリオアプリであるGroup Calendarを例にrouteの定義を説明します。 通常のユーザーであれば、ログイン後にconfig/routes.rbに定義したresources :calendarsによりマイページ(indexアクション)にリダイレクトします。 そこで管理者ユーザーの場合は、namespaceを利用して以下のように記述し、admin/calendarsをマイページのpathにして、管理者ページであることを明示的にしました。 config/routes.rb namespace :admin do # 以下の記述により、admin/calendarsへのpathが開かれます。 resources :calendars, only: %i[index] end Controllerの作成その1 routeの定義でadmin/calendarsというpathを開いたので、以下のようにAdmin::Calendars Controllerを作成します。 ターミナル rails g controller admin/calendars 作成したcontrollerに以下の通り記述します。 app/controllers/admin/calendars_controller.rb class Admin::CalendarsController < ApplicationController def index # 省略 end end Controllerの作成その2 管理者ユーザーにはニュースリリース機能を使えるようにしたいので、News Controllerを作成します。 ターミナル rails g controller news 作成したNews Controllerに以下のように記述します。 private actionかつbefore_actionのcheck_admin?により、管理者以外のユーザーが管理者用のページであるニュースリリース関連機能のページを表示できないようにしています。 app/controllers/news_controller.rb class NewsController < ApplicationController before_action :check_admin? def index # 省略 end # 中略 private def check_admin? unless current_user.admin redirect_to root_path end end end View Viewはこのように書きます。 ログイン中のユーザー(current_user)のadminカラムの値がtrueなら、ニュースリリース機能関連のページへのリンクを表示します。 app/views/news/_link_to_news_release_page.html.erb <% if current_user.admin? %> <li><%= link_to "News Release", news_index_path %></li> <% end %> 完成!! 以上でdeviseでログイン認証機能を実装済みのアプリに新たなテーブルを追加せずに管理者機能を実装する手順が完了しました。 ぼくのポートフォリオアプリでは、以下のように管理者ページにのみNews Release用のページリンクが表示されるようにしました。 管理者ユーザーのマイページ 一般ユーザーのマイページ Rails学習中のみなさまの参考になれば幸いです。 ポートフォリオ 未経験からのエンジニア転職に向けて作ったポートフォリオを公開しています。 よろしければ覗いて見てください。 Group Calendar
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails * Vue の歴史を辿ってみる

Rails * Vue の歴史を辿ってみる この話を読み終えてわかること Rails * Vueの歴史ってまだまだ浅いこと Rails * Vueの情報はググってみると、やり方が2つに別れていること 初学者でも、全然戦えるフィールドであること Railsの歴史 1995(26年前) Ruby誕生、割と古くからある 割と泣かず飛ばずの言語だった 2005(16年前) RubyOnRailsが誕生 有名企業が次々採用 Airbnb twitter GitHub Rubyが注目され始めるタイミングが瞬間がここ Vueの歴史 2009年(12年前) 土台であるnode.js誕生 WEB業界に衝撃を与えた front, backの完全分業化の実現に大貢献 2014(7年前) Vue.js誕生 従来最強は Facebook製のReact(2011〜) Google製のAngular(2012〜) そこに一石投じた1人の開発者[Evan You]によって誕生 Railsさん、node.jsすげえに気づいて取り込み始める 2016年(5年前) webpackの拡張GEM、webpackerを本気でサポート開始 node.jsのplugin管理をRails上でやりやすくしようと言う試み開始 ただ、node.jsをそもそも使ってた人たちから酷評される webpackerは独自すぎて使いにくい その頃、Vueは 2016年(5年前) Vue2.0が発足 これが強烈に評判がよく、一気に普及 github上でreactやangularのstar数を追い抜く レスポンス早く、書きやすく、めちゃいいやんってなる RailsのWebpackerいらないトレンドが普及 2018年頃(3年前くらい) 脱webpacker記事が増える ならば、しっかりとfront, backを切り分けた方が良いでしょ。と言う流れに node.jsをRailsの中で組み立てるのではなく node.js環境とRails環境をそもそも分けてしっかり疎結合に そして最近のVueの動き 2020年(1年前) Vue3.0がスタート TypeScriptいいよね!の流れと仲良くするため?が強い印象 一層、バグ少なく組みやすいcomposition APIをリリース ここまでの話からわかること 2009年(12年前) WEBの世界は大きく変化があった。 2016年(5年前) Vue.jsの躍進が始まった 2018年(3年前) Rails * Vueのあるべき姿が見え始めた なのでこの分野で見たら、3年プレイヤーくらいが最年長と言う感じ ここまでの状況を踏まえて Rails * Vueって、歴史そんなに深くない この掛け算で戦ってる人達 まだ初学者の方でも、全然手の届く範囲で先駆者たちは走ってる さらにまだまだ進化は続くので、トレンドを追っていれば追い抜くのも難しくない またみんな1年生に戻るかもしれないので というわけで 初学者の方も、焦ったり、すごい人見てげんなりしなくても大丈夫 この分野に限ってはみんな若手 楽しんでたら、気がついたら追い越してるっていう可能性は全然あるので 焦らず楽しんでいきましょうー!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyとは!

①.結論! Rubyはプログラミング言語の1つです! 記述がシンプルでわかりやすいため気軽に楽しくプログラミングでき、しかも実用的です! 大きなWebアプリケーションから小さなプログラムまで、さまざまなソフトウェアを作ることができます! ②.Rubyの特徴! 特徴は以下の通りです! ・Webアプリケーションの開発に特化している! ・データベースとの連携が容易! ・世界で幅広く使われている! ・文法が覚えやすい! これらの特徴から、初めて学習するプログラミング言語に向いています! ③.Rubyと他のプログラミング言語の違い! Rubyの違いは、記述がシンプルでわかりやすいです! 気軽に楽しくプログラミングでき、しかも実用的です! 大きなWebアプリケーションから小さなプログラムまで、さまざまなソフトウェアを作ることが出来ます! ④.まとめ 様々な言語がありますが、まずはRubyからと言う感じですかね! 初心者の私でもやっていけるかなと思います! まぁ私にとっては、充分難しいとは思いますが^^; なにか説明が間違っていたら教えてください(_ _)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】scopeではなぜfirstメソッドを定義しないのか

きっかけ 下記のコードを見たときに少し違和感を覚えた。 class Article < ApplicationRecord scope :published, -> { where(published: true) } end class ArticlesController < ApplicationController def index @article = Article.published.first end end firstメソッドをモデルのpublishedメソッドの中で書いてしまった方が コントローラーが少しすっきりする気がするのになんでそうしてないんだろうと疑問に思ったので scopeについて改めて調べてみた。 class Article < ApplicationRecord scope :published, -> { where(published: true).first } end class ArticlesController < ApplicationController def index @article = Article.published end end 問題になりそうなケースと原因 例えば条件に合うArticleがない場合、nilが返るはずのところでなぜか全件取得されてしまうらしい。 これはscopeの中で行われている処理の結果がnilの場合、全件取得が行われる仕様になっているため。 まとめ scopeは「メソッドを定義するより1行ですっきり書けるもの」という認識しかなかったが 仕様をよく理解して適切に使っていきたい。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】injectについて

inject eachなどのようにブロック内の処理を繰り返し行うメソッド 1~5までの合計を計算する [1, 2, 3, 4, 5].inject {|sum, num| sum + num } #=> 15 ↓上のコードと同じことをしている sum = 0 [1, 2, 3, 4, 5].each {|v| sum += v } p sum # => 15 初期値を指定して毎回のループでリセットすることもできる(デフォルトは初期値0なので省略可) [1, 2, 3, 4, 5].inject(1) {|sum, num| sum + num**2 } #=> 56 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

form_withでのエラー

遭遇したエラー form_withを使って情報を送信しようとしたのですがその時にエラーが出て結構足止めくらいました。 スクショ忘れたのでエラー書きます。 error Nomethod error post_products_path Did you mean? post_product_path 実際のコード 以下を見てわかる通り、post_pathなんて指定していません <%= form_with model: @post_product do |f| %> <%= f.submit "追加", class: "btn btn-outline-info" %> <%= f.hidden_field :title, value: product[:title] %> <%= f.hidden_field :image, value: product[:image] %> <%= f.hidden_field :product_url, value: product[:product_url] %> <% end %> *hidden_fieldで送っているのは、楽天apiの商品情報です。 商品情報はeachで回しているので、userがするのはsubmitボタンを押すだけでどの商品を追加するかの処理が実行されます。 hidde_fieldで送っているのはそのためです。* もっと良い書き方できたのかもしれませんが現状これがベストでしたorz 試したこと form_withのエラーということで、どこかに間違いがあるかな〜と見返してみたのですが、 form_withの書き方はあっているので余計にわかりませんでした。 書き方 form_with model: ”modelクラスのインスタンス” do 後はパスなんですが、railsから提案されたパスを入れても動きませんでした。 なら次はurlで指定しよう! form_with url: createアクションへのパス do 一応formでエラーにはならなかったのですが、今度は別のところでエラーに、 param is missing or the value is empty: ストロングパラメーターをコントローラーで指定していたので多分それが原因になっているのかな? 前にも同じようなエラーが出たのですが、その時は params.require(:model).permit(:カラム, ......) ↓ これを、、、、、 params.permit(:カラム, ......) こう! とすることで解決したのですが、どこかの記事でみたところrequireを消すのは良くないとのこと なのでrequireを元に戻します。 formを直さないといけないので調べました。 まずmodelでインスタンスを指定するには、コントローラーでnewメソッドを使ってインスタンスを作らないといけない。 僕の場合作っていたので、他を探します。 ここで思い出したのが、最初のエラーでpathが違うと言われました。 routingをもう一度みてみると、create アクションのpathと、エラーで出てきていたpathが違ったのです。 試しにform_withの記載を form_with model: @model, url: createのpath とすることで無事送信できました! form_withが想定するpathとresoucesで作られたpathが違うのが原因みたい formは単数系を想定して resoucesは複数で生成されるので、urlでcreateアクションのものを使うよ~と指定してあげるのがいい! 余談 途中で下記のエラーが出ましたが、これの原因はコントローラーでモデルクラスのインスタンスを作成していないのが原因 上で書いた通りrequireを省略すれば解決するのですが、requireは省略しない方がいいと言われたので注意しましょう。 param is missing or the value is empty:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rspec豆知識: 外側let!と内側letの同名シンボルが混在する時に、内側letは外側のタイミングで評価される

前提 rspec --version RSpec 3.10 - rspec-core 3.10.1 - rspec-expectations 3.10.1 - rspec-mocks 3.10.2 - rspec-rails 5.0.2 - rspec-support 3.10.2 問題 このテストファイルを実行した時、標準出力にはどの順番で何が出力されるでしょうか。 以下のポイントに注意してください。 一番外側の :a は let! で正格評価になっている context内の :a は let で遅延評価になっている expect(a).to be true されている この記事のタイトルのことはひとまず忘れて、純粋な気持ちで問題を解いてみてください。 sample_spec.rb RSpec.feature type: :system do let!(:a) do pp 'outer variable (let!)' true end before do pp 'outer before' end context do let(:a) do pp 'inner variable (let)' true end before do pp 'inner before' end # 実行されるテスト it do pp 'inner it' expect(a).to be true end end end 解答 "inner variable (let)" "outer before" "inner before" "inner it" 補足 外側の変数が let(:a) で、両方の変数が遅延評価になっている場合は、純粋に遅延評価され、以下のようになる。 "outer before" "inner before" "inner it" "inner variable (let)" expect(a).to be true のタイミングで a が評価されるためである。 しかし外側に let! がある場合は let! が本来評価されるタイミングで内側のcontext側の let が評価されるため、解答のような順番になる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CSV 使い方 基本

require "csv" text =<<-EOS id,first name,last name,age 1,taro,tanaka,20 2,jiro,suzuki,18 3,ami,sato,19 4,yumi,adachi,21 EOS csv = CSV.generate(text, headers: true) do |csv| csv.add_row(["5", "saburo", "kondo", "34"]) end print csv
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】ストロングパラメーターはなぜ必要か?paramsの脆弱性

はじめに どうも、27歳未経験からエンジニア転職を目指しているもきおです。 Railsでコントローラを記載する際に最後にストロングパラメータ書いてねって記載されてたりしますが、そもそも ストロングパラメータって何ぞや?って思いながらも当時はとりあえず動けばいっかみたいな感じであまり理解しないまま進めてしまってました。 ストロングパラメータの記載はこんな感じ user_controller.rb private def user_params params.require(:user).permit(:name,:email,:password) end 今回はなんでこれが必要なのって話とparamsは使いようによってセキュリティの脆弱性があるよってお話をしていきたいと思います。 ストロングパラメータを使わないとどうなる? ストロングパラメータを使用しないとどのようなセキュリティ上の問題が起こるのでしょうか? 例えば以下の記載でuser_controllerのcreateを作成したとします。 user_controller.rb private def create @user = User.new(params[:user]) end 一見前回のparamsの記事のようにparamsによってuserの値を全て取ってきてuserテーブルにユーザーの情報が情報が格納されるので問題ないように思えます。 ※paramsに関しては前回書いた記事をご覧いただけますと幸いです。 しかし、ユーザー情報を全て格納されるというのは 極めて危険な状態です。 セキュリティ上危険な例 実際の例を見てみましょう。User情報にadmin(管理者)属性を持たせたとしましょう。 admin=’1’という値をparams[:user]の一部に紛れ込ませて渡してしまえば、簡単に管理者属性を持たせる事ができてしまいます。これはcurlなどのコマンドを使えば簡単に実現できてしまいます。 ※Railsチュートリアル7章ユーザー登録参照 https://railstutorial.jp/chapters/sign_up?version=6.0#code-first_create_action 悪意のあるユーザが自身に管理者権限を付与するなどシステムを自由に操作できてしまう危険性があるのです。 これを防ぐためにストロングパラメータによって特定の情報しか受け取らない設定を行い、意図しない登録、更新を防ぐ事ができるのです。 実際にストロングパラメータを使用しよう ストロングパラメータをの基本形は以下になります。 user_controller.rb private def create @user = User.new(params[:user_params]) end 先程の[:user]→[:user_params]に変更しています。 これはストロングパラメータを使いやすくするために、user_paramsという外部メソッドを使うのが慣習になっています。このメソッドは適切に初期化したハッシュを返し、params[:user]の代わりとして使われます。 ストロングパラメータ記述の仕方 user_controller.rb private def user_params params.require(:キー(モデル名)).permit(:カラム名1,:カラム名2,・・・) end requireの後にモデル名、permitの後に受け取るカラム名を指定します。 これを元に最初に記述した user_controller.rb private def user_params params.require(:user).permit(:name,:email,:password) end これで受け取る情報を制限し、悪意のあるユーザーに余計な値(管理者権限等)を受け渡さないように セキュリティを強化できました。 あとがき 今回はストロングパラメータについて理解を深めていきました。 この記事が少しでも良いと感じていただけましたらLGTMポチッといただけますと幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby] AtCoder過去問 B - A to Z String

はじめに AtCoder過去問B問題をRubyで解いてみました。 よろしくお願いします。 問題はこちらから確認してください↓ B - A to Z String まずは入力受け取りです。 一文字ずつ分けて配列で受け取っています。 s = gets.chomp.chars この問題メソッドさえ知っていれば非常に簡単で、要は配列sの中で一番はじめに出てくる(一番先頭に近い)文字Aのインデックス番号(位置)を特定し、配列sの中で一番末尾に近い文字Zのインデックス番号(位置)がわかれば、zのインデックスに+1した数(インデックス番号は0から始まるため+1する)からAのインデックスを引けば文字列の数が分かります。 Aのインデックスはindexメソッドで分かります。 Zのインデックスはrindexというメソッドで末尾からはじめに見つかるZの位置を特定することができます。 s = gets.chomp.chars a_index = s.index("A") z_index = s.rindex("Z") puts z_index+1-a_index a_indexに+1していない理由は、ここで欲しいのは配列sの先頭からはじめのAにたどり着くまでの文字の数なのでAは含まれないからです。もともとインデックスは0から始まるので−1する必要はありません。 逆にz_indexはZを含めた、最後のZの位置を調べたいので+1しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails部分テンプレート(パーシャル)まとめ

使い方1 before xxx.html.erb <%= @user.name %> <%= @user.email %> <%= @user.age %> after xxx.html.erb <%= render partial: 'hoge', locals: {user: @user} %> _hoge.html.erb <%= user.name %> <%= user.email %> <%= user.age %> ※ 書き方:<%= render partial: 'ファイル名', locals: { '部分テンプレート内で使う変数': '変数に入れる値' } %> ※ ファイル名は、render記載のファイルと同じディレクトリにある_hogeならhoge。 ※ localsオプションを使った場合はpartialは省略できない。 ※ partialを省略したいならlocalsオプションも省略しないといけない (例:<%= render 'hoge', user: @user %>) ※ メリット:コードがスッキリする。部分テンプレートを使いまわせるから便利&コードの保守性が上がる。 使い方2 before xxx.html.erb <% @users.each do |user| %> <%= user.name %> <%= user.email %> <%= user.age %> <% end %> after xxx.html.erb <% @users.each do |user| %> <%= render partial: 'user', user: user %> #使い方1と同じ <% end %> _user.html.erb <%= user.name %> <%= user.email %> <%= user.age %> ※ @usersの要素の個数分、部分テンプレートが呼び出されて表示される。 collectionオプションを使うと、下記のようにeach文を省略できる。 xxx.html.erb <%= render partial: 'user', collection: @users %> _user.html.erb <%= user.name %> <%= user.email %> <%= user.age %> ※ 書き方:<%= render partial: 'hoge', collection: 繰り返し表示する要素が入っているインスタンス %> ※ collectionオプションの部分にeachで回したかった変数を入れる ※ collectionに指定した変数の要素の分だけ部分テンプレートが繰り返し表示される ※ collectionオプションを使用した場合、partialで指定したファイルの名前がそのまま部分テンプレート内で使用する変数名になる。 もし別の名前として変数を使いたい場合はasに変数名を指定する <%= render partial: 'user', collection: @users, as: "hoge" %> _user.html.erb <%= hoge.name %> <%= hoge.email %> <%= hoge.age %> ※ @usersに入っている要素が一つずつ取り出され、部分テンプレート_user.html.erb内の変数hogeに代入される。 ※ 上のコード(asは除く)は以下の条件を全て満たしている時、省略できる。 ・ viewsフォルダ内にあるusersフォルダに部分テンプレート_user.html.erbが存在する ・部分テンプレート内で使う変数がuserである まとめると、views/hoges/_hoge.html.erb内で変数hogeを使っていることが省略の条件 なので、 <%= render @users %> と書けば、上述したeach文とcollection記法と意味は同じ #3つとも意味は同じ xxx.html.erb <%= render @users %> <% @users.each do |user| %> <%= render partial: 'user', user: user %> <% end %> <%= render partial: 'user', collection: @users %> users/_user.html.erb <%= user.name %> <%= user.email %> <%= user.age %> ※ 3つとも意味は同じだがパフォーマンス(実行速度)が変わってくるので注意 each文の中にrender入れる VS collectionオプションを使う eachで表示すると@usersなどに入っている要素の個数回分部分テンプレートが呼び出されるが、collectionオプションを使用して記述すると部分テンプレートが呼び出されるのは1回のみ。 なので、eachで表示するよりもcollectionオプションを使う方がパフォーマンスが良くなる。 部分テンプレートを繰り返し呼び出す時はなるべくeach文ではなくcollectionオプションを使うようにする。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む