セッション / クッキー
認証機能とは?
認証機能とは、ユーザの情報を検証して、システムへのログインやユーザの有効性確認を行うための機能です。
そしてこの認証機能の中でも、特に使用頻度が高いのが、ログイン機能でしょう。 では、ユーザーがログインしている / していないを、システム的にはどのように判定しているのでしょうか。 これには、セッションと呼ばれる仕組みを利用しています。
セッションとは?
セッションとは、Webサイトにアクセスして行う一連の行動のことを指し、ページを跨いで情報を保持したい場合に利用される機能です。
webページ閲覧時に用いられるプロトコルをHTTPプロトコルと呼ぶのですが、このHTTPプロトコルは、以前の通信を記憶しておく術を持ち合わせていません。(これを、ステートレスなプロトコル と呼んだりします。) つまり、ログイン後、次のページを見たいという通信を行う際には、このHTTPプロトコルは、ユーザーがログインしていることを忘れてしまっているのです。
そこで、セッションの出番です。 セッションは、ページを遷移しても変わらず情報を保持することができるため、このセッションにログイン中のユーザーの情報を格納しておくことによって、ページを遷移しても、変わらずログイン状態を維持し続けることができるのです。
※プロトコルとは、コンピューター同士が通信をする際の手順や規約などの約束事のことを指します。
webアプリケーションでは、アプリケーションサーバー側にセッションという仕組みを用意しており、1つのブラウザから連続して送られる一連のリクエスト間で状態を共有できます。セッションの情報はRails標準では、ブラウザ側のクッキーに保存されます。また、sessionは明示的に削除する(又は有効期限切れになる)まで消えません。
Railsアプリケーションにはユーザーごとにセッションが設定されます。前のリクエストの情報を次のリクエストでも利用するためにセッションに少量のデータが保存されます。あらゆるセッションは、暗号化されたセッション固有のID(セッションID)をcookieに保存します。Railsでセキュリティ上の理由からセッションIDをURLで渡すことは許可されていません。そのため、セッションIDは必ずcookieで渡すようにしているのです。
Action Controller の概要 - Railsガイド
cookieとは?
ブラウザとwebサーバー間でやりとりされる時に使われる仕組みです。cookieでは、webサーバーからブラウザへHTTPレスポンスを返す際に、何らかのcookie情報を含めて送ります。cookie情報はキーと値(value)のペアの集合です。ブラウザはこのcookieの情報をサーバーのドメインなどの情報に紐付けて保存し、次回以降、同じドメインのサーバーへのHTTPリクエスト時に、ブラウザで保管しておいたcookie情報をリクエストに添えて送ります。 そうすることにより、webサーバーはどのブラウザからのリクエストかを識別することが出来るのです。つまり、cookieは複数のリクエストの間で共有したい状態をブラウザ側に保存する仕組みです。
クッキーは平文
セッションIDは暗号化されたもの
Railsの場合、config/initializer/session_store.rb
にて、セッション情報をどこに保存するのかが指定されています。デフォルトでは、
# セッション情報をどこに保存するのかを設定 # _○○_sessionというkeyで、cookieを用いてセッション情報を管理する Rails.application.config.session_store :cookie_store, key: '_○○_session'
という設定がなされています。
cookieとは、webブラウザが保持する情報のことです。webでは毎回ページを読み込む度にブラウザとサーバーとの間で通信がなされており、このcookieもリクエスト、レスポンスの度に毎回やりとりがなされます。
つまり、セッション情報の保存先をcookieにするということは、
- ログイン時に、ユニークなセッション情報を発行
- それをサーバ : ブラウザ間の通信の度に、cookie内でやりとり
- cookieを受け取ったサーバは、cookie内のセッション情報を元に今リクエストを送ってきているユーザがログイン中か否かを判定
という流れを経ているのです。 但し、cookieに全てのセッション情報が載ってしまうということは、比較的簡単にセッション情報を確認できてしまうことも意味します。
↓Railsのセッション管理方法について
Rails4系からはcookieに載るセッション情報が暗号化されたとは言え、秘密鍵を知っていれば誰でもログイン情報を復元できてしまう恐れがあるのです。ですので、よりセキュリティを強固にしたい場合、このセッションの管理先をredisなどのKVSデータベースに指定したりなどの方法を取る場合もあります。
今回はsorceryは上記で説明した、ログインに必要なことをほぼ自動的にやってくれます。
↓sessionの保存先をcookieからKVSに変更する理由
セッションを保存するとき、なぜ、Cookieではなくmemcachedやredisを使用するのでしょうか?
※KVSとは、キーバリューストア(Kye-Value Store)の略で、保存項目がkeyとvalueのみのDBのことを指します。
参照
sorceryって何??sorceryを使った、ユーザー登録、ログイン&ログアウト機能の実装からsorceryとはなんぞやを調べてみた。
sorceryとは?
sorceryとは、ユーザ認証機能を簡単に実装できるライブラリ(gem)です。同じように認証機能を提供してくれているものとしてdeviseなどが挙げられますが、sorceryの方がよりシンプルで、カスタマイズ性に富んでいるという特徴を持ちます。
ユーザの認証の基本的な機能であるパスワード認証
を始め、
- User Activation
- Reset Password
- Remember Me
- Session Timeout
- Brute Force Protection
- Basic HTTP Authentication
- Activity Logging
といった機能が揃っていて、必要な機能を選んで使うことができます。
間違えやすい点としては、Sorceryはログイン認証機能を実装する為のGemであり、ログイン認証機能自体はSorceryが提供するメソッドによって実現されます。 まとめると、SorceryというGemをRailsにインストールすると、Sorceryが提供する様々なログインに関するメソッドを使えるようになるということです。
[引用:【Rails】駆け出しVimmerがSorceryをざっくり説明してみた]
Sorceryをインストールして、ユーザー登録機能を実装する
1 RailsアプリケーションのGemfileにsorceryを追加します。
gem 'sorcery'
2 コマンドラインで、bundle installしコマンドを実行し、Gemfileに記載したSorceryGemをインストールします。
$ bundle install
3 **Sorceryによって追加されたジェネレーターを実行して、Sorcery Gemを使ったアプリの構築を開始します。
まず、Sorceryが提供するジェネレータbundle exec rails g sorcery:install
を実行し、Userモデルとmigrationファイルを生成します。**
$ bundle exec rails g sorcery:install
上記のコマンド実行後、次のファイルが作成されます。↓
- app/models/user.rb(userモデル)
- config/initializers/sorcery.rb(initializersファイル)
- db/migrate/yyyymmddhhmmss_sorcery_core.rb(migrationファイル)
4 rake rails db:migrateを実行して、usersテーブルを生成します。
$ rake db:migrate
5 以下のコマンドをターミナル上で実行して、Userモデルに対応したビューとコントローラを作成します。
同時に、userテーブルにstring型のemailカラムとstring型のcrypted_password(暗号化されたパスワード)カラムとstring型の※saltカラムを作成します。
$ rails g scaffold_controller user email:string crypted_password:string salt:string
仮想属性とは? 例えば今回のように、databaseのusersテーブルがあるとします。ユーザー情報を登録するusersテーブルにはpasswordというカラムはなく、password〇〇や、〇〇password といった暗号化されたパスワードを入れるようになっています。今回はcrypted_passwordカラムに暗号化されたパスワードが入るようになっています。 そして、crypted_passwordカラムにパスワードを暗号化して入れるためにpassword属性とpassword_confirmation属性という仮想属性を作っているのです。仮想属性にパスワードを入れてsaveすると、暗号化されたものが実際にcrypted_passwordカラムに入ります。 つまり、DBのテーブルにパスワードを保存するために仮想属性が必要になるということです。 実際には、DBに存在しない属性(カラム)であることから、仮想属性と呼ばれます。 params.permit(:password) するときに、password= というsetter methodがないとエラーになりますので atter_accessorを使って、getter、setterの仮属性定義が必要です。
※password_confirmation(パスワードコンファーメイション)とは、データベースに保存されない仮想の属性で、パスワードの入力確認に使われます。
この属性を使用するとpasswordとpassword_confirmationの双方が一致しているかのバリデーションが自動で追加されます。
現在パスワードの保管方法は、単にパスワードをハッシュ化して、暗号化したものを保管するのではなく、元のパスワードの文字列に別の文字列(ソルト)を付け加えることで文字列全体を長くしてから、繋げた文字列をまとめてハッシュ化したものをDBに格納しています。そうすることで、よりセキュリティを高めることができるのです。DBにはソルトとハッシュ値を保存しています。ハッシュ化(パスワード文字列+ソルト)=ハッシュ値
6 暗号化されたパスワードやソルトをユーザーに編集/閲覧させることはセキュリティ上、望ましくないため、ビューでこれらの属性を表示させないようにします。
ビューであるapp / views / users /のすべてのテンプレートからこれらの属性を削除します。
7 UsersControllerでStrong parameterを定義してフォームの特定の属性の値を取得できるように設定します。
class UsersController < ApplicationController # ... private def user_params # paramsオプジェクトとして受け取れる値は、userテーブルのemail属性、password属性、password_confirmation属性の値のみと設定。 # ここで、password属性、password_confirmation属性の2つの属性は仮想属性であり、実際にはDBに存在しない属性です。passwordとpassword_confirmationの双方の値が一致しているか確認した後、暗号化されたものがDBに保存されます。 params.require(:user).permit(:email, :password, :password_confirmation) end end
8 暗号化されたパスワードがDBに保存される前に、ユーザーが入力したパスワードをフォームで受け取るために仮想フィールドをビュー(今回はビューテンプレート)に追加する必要があります。
# app/views/users/_form.html.erb <div class="field"> <%= form.label :password %><br /> <%= form.password_field :password %> </div> <div class="field"> <%= form.label :password_confirmation %><br /> <%= form.password_field :password_confirmation %> </div>
9 Userモデルに以下の記載をします。validates :password, confirmation: trueをUserモデルに設定することで、Userモデルにpassword属性とpassword_confirmation属性が定義されます。
(この2つはuserテーブルのcrypted_passwordカラムに紐づくUserモデルの仮想属性です。) この2つの仮想属性は暗号化されてから、userテーブルでcrypted_passwordカラムに変更されるようになっています。
# app/models/user.rb class User < ActiveRecord::Base authenticates_with_sorcery! validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] } validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] } validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] } validates :email, uniqueness: true end
10 最後に、ルーティングを定義します。
# config/routes.rb root :to => 'users#index' resources :users
アプリを実行し、http://localhost:3000/usersにアクセスすると、ユーザー新規登録ページに遷移し、そこで新しいユーザーを作成できます。Soceryを使用しているため、パスワードは自動的に暗号化され、ソルトも自動作成されます。
Sorceryを使って、ログイン・ログアウト機能を実装する
1 . ユーザーのログイン状態を維持し続けるために、以下のコマンドをターミナル上で実行します。このコマンドを実行することで、ユーザーのログインとログアウトに対応したコントローラとビューが作成されます。ログインとログアウト機能で、セッションを使用するので、コントローラー名をUserSessionsと設定しています。
$ rails g controller UserSessions new create destroy
2 . 上記で作成したコントローラーにログインとログアウト処理をメソッドとして記載します。
# app/controllers/user_sessions_controller.rb class UserSessionsController < ApplicationController # ログイン処理 def create # loginメソッドを使って、paramsで取得したemailによるUser検索、パスワードの検証を行い、正常に処理できるとセッションデータにUserレコードのid値を@userに格納する @user = login(params[:email], params[:password]) if @user # @userがnullではない時、ログインできている時 # ログインが成功すると、ログイン成功のフラッシュメッセージを表示するとともに、ユーザーが要求したページにリダイレクトする。 redirect_back_or_to(:users, notice: 'Login successful') else # @userがnullの時、ログインできていない時 # ログインに失敗すると、ログイン失敗フラッシュメッセージを表示して、新規登録ページに遷移する。 flash.now[:alert] = 'Login failed' render action: 'new' end end # ログアウト処理 def destroy # logoutメソッドでセッションをリセットする logout redirect_to(:users, notice: 'Logged out!') end end
loginメソッドで、認証処理が行われる。 @user = login(params[:email], params[:password])でemailによるUser検索、パスワードの検証を行い、正常に処理できるとセッションデータにUserレコードのid値を格納する、という処理が行われている。
logoutメソッドで、セッションをリセットする。
redirect_back_or_toメソッド・・・保存されたURLがある場合そのURLに、ない場合は指定されたURLにリダイレクトします。ユーザーがリクエストしたURLがあるときはそのURLにリクエストを、リクエストURLがない場合は、指定されたURLにリダイレクトします。 例えば、掲示板ページにアクセスしようとしたユーザにログインを要求する場合、require_loginメソッドでユーザをログインページに誘導し、ログインが成功したら、最初に訪れようとしていた掲示板ページにリダイレクトさせるということが可能になる。
3 . ログインフォームを作成する
# app/views/user_sessions/new.html.erb <h1>Login</h1> <%= render 'form' %> <%= link_to 'Back', users_path %>
# app/views/user_sessions/_form.html.erb <%= form_with url: login_path, method: :post do |f| %> <div class="field"> <%= f.label :email %><br /> <%= f.text_field :email %> </div> <div class="field"> <%= f.label :password %><br /> <%= f.password_field :password %> </div> <div class="actions"> <%= f.submit "Login" %> </div> <% end %>
4 . ルーティングを定義する
# config/routes.rb root :to => 'users#index' resources :users get 'login' => 'user_sessions#new', :as => :login post 'login' => "user_sessions#create" post 'logout' => 'user_sessions#destroy', :as => :logout
5. 現在は、ログアウトしているユーザーでもログインしているユーザーと同じような操作が出来てしまうので、操作の前にログイン認証
を行うようにします。ただし、ログアウト時でもできる操作に関しては、Skip処理
をすることで、ログイン認証を行わず、操作ができるように設定します。
# app/controllers/application_controller.rb # 全てのアクション実行前にログイン認証を必須とする。 before_action :require_login
# app/controllers/users_controller.rb # index, new, createアクション実行時のみ、ログイン認証処理を必須としない。 skip_before_action :require_login, only: [:index, :new, :create]
# app/controllers/user_sessions_controller.rb # new, createアクション実行時のみ、ログイン認証を必須としない skip_before_action :require_login, only: [:new, :create]
6 . ログインしていないユーザーがログインユーザー専用のページにアクセスするのを防ぐため、以下のコードをapplication_controller.rbに記載します。そうすることで、もしログインしていないユーザーがログインユーザー専用のページにアクセスした際、ログインページに遷移します。
# app/controllers/application_controller.rb private def not_authenticated redirect_to login_path, alert: "Please login first" end
sorceryを使うと使用できるようになるメソッド
require_login
ログインをしていないユーザーをアクション単位で弾く。アクセスしようとしたURLをセッションに格納し、
not_authenticated
を実行するメソッド。以下のようにbefore_action
で指定する。アクションごとに変える場合は、only: :action
を付ける。アクション内の分岐など、もっと細かい単位で弾きたい場合は後述のlogged_in?
を使う。
sorceryのモジュール内に定義されているメソッドで、sorcery gemをインストールすることによって、使えるメソッドになる。ApplicationController内に定義することで、全てのアクション実行前に使えるようになる。
before_action :require_login
not_authenticated
先ほどの
require_login
内で、このメソッドも実行される。デフォルトではredirect_to root_path(自動的にルートに飛ばされる)と定義されているが、カスタマイズしたい場合はapplication_controller
で上書きをする。
class ApplicationController < ActionController::Base protected def not_authenticated redirect_to login_url, alert: 'ログインしてください' end end
current_user
現在ログイン中のuserを返す。コントローラ、ビューで使える。
logged_in?
現在ログイン中かどうか、true or falseで返す。コントローラ、ビューで使える。ログインしているかどうかによって場合分けをしたいときに使うことが多い。
今回は、ヘッダーの表示をログインしているか、ログインしていないかで場合分けするために、logged_in?メソッドを使い、実装しました。
<% if logged_in? %> <%= link_to 'プロフィール', user_url(current_user) %> <% else %> <%= link_to 'ログイン', login_url %> <% end %>
sorceryを使わないで、ログインの有無による、表示分けを行う時は、以下を参照
【Sorcery】Sorceryで使えるようになるメソッドとその活用例 - Qiita
参考
Simple Password Authentication · Sorcery/sorcery Wiki
GitHub - Sorcery/sorcery: Magical Authentication
GitHub - heartcombo/devise: Flexible authentication solution for Rails with Warden.
【Rails】駆け出しVimmerがSorceryをざっくり説明してみた
第16回 データベースからパスワードが盗まれても問題がなくなる方法
CSV形式のファイルのインポート/エクスポートについて
CSV形式とは?
1単語ずつカンマで区切られていて、1行ずつ情報が並んでいます。また、テキストデータとは文字のみのデータのことを指し、ワードやエクセルのように画像が入っていたり、文字サイズや色の装飾などがされていないプレーンな文字データのことを言います。通常のテキストデータの拡張子は「.txt」ですがCSVの場合は「.csv」となります。
# CSV形式の例 氏名,年代,性別,住所 山田 花子,30代,女性,東京都豊島区 田中 太郎,50代,男性,和歌山県有田郡
CSV
は各ソフトウェアが共通で対応している形式のため、異なるソフトウェア間で簡単にデータの移行が可能になっています。
csv形式のデータを扱うには、csvライブラリが必要です。
そのため、ライブラリを取り込んで、使えるように設定が必要です。
require 'csv' ~省略~
読み込み
# CSVライブラリを読み込む require "csv" csv_text = <<~CSV_TEXT Ruby,1995 Rust,2010 CSV_TEXT IO.write "sample.csv", csv_text # ファイルから一行ずつ CSV.foreach("sample.csv") do |row| p row end # => ["Ruby", "1995"] # ["Rust", "2010"] # ファイルから一度に p CSV.read("sample.csv") # => [["Ruby", "1995"], ["Rust", "2010"]] # 文字列から一行ずつ CSV.parse(csv_text) do |row| p row end # => ["Ruby", "1995"] # ["Rust", "2010"] # 文字列から一度に p CSV.parse(csv_text) # => [["Ruby", "1995"], ["Rust", "2010"]]
書き込み
# CSVライブラリを読み込む require 'csv' # ファイルへ書き込み CSV.open("path/to/file.csv", "wb") do |csv| csv << ["row", "of", "CSV", "data"] csv << ["another", "row"] # ... end # 文字列へ書き込み csv_string = CSV.generate do |csv| csv << ["row", "of", "CSV", "data"] csv << ["another", "row"] # ... end
1行変換
# CSVライブラリを読み込む require 'csv' csv_string = ["CSV", "data"].to_csv # => "CSV,data" csv_array = "CSV,String".parse_csv # => ["CSV", "String"]
現場Rails p312[csv出力する(エクスポート)]
app/models/task.rb
#タスクデータをCSVファイルとして出力する方法 class Task < ApplicationRecord # どの属性をどの順番で出力するかを定義してcsv_attributesメソッドで要素に属性名の文字列を持つ配列を作成します。 def.csv_attributes ["name", "description", "created_at", "updated_at"] end # CSV形式でインスタンスの中身を出力できるようにします。 def self.generate_csv # CSV.geterateでCSVデータの文字列を生成する CSV.generate(headers: true) do |csv| # headers: trueはデータベースの一番上の行(ヘッダー)をCSVのレコードのタイトルをとして指定します。 # csvに上記で定義したcsv_attributesを一行目として代入します。 csv << csv_attributes all.each do |task| # 省略前は、Task.all.each do |task| # Task.allでタスクを全件取得。その後、eachでtaskを一つ一つ取り出してtaskの値を代入していきます。 csv << csv_attributes.map{ |attr| task.send(attr) } # mapはcsv_attributesの配列の中身全てに対して処理を行います。 # この時、|attr|には["name", "description", "created_at", "updated_at"]が順番に入って処理に渡されます。 # task.sendは引数に指定したメソッドを文字列で返します。 # 文字列として返った属性名の中身をCSVに追加します。 end end end private ~省略~ end
<<
は配列の末尾に要素を追加するメソッド
Array#<< (Ruby 3.0.0 リファレンスマニュアル)
map() メソッド
は、与えられた関数を配列のすべての要素に対して呼び出し、指定した処理の結果を要素にした新しい配列を生成します。
# 配列の要素をすべて 3 倍にする→ その結果を新しい配列として生成する。 # 配列pの要素を順番に変数|n|に取り出してきて、そのnを使って、{ブロック内の後ろの処理を行う}。 # n * 3をした結果を新たな要素として配列を作成する。 p [1, 2, 3].map {|n| n * 3 } # => [3, 6, 9]
Array#collect (Ruby 3.0.0 リファレンスマニュアル)
sendメソッド
は、引数として渡されたメソッド(文字列、シンボル、変数として渡された)を実行し、その実行結果を返します。つまり、レシーバの持っているメソッドを呼び出してくれるメソッドです。 ブロック付きで呼ばれたときはブロックもそのまま引き渡します。
class User def name puts "taro" end end user = User.new #定義したメソッドを呼び出す user.name # => taro # sendを使った書き方 user.send(:name) # => taro user.send("name") # => taro
Object#send (Ruby 3.0.0 リファレンスマニュアル)
CSV.generateメソッド
は、与えられた文字列をラップして CSV のオブジェクトとしてブロックに渡します。ブロック内で CSV オブジェクトに行を追加することができます。ブロックを評価した結果は文字列を返します。
generate(str = "CSVに変換したい文字列", options = Hash.new) {|csv| ... } -> String
CSV.generate (Ruby 3.0.0 リファレンスマニュアル)
app/controller/tasks_controller.rb
# コントローラに異なるフォーマットでの出力機能を追加する def index ~ 略 ~ respond_to do |format| # formatがHTMLとしてアクセスされた場合 format.html # formatがCSVでアクセスされた場合 # send_dataメソッドを使って、レスポンスを送り出します。 # モデルで定義したgererate_csvメソッドを使って、タスクデータをCSV形式にしたものをレスポンスとしています。 format.csv { send_data @tasks.generate_csv, filename: "sample.csv"} end end
参照
【Ruby】よく使う、CSVライブラリを使ったCSV操作 - Qiita
Array#<< (Ruby 3.0.0 リファレンスマニュアル)
Array#collect (Ruby 3.0.0 リファレンスマニュアル)
Object#send (Ruby 3.0.0 リファレンスマニュアル)
【Ruby on Rails】sendメソッドのいろんな書き方 - Qiita
ActiveStorageとは何か?
ActiveStorageとは?
Active Storage
とは、ファイルアップロードを簡単に実装できるgemです。railsの標準のgemで、rails5.2から追加されました。
以前は、CarrierWaveというgemが使われていましたが、Rails5.2からはActiveStorage
が使われることが多くなりました。
ユーザー側から、画像などのファイルをサーバーにアップロードして、サーバーにおいてあるファイルをモデルが取得するためにActiveStorage
が使われます。また、サーバーではなく、Amazon S3, Google Cloud Storage, Microsoft Azure Storageなどのクラウドストレージサービス
に対してファイルをアップロードすることもできます。
例)
SNSを使って、写真や画像を投稿する時、(=画像ファイルをアップロードする時)ActiveStorageを使って、画像ファイルを保存している。保存した場所(サーバーやクラウドストレージサービス)から、取り出してきて、見えるように表示している。
ActiveStorageの導入
#gemのインストールはRailsインストール時に行われているので、不要。 $ rails active_storage:install #=>コマンド実行時に、active_storage_blobs とactive_storage_attachmentsという2つのテーブルを作成するマイグレーションファイルが生成されます。 #migrationファイルをDBに反映させる。 $ rails db:migrate
active_storage_blobs
は実際にアップロードしたファイルが保存されるテーブル(実際はファイル名、ファイルの種類、バイト数、誤り検出符号などのファイルデータを保持するテーブル)で、active_storage_attachments
は実際に操作を行うモデルとactive_storage_blobsを紐づける中間テーブルになります。
ファイルの保存場所を管理する設定の仕方
設定はRails .application .config .active _storage.service
にファイルを保存する場所を名前で指定し、そのファイルの保存場所の名前に対応する設定をconfig/storage.yml
に定義することで行います。
#ファイルを保存する場所をlocalに設定 #デフォルトではdevelopment環境のファイルの管理場所はlocalとなっている config.active_storage.service = :local
test: #testにおいてあるファイルの設定方法。ここで、testを定義している。 service: Disk #testの時は使用するサービスをローカルディスクに設定 root: <%= Rails.root.join("tmp/storage") %> #ファイルを格納する場所 local: #localにおいてあるファイルの設定方法(デフォルト)。ここで、localを定義している。 service: Disk #localの時は使用するサービスをローカルディスクに設定 root: <%= Rails.root.join("storage") %> #ファイルを格納する場所 # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) # amazon: # service: S3 # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> # region: us-east-1 # bucket: your_own_bucket # Remember not to checkin your GCS keyfile to a repository # google: # service: GCS # project: your_project # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> # bucket: your_own_bucket # Use rails credentials:edit to set the Azure Storage secret(as azure_storage:storage_access_key) # microsoft: # service: AzureStorage # storage_account_name: your_account_name # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> # container: your_container_name # mirror: # service: Mirror # primary: local # mirrors: [ amazon, google, microsoft ]
ここのファイルに書いてある情報(ルート、サービスなど)をもとに、モデルが実際のファイルを取得しに行きます。
Active Storageではローカルの他にも様々なサービスを指定することができます。
コメントアウトされている部分をみるとAWSのS3
、googleのGCS
、microsoftのAzureStorage
も使うことができます。使う場合はこちらのコメントアウトを外し、Gemfileに対応したgemを追加する必要があります。
# Active Storageで指定できるservice AWS aws-sdk-s3 # 使用できるgem google google-cloud-storage # 使用できるgem microsoft azure-storage # 使用できるgem
モデルと添付ファイルを紐づける(1つの添付ファイルの場合)
class Task < ApplicationRecord # has_one_attachedを使って、Taskモデルに1つの画像を添付する has_one_attached :image #imageはファイルの呼び名のこと end
:image
はファイルの呼び名で、:photo
、:avatar
、:hoge
など、ファイルの用途に合わせて好きなものを指定できます。以上のように指定することで、active storageが自動でBlobとAttachmentの2つのテーブルを使って、モデルからimageファイル(カラム)を取得できるようにしてくれます。
ここで、指定しているのは画像ファイル(:image)
ですが、モデルに指定することで、まるでモデルのカラムであるかのように扱うことが出来ます。
つまり、imageというカラムで画像ファイルimageを保存しているイメージです。
モデルと添付ファイルを紐づける(複数の添付ファイルの場合)
# 複数の画像ファイルを一つのタスクに紐づける場合,has_one_attachedの代わりにhas_many_attachedを使う class Task < ApplicationRecord has_many_attached :images end
has_one_attachedとhas_many_attached
**has_one_attached**
= レコードとファイルの間に1対1のマッピングを設定します。各レコードには1つのファイルを添付できます。
例)1つのメッセージに1つの画像ファイルを紐づける時に使用します。
- **has_many_attached**
= レコードとファイルの間に1対多の関係を設定します。各レコードには、多数の添付ファイルをアタッチできます。
例)1つのメッセージごとに複数の画像ファイルを紐づける時に使用します。
保存したファイルを表示させるために、ビューを編集する
# カラムとして扱っている画像ファイルを表示する <%= image_tag user.image %> #画像の大きさを調節するためにvariantメソッドを使用する # 画像の幅を統一したい場合はvariantメソッドを使用 # 200x200のサイズで画像が表示 <%= image_tag user.image.variant(resize:'200x200') %>
画像を削除する
# 画像を削除するにはpurgeメソッドを使う user = User.find(params[:id]) user.image.purge
画像の大きさを調節する
デフォルトでは、画像は元のファイルのサイズのままであり、サイズを変更したい場合は、設定が必要になる。
# ImageMagickという画像変換ツールをインストール
$ brew install imagemagick
# ImageMagickをrailsで使えるようにするためmini_magickというgemをインストール gem 'mini_magick' #この後、bundle installコマンドをうつ
# 画像の幅を統一したい場合はvariantメソッドを使用 # 200x200のサイズで画像が表示 <%= image_tag user.image.variant(resize:'200x200') %>
ビューなどに画像ファイルを表示する記述を書いたとしても、StrongParametersを設定していた場合、アップロードしたファイルの情報を無視せず表示するために、新たに許可するパラメータのキーを設定し直す必要があります。
# 許可するパラメータのキーとして:imageを追加 def task params params.require(:task).permit(:name, description, :image) end
ActiveStorageのメソッド
attachメソッド
レシーバーのオブジェクトに対して、指定のファイルを直接紐付けるメソッドです。
# 既存のメッセージに新しい画像を追加 @message.images.attach(params[:images])
attached?メソッド
レシーバのオブジェクトに指定のファイルが紐付いていればtrueを紐付いていなければ、falseを戻り値として返すメソッドです。
# あるメッセージに何らかの画像が添付されているかどうかを調べる @message.images.attached?
variantメソッド
画像を変換する際に使われるメソッドです。このメソッドを使うためには、image_processing gem
をGemfile
に追加する必要があります。
# ブラウザがバリアントURLにヒットすると、Active Storageは元のblobを指定のフォーマットに遅延変換し、新しいサービスのロケーションにリダイレクトします。 # 画像ファイルを100 * 100のサイズに変換する <%= image_tag user.avatar.variant(resize: "100x100") %>