- 投稿日:2020-04-03T22:16:55+09:00
初心者の気持ちがわかる!! Railsチュートリアル第1章(1.1 1.2)
初心者の気持ちがわかる!!Railsチュートリアル 1.1 1.2
投稿の目的
1、私自身(初心者)が躓いた部分の解決方法をできるだけわかりやすく記載(urlのみの場合あり)する事で、初心者の方が躓いてもリタイアする事なく、Railsチュートリアルを完走できることを目的とする。(読者)
2、1、2周目で学んだことをoutputする場とし、曖昧な部分を明確にし、理解を深める。(筆者)
筆者の現状
Railsチュートリアル1周目が終わりました。
とにかく手を動かし、理解は5割ぐらいというところでしょう、、、
そこで2周目は80~90%理解を目指して、進めていくつもりです。
1周目でわかりにくかった所、その際参考にしたサイトなど控えております。
それでは、早速、、、
Railsチュートリアル (1.1 1.2)
1.1 はじめに
1.1.1 前提知識
私は、Railsチュートリアルに取り掛かるまでに,
ProgateのHTML,CSS,コマンドラインコース,Gitコースをやっておきましょう。
完璧にする必要はありません。1、2周ぐらいで十分です。
(私は、上記に加えて、
Progate:Javascript(以後js),jQuery
ドットインストール:HTML,CSS,js,jQuery
を完了させていました。
余裕があればやってみてください。後々必要です。)1.1.2 この本における取り決め
さらっと読んでおきましょう。
1.2 さっそく動かす
1.2.1 開発環境
本チュートリアルでは開発環境はIDE(統合開発環境)であるAWS Cloud9を使用します。
開発環境はローカル開発環境とIDE(統合開発環境)の二つに分けられます。
簡単にいうと、
ローカル開発環境:自分でカスタマイズしないといけない
→初心者には大変、、
IDE:ある程度元からカスタマイズされている。
→煩雑で時間のかかるカスタマイズをしなくてすむ。AWS Cloud9のクラウドIDEを利用する手順はRailsチュートリアルの解説通り。
1.2.2 Railsをインストールする
最初に、Rubyドキュメントのインストールで無駄な時間を使わないための、コマンドを追加する。
(これはrailsチュートリアルに現段階では理解する必要がないとの記載のため追究せず、、)$ printf "install: --no-document \nupdate: --no-document\n" >> ~/.gemrc今回は初めてなので以下に写真も載せておく、
ターミナルに記載していく。
(コピーしてくる時は「$」もコピーしてこないように、、
$は元からコンソールに記載済み)
次に、gemコマンドを使ってrailsライブラリをインストールする。
$ gem install rails -v 5.1.6上記にてrailsの5.1.6バージョンをインストールする。
vは(version)のこと、
-v以降にversion番号をいれる事でversionの指定ができる。Q. ライブラリとは? gemとは? gemコマンドとは?
A.
ライブラリとは、rubyを簡単に速く、書くための便利グッズ的なものです。rubyには便利なライブラリ(パッケージ)がたくさんあります。
今回はrailsというライブラリをインストールします。(今後他のライブラリもどんどんインストールしていきます。)
gemとは、rubyのライブラリ(パッケージ)事をいい、
gemコマンドとは、ライブラリ(パッケージ)を管理するものです。
つまり、gemコマンドでインストールやアンインストールができます。筆者コメント
私自身初心者なので、間違いがあると思います。
間違いがあれば指摘していただけると幸いです。また、
もう少しここを詳しく!!、イマイチわからなかった!!
等あれば、コメントいただけるとできる限り改善しようと思います。
私自身の成長にも繋がると思いますので、、、最後まで、読んでいただきありがとうございました。
- 投稿日:2020-04-03T20:04:42+09:00
【Rails】Ajax通信でコメント機能を実装する
Qiita投稿の背景
Railsアプリケーションでコメント機能を実装する際に、非同期通信(Ajax通信)を活用しています。
しかし、非同期通信(Ajax通信)の全体の流れや、記述の方法をなかなか理解できないため、アウトプット兼自分の備忘録として投稿します。この記事の対象者
- RailsのMVCを理解している方
- 非同期通信(Ajax通信)について、「なんとなく」知っている方
環境
- ruby 2.5.1
- Rails 5.2.4.1
事前準備
モデルとアソシエーション(事前準備①)
user.rbclass User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :buys has_many :comments endbuy.rbclass Buy < ApplicationRecord belongs_to :user has_many :comments, dependent: :destroy endcomment.rbclass Comment < ApplicationRecord belongs_to :user belongs_to :buy endコントローラー(事前準備②)
comments_controller.rbclass CommentsController < ApplicationController def create @comment = Comment.create(comment_params) respond_to do |format| format.html {redirect_to "/buys/#{@comment.buy.id}"} format.json end end private def comment_params params.require(:comment).permit(:text).merge(user_id: current_user.id, buy_id: params[:buy_id]) end endRailsのコントローラーで利用できる respond_to というメソッドは、リクエストがHTMLのレスポンスを求めているのか、それともJSONのレスポンスを求めているのかを条件分岐してくれます。
コメント送信フォームとコメント一覧(事前準備③)
buys/show.html.haml/ 省略 .buy-comment - if current_user .buy-comment__send = form_for [@buy, @comment], html: {class: "buy-comment__send-form", id: "buy-comment__send-form"} do |f| = f.text_area :text, class: "buy-comment__send-text", placeholder: "コメントをする" = f.submit "SEND", class: "buy-comment__send-submit" .buy-comment__title 〈コメント一覧〉 - if @comments .ajax - @comments.each do |comment| .buy-comment__list .buy-comment__list-writter = "#{comment.user.name}:" .buy-comment__list-comment = comment.textコメント送信フォームとコメント一覧は、BUYモデルの詳細ページに表示することとします。
gem'jquery-rails'をインストール
Gemfilegem'jquery-rails'bundle installをします。
application.js//= require jquery ←を追加 //= require rails-ujs //= require activestorage //= require turbolinks //= require_tree .//= require jquery は必ず //= require_tree . より上段に記述する必要があります。
各jsファイルを読み込むより先にjqueryを読み込まないと、各jsファイルでjqueryを使用できないからです。jbuilderを作成する
views/comments/create.json.jbuilderjson.text @comment.text json.user_id @comment.user.id json.user_name @comment.user.namerails newコマンドでアプリケーションを作成した際にgemfileにデフォルトで記述されている'jbuilder'を活用します。'jbuilder'とは、入力データをJSON形式で出力するテンプレートエンジンのことです。
(JSONとは、JavaScript Object Notationの略で、データ交換を行うデータフォーマットの一種です。)
拡張子を「.json.jbuilder」とするとそのビューファイル内でjbuilderが提供する記述を使うことができます。jbuilderは、viewと同じように該当するアクションと同じ名前にする必要があります。
commentのcreateアクションに対応するjbuilderのファイルにするので、作成するのはviews/comments/create.json.jbuilderになります。jbuilderファイルでは基本的に「json.KEY VALUE」という形で書くことができます。
これによって、jbuilderで定義したキーとバリューの形で、javascriptファイルにデータを返すことができます。jsファイルを作成する
comment.jsというファイルをassets/javascripts以下に作成します。
記述は長くなりますが、最終的にはこのような形になります。comment.js$(function(){ function buildHTML(comment){ var html = `<div class = buy-comment__list> <div class = buy-comment__list-writter> ${comment.user_name}: </div> <div class = buy-comment__list-comment> ${comment.text} </div> </div>` return html; } $('#buy-comment__send-form').on('submit', function(e){ e.preventDefault(); var formData = new FormData(this); var url = $(this).attr('action') $.ajax({ url: url, type: "POST", data: formData, dataType: 'json', processData: false, contentType: false }) .done(function(data){ var html = buildHTML(data); $('.ajax').append(html); $('.buy-comment__send-text').val(''); $('.buy-comment__send-submit').prop('disabled', false); }) .fail(function(){ alert('error'); }) }) });記述内容を一つずつ説明していきます。
- 2行目〜12行目
コメントを送信して保存後に、追加するHTMLの箇所を記述しています。このような記述で実現できるのは、テンプレートリテラル記法を使用しているからです。
(テンプレートリテラル記法とは、ダブルクオートやシングルクオートの代わりにバックティック文字で囲むことで、複数行文字列や文字列内挿入機能を使用できます。)
buildHTMLの引数として渡されたcommentはjbuilderのデータであるため、create.json.jbuilder内で定義したキーとバリューの形式で使用することができます。(詳細は後述)
また、「 ${} 」(プレースホルダー)を用いることでテンプレートリテラルの中で変数を使うことができるようになります。
- 13行目
on メソッドを使用して、formのsubmitを押下するとイベントが着火するようにしています。
今回の非同期通信(Ajax通信)の起点となります。
- 14行目
フォームが送信される時、デフォルトの状態だとフォームを送信するための通信が行われるため、preventDefault()を使用してデフォルトのイベントを止めます。非同期通信(Ajax通信)にてcreateアクションを実行するためです。
- 15行目
new FormData(フォーム要素)とすることでFormDataを作成できます。
(FormDataとは、formタグ内の要素をjavascriptのオブジェクトにしたもので、input要素に入力された内容がキーバリュー形式でjavascriptのオブジェクトとして取得できます。)
- 16行目
attr メソッドとは、要素が持つ指定属性の値を返します。要素が指定属性を持っていない場合、関数はundefinedを返します。
イベントが発生した要素のaction属性の値を取得しています。action属性にはフォームの送信先のurlの値が入っています。
- 17行目〜24行目
非同期通信(Ajax通信)でcreateアクションを行うための記述です。 .ajax の部分がメソッドの呼び出しとなっています。この記述によって、画面が遷移することなく、formの内容が保存されるようになります。
ajaxメソッドには、キーバリュー形式で引数を渡します。JavaScriptでは、このような形式のオブジェクトはオブジェクト型と呼ばれます。
dataTypeに'json'を指定することによって、コントローラーからはJSON形式(create.json.jbuilder経由)でレスポンスが返ってきます。(同期通信の場合は、特に指定せずともHTML形式のデータを返すようRailsが動いてくれています。)
オプション 説明 type HTTP通信の種類を記述します。通信方法は、GETとPOSTの2種類があります。 url リクエストを送信する先のURLを記述します。 data サーバに送信する値を記述します。 dataType サーバから返されるデータの型を指定します。 processData デフォルトではtrueになっており、dataに指定したオブジェクトをクエリ文字列に変換する役割があります。 contentType サーバにデータのファイル形式を伝えるヘッダです。デフォルトでは「text/xml」でコンテンツタイプをXMLとして返してきます。 (ajaxのリクエストがFormDataのときは、processDataとcontentTypeについては値を適切な状態で送ることが可能なため、falseにすることで設定が上書きされることを防ぎます。FormDataをつかってフォームの情報を取得した時には必ずfalseにするという認識で構わないかと思います。)
- 25行目と31行目
ajaxメソッドの後につけることで、非同期通信(Ajax通信)が成功した場合と失敗した場合に、条件分岐することができます。いずれも、ajaxメソッドとセットとなるメソッドです。
done は通信が成功したときに、fail は通信が失敗したときに動きます。
- 25行目〜30行目
dataという変数には、ajaxメソッドによるリクエスト(JSON形式で返してねというリクエスト)によって返ってきたレスポンスが代入されます。この場合のレスポンスは、create.json.jbuilderのデータです。
非同期通信(Ajax通信)によるコメント登録が成功した後は、2行目に定義したHTMLを append メソッドで元のHTMLに追加しています。
さらに、val メソッドを使用して、フォームのコメント入力欄を空にしています。
最後に prop メソッドを使用して、disabled属性をfalseにしています。(disabled属性とは、ボタンが押せなく属性のことです。disabled属性をfalseにするということは、ボタンを押せるようにするということです。)
メソッド 説明 append 対象の要素の末尾にテキストやHTML要素を追加できるメソッド val HTMLに記述されているvalue属性を取得できるメソッド prop HTMLに記述されているid・class・name…などの属性や、checked・selected…などのプロパティを取得できるメソッド
- 31行目〜33行目
通信が失敗したときに、alertを出力するようにしています。
処理の流れを整理
実装はここまでで完了です。
最後に処理の流れを確認しておきましょう。
- フォームが送信されたら、javascriptのイベントが発火
- javascriptファイル内で非同期通信でコメントが保存される
- コメントが保存された後のレスポンスは、JSON形式にするように指示
- コントローラー内でrespond_toを使用してHTMLとJSONの場合で処理を分岐
- jbuilderを経由して、作成したメッセージをJSON形式でjavascriptに返す
- jbuilderから返ってきたJSONをdoneメソッドで受取り、HTMLを作成
最後に
非同期通信(Ajax通信)は、どの部分を実装しているのかが大変分かりにくかったです。
(個人的には、多くの記述が一つのjavascriptファイルに集約されているところが難解でした、、、。)全体の流れをしっかり理解した上で、一つずつコードを記述していくという意識が大事だなと感じました。
エンジニア転職に向けて、日々学習中です、、、、、
twitterも繋がっていただけたら嬉しいです。
- 投稿日:2020-04-03T18:05:19+09:00
turbolinkのページごとの使用・不使用について
色々調べたものの、意外と時間を食ったので書きました。
ページごとに消すには、2つの方法がある。
- data-turbolinks="false"を、ページのトップの親要素に入れる.
- content_for?を使用する
1. data-turbolinks="false"を、ページのトップの親要素に入れる.
1ページだけなど、使用しないページが少ない場合は、1番だけで済む。
ポイントは、最上位の親要素にいれること。
他の記事で、親要素に入れると書いてあるが、該当リンクの親要素では効かない。
ページ全体の親要素にいれること。app/views/<article data-turbolinks="false"> <div> <a> </div> </article>こんな感じ。
2. content_for?を使用する
具体的な使用法はこちらに書いてあったので、ご紹介。
基本的には、キーを決めて、そのキーの有無でページの処理を決めるイメージ。https://qiita.com/Cheekyfunkymonkey/items/216bf7426493e6213927
ほとんど1番の方で済みそうですよね。
- 投稿日:2020-04-03T15:22:29+09:00
nginx + unicorn を使用してみた (Mac & Heroku)
nginx + Unicorn + rails の導入方法(ローカル環境)
unicorn
Gemfileにunicornのgemを追加
Gemfile.rb#Gemfileに追加 gem 'unicorn' gem 'unicorn-rails'bundle installを実行する
bundle installunicornの設定ファイルを作成
unicorn.rb# config/unicorn.rb に下記内容を記載 # ※パスは変更する。 rails_root = File.expand_path('../../', __FILE__) worker_processes 2 working_directory rails_root listen "#{rails_root}/tmp/unicorn.sock" pid "#{rails_root}/tmp/unicorn.pid" stderr_path "#{rails_root}/log/unicorn_error.log" stdout_path "#{rails_root}/log/unicorn.log"デーモンとして起動
# 対象のrailsアプリケーションのディレクトリ内にいること unicorn_rails -c config/unicorn.rb -E development -D停止
# 対象のrailsアプリケーションのディレクトリ内にいること kill -QUIT `cat tmp/unicorn.pid`nginx
brewコマンドでインストール
brew install nginx起動確認
sudo nginx設定ファイル編集
バックアップをしてからnginx.conf.defaultをコピーする。
cd /usr/local/etc/nginx
cp nginx.conf nginx.conf.YYYYMMDD
cp nginx.conf.default nginx.conf
nginx.confの編集を行う
※パスは変更する#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 8080; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } # another virtual host using mix of IP-, name-, and port-based configuration # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} # HTTPS server # #server { # listen 443 ssl; # server_name localhost; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; # location / { # root html; # index index.html index.htm; # } #} include servers/*; upstream unicorn { # nginxとunicornの連携 # unicorn.rbで設定したunicorn.sockを指定 server unix:/Users/yotanaka/diveintocode/nginx_rails_app/tmp/unicorn.sock; } server { listen 8081; server_name localhost; root /Users/yotanaka/diveintocode/nginx_rails_app/public; access_log /usr/local/var/log/nginx/nginx_app_access.log; error_log /usr/local/var/log/nginx/nginx_app_access_error.log; client_max_body_size 100m; error_page 500 502 503 504 /500.html; try_files $uri/index.html $uri @unicorn; location @unicorn { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://unicorn; } } }nginx再起動
sudo nginx -s reload起動確認
下記アドレスにアクセスしRailsの画面が表示されることを確認する。
nginx + Unicorn + rails の導入方法(HEROKU環境)
Herokuのアプリケーションを作成する
対象のアプリケーションフォルダ内で下記コマンドを実行する。
rails assets:precompile RAILS_ENV=production git add . git commit -m 'コミットメッセージ' git push heroku master heroku run rails db:migratebuildpacksを導入
下記コマンドを対象のRailsアプリケーションフォルダ内で実行
heroku buildpacks:add heroku-community/nginx設定ファイル修正
アプリケーションフォルダの直下にProcfileを作成する
作成後下記内容を記述
※ vim Procfile
web: bin/start-nginx bundle exec unicorn -c config/unicorn.rb
unicorn.rbを修正する。
※ vim config/unicorn.rb
unicorn.rbrequire 'fileutils' listen '/tmp/nginx.socket' before_fork do |server,worker| FileUtils.touch('/tmp/app-initialized') end修正内容をコミットする
git add . git commit -m 'コミットメッセージ 'Herokuにデプロイする
git push heroku master参考
Nginx+UnicornでRailsを動かすon Mac
heroku-buildpack-nginx
Rails と Rack
なぜrailsの本番環境ではUnicorn,Nginxを使うのか? ~ Rack,Unicorn,Nginxの連携について ~【Ruby On Railsでwebサービス運営】
Rails開発におけるwebサーバーとアプリケーションサーバーの違い(翻訳)
なぜRailsのWebアプリにunicornが必要なのか
Herokuに必要なProcfileの書き方についてまとめておく
unicornの起動と終了
- 投稿日:2020-04-03T13:10:41+09:00
[Rails]httpからhttpsにリダイレクトする方法
はじめに
オリジナルサービスを作成した際に、httpで作成しておりパスワードを入れる時に
「このサイトは危険です!」みたいな警告が流れてきました。
これは。と思い、URLを見るとhttpのままになっていました。パスワード打つ時にこんな警告が流れるともうそのサイト使いたくならないですよね笑結論
単純ですが、
config/environments/production.rb
にこの一文を書き足す・または修正するだけでhttps化できました。config/environments/production.rbconfig.force_ssl = trueまとめ
railsってなんでもありますね。ほんとすごい。
他にも設定することがあるのかもしれませんが、特に現状は問題なく経過しています。
- 投稿日:2020-04-03T12:46:41+09:00
nullを条件にしたクエリでデータを取ってくる方法 in ruby and firebase
一覧表示させるためにwhereを使う
hogehoge.rbdef self.all list = collection.where("format", "=", null).order(:created_at).get end上記のような形でformatというフィールドにnullを持ったドキュメントを全て持ってくるようなクエリを作るようなコードを書いているつもりなのですが、null部分がundefinedのエラーが出てきてしまい、取得することができなさそうなんですね。
ここの記事には、
Firestore select where a field is null [duplicate]
it is not possible to index on a field that is NOT in the document
と書いてあるし、無理なのかとも思ったんですが、更に下の方には、
However there is a workaround which consists in querying documents that contain properties with a null value data type
とあるので、いけそうでもある。
解決策
rubyではnullってnilで表すんだわ
そう、これですね。ruy上ではnullを扱うにはnilにするのでした。hogehoge.rbdef self.all list = collection.where("format", "=", nil).order(:created_at).get endとすれば、firebaseからnullを条件にしてドキュメントを取ってくることができました。
おまけ
データを取得してくる時にコレクションに対して、インデックスがないから作れという形でエラーが出ることがあります。
9: The query requires an index. You can create it here: 長ったらしいURLこんな感じのエラーが出るので、URLをコピペすると、firebaseコンソールに飛び、インデックスを作成するためのダイアログが開くので、ポチッと押してインデックスがビルドされるのを待ってください。ビルドには数分かかります。しばらく経ってまだビルド中みたいになっていてもコンソール自体に更新かけると終わってたりします。
- 投稿日:2020-04-03T11:39:19+09:00
【rspec】confirmダイアログのテスト
開発環境
ruby '2.6.3'
rails '6.0.2'railsでconfirmダイアログのテストをする。
accept_confirm
だけで表示内容の確認とOKを押してくれる- Method: Capybara::Session#accept_confirm
systemテストcontext "削除する" do it "投稿消去" do click_button "消去する" expect{ expect(page.accept_confirm).to eq "本当に削除しますか?" expect(page).to have_content "レビューを消去しました。" }. to change(@user.posts, :count).by(-1) end end注意点
- expectのブロック内にひとつ以上のexpectもしくはfindを入れないと、ダイアログが表示されてacceptされる前に次へ進んでしまうので注意が必要です。 つまり
systemテストcontext "削除する" do it "投稿消去" do click_button "消去する" expect{ expect(page.accept_confirm).to eq "本当に削除しますか?" expect(page).to have_content "レビューを消去しました。" # ↑この一文かsleepが必要です。 }. to change(@user.posts, :count).by(-1) end endその他
slim= button_to "消去する",post_path(@post), method: :delete, data: {confirm: "本当に削除しますか?"}コントローラーdef destroy @post = current_user.posts.find(params[:id]) flash[:notice] = "レビューを消去しました。" @post.destroy redirect_to user_path(current_user.id) end
- 投稿日:2020-04-03T11:01:27+09:00
Remember機能を実装する~Railsチュートリアル9章~
第9章も終了しました。
9章では主にRemember機能の実装方法について学びました。
ここはあまり時間をかけずに少し足早に通りすぎましたね。記憶トークンやRemember me機能についてざっくりとだけ理解した感じです。というのも今回はログイン機能の発展型、応用であるためまだまだ覚えることがたくさんある初学者の自分としては現段階では優先度が低い内容なのかなと感じました。Remember me機能
ユーザーのログイン状態をブラウザを閉じた後でも有効にする機能を実装する。
またこの機能を使うかをユーザーに決めてもらうためチェックボックスをフォームに追加する。
Sessionの永続化を行うために、記憶トークン(要するにパスワードのようなもの)を生成しcookiesメソッドによる永続cookiesの作成や、安全性の高い記憶ダイジェストによるトークン認証に記憶トークンを活用する。パスワードとトークンの違い
パスワードはユーザーが作成・管理する情報
トークンはコンピュータが作成・管理する情報sessionメソッドで保存した情報は自動的に安全が保たれるが、cookiesメソッドに保存する情報はそうはなっていない。特に、cookiesを永続化するとセッションハイジャックという攻撃を受ける可能性があります。(記憶トークンを奪って、特定のユーザーになりすましてログインする)
cookiesを盗み出す有名な方法
(1) 管理の甘いネットワークを通過するネットワークパケットからパケットスニッファという特殊なソフトウェアで直接cookieを取り出す。
対策...Secure Sockets Layer (SSL) をサイト全体に適用して、ネットワークデータを暗号化で保護し、パケットスニッファから読み取られないようにしている。
(2) データベースから記憶トークンを取り出す。
記憶トークンをそのままデータベースに保存するのではなく、記憶トークンのハッシュ値を保存するようにする。
(3) クロスサイトスクリプティング (XSS) を使う。
Railsによって自動的に対策が行われます。具体的には、ビューのテンプレートで入力した内容をすべて自動的にエスケープする。
(4) ユーザーがログインしているパソコンやスマホを直接操作してアクセスを奪い取る。
ログイン中のコンピュータへの物理アクセスによる攻撃については、さすがにシステム側での根本的な防衛手段を講じることは不可能。二次被害を最小限に留めることは可能である。具体的には、ユーザーが (別端末などで) ログアウトしたときにトークンを必ず変更するようにし、セキュリティ上重要になる可能性のある情報を表示するときはデジタル署名 (digital signature) を行うようにする。
記憶トークンと暗号化
まずデータベースに種類=stringのremember_digest属性をApplicationに追加する。
記憶ダイジェスト用に生成したマイグレーション
class AddRememberDigestToUsers < ActiveRecord::Migration[5.0] def change add_column :users, :remember_digest, :string end endこれをデータベースに追加。次は記憶トークンの作成を行う。新しいトークンを作成するためにnew_tokenメソッドを作成する。このダイジェストメソッドではユーザーオブジェクトが不要となるためUserモデルのクラスメソッドとして作成する。
# ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end end次にuser.rememberメソッドを作成する。これは
記憶トークンをユーザーと関連付け、トークンに対応する記憶ダイジェストをデータベースに保存する役割を担う。
またremember_token属性を設定しなければならない。
(要するにパスワードを生成するフェーズ)
attr_accessorを使って「仮想の」属性を作成する。class User < ApplicationRecord attr_accessor :remember_token # 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end endselfキーワードを与えると、この代入によってユーザーのremember_token属性が期待どおりに設定される。
update_attributeメソッドを使って記憶ダイジェストを更新することでこのメソッドはバリデーションを素通りさせる。ログイン状態の保持
記憶トークンをユーザーと関連付け、トークンに対応する記憶ダイジェストをデータベースに保存する役割が完成。
次にユーザーの暗号化済みIDと記憶トークンをブラウザの永続cookiesに保存して、永続セッションを作成する準備ができました。これを実際に行うにはcookiesメソッドを使います。
個別のcookiesは、1つのvalue (値) と、オプションのexpires (有効期限) からできている。ユーザーIDとcookiesに保存するにはcookies[:user_id] = user.idを使用するがこれではIDがそのままcookiesに保存されてしまう。
そのため署名付きcookiesを使用する。cookies.signed[:user_id] = user.idさらに有効期限20年cookiesを設定できるpermanentメソッドを追加するとこうなる。
cookies.permanent.signed[:user_id] = user.idcookiesを設定すると以後のビューでこのようにcookiesからユーザーを取り出せる。
User.find_by(id: cookies.signed[:user_id])cookies.signed[:user_id]では自動的にユーザーIDのcookiesの暗号が解除され、元に戻る。
続いて、bcryptを使ってcookies[:remember_token]がremember_digestと一致することを確認します# 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end end引数がremember_tokenとなっているが、これはattr_accessorで定義したremember_tokenとは無関係のただの引数であることに注意。ここにはログイン時にremember(user)メソッドで設定したcookieに保存されたremember_tokenが代入される。
つまり、remember_tokenを使って設定されたcookieとremember_digestカラムの値が一致するかを検証しており、一致すればtrueを返す。cookiesメソッドでユーザーIDと記憶トークンの永続cookiesを作成する。
# ユーザーのセッションを永続的にする def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end永続セッションの場合は、session[:user_id]が存在すれば一時セッションからユーザーを取り出し、それ以外の場合はcookies[:user_id]からユーザーを取り出して、対応する永続セッションにログインする必要があります
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?(cookies[:remember_token])
log_in user
@current_user = user
endさらにcurrent_userヘルパーを定義する# 記憶トークンcookieに対応するユーザーを返す
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?(cookies[:remember_token]) log_in user @current_user = user end end end新しくログインしたユーザーは正しく記憶される。
ユーザーを忘れる
ユーザーがログアウトできるようにするために、ユーザーを記憶するためのメソッドと同様の方法で、ユーザーを忘れるためのメソッドを定義する。このuser.forgetメソッドによって、user.rememberが取り消さる。
# ユーザーのログイン情報を破棄する def forget update_attribute(:remember_digest, nil) end end永続Sessionからログアウトする
# 永続的セッションを破棄する def forget(user) user.forget cookies.delete(:user_id) cookies.delete(:remember_token) end # 現在のユーザーをログアウトする def log_out forget(current_user) session.delete(:user_id) @current_user = nil end2つの目立たないバグ
実は小さなバグが2つ残っている。
1つ目のバグ
ユーザーは場合によっては、同じサイトを複数のタブ (あるいはウィンドウ) で開いていることもあり、ログアウト用リンクはログイン中のみ表示される。
今のcurrent_userの使い方では、ユーザーが1つのタブでログアウトし、もう1つのタブで再度ログアウトしようとするとエラーになってしまう。これは、もう1つのタブでログアウトし、current_userがnilとなるため、log_outメソッド内のforget(current_user)が失敗してしまうからである。
→この問題を回避するためには、ユーザーがログイン中の場合にのみログアウトさせる必要がある。2番目の問題は、ユーザーが複数のブラウザ (FirefoxやChromeなど) でログインしていたときに起こります。例えば、Firefoxでログアウトし、Chromeではログアウトせずにブラウザを終了させ、再度Chromeで同じページを開くと起こる。例えばユーザーがFirefoxからログアウトすると、user.forgetメソッドによってremember_digestがnilになります。この時点では、Firefoxでまだアプリケーションが正常に動作しているがlog_outメソッドによってユーザーIDが削除されるため、ハイライトされている2つの条件はfalseになります。
記憶トークンcookieに対応するユーザーを返す
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?(cookies[:remember_token]) log_in user @current_user = user end end end結果として、current_userメソッドの最終的な評価結果は、期待どおりnilになる。
一方、Chromeを閉じたとき、session[:user_id]はnilになります (これはブラウザが閉じたときに、全てのセッション変数の有効期限が切れるためです)。しかし、cookiesはブラウザの中に残り続けているため、Chromeを再起動してサンプルアプリケーションにアクセスすると、データベースからそのユーザーを見つけることができてしまいます。
# 記憶トークンcookieに対応するユーザーを返す 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?(cookies[:remember_token]) log_in user @current_user = user end end end結果として、次のif文の条件式が評価される。
user && user.authenticated?(cookies[:remember_token])このとき、userがnilであれば1番目の条件式で評価は終了するのですが、実際にはnilではないので2番目の条件式まで評価が進み、そのときにエラーが発生します。原因は、Firefoxでログアウトしたときにユーザーのremember_digestが削除してしまっているにもかかわらず、Chromeでアプリケーションにアクセスしたときに次の文を実行してしまうからです。
BCrypt::Password.new(remember_digest).is_password?(remember_token)上のremember_digestがnilになるので、bcryptライブラリ内部で例外が発生します。この問題を解決するには、remember_digestが存在しないときはfalseを返す処理をauthenticated?に追加する必要があります。
テスト駆動開発は、この種の地味なバグ修正にはうってつけです。そこで、2つのエラーをキャッチするテストから書いていくことにしましょう。まずはリスト 8.31の統合テストを元に、redになるテストを作成する。
リスト 9.14: ユーザーログアウトのテスト red
test/integration/users_login_test.rb
require 'test_helper'class UsersLoginTest < ActionDispatch::IntegrationTest . . . test "login with valid information followed by logout" do get login_path post login_path, params: { session: { email: @user.email, password: 'password' } } assert is_logged_in? assert_redirected_to @user follow_redirect! assert_template 'users/show' assert_select "a[href=?]", login_path, count: 0 assert_select "a[href=?]", logout_path assert_select "a[href=?]", user_path(@user) delete logout_path assert_not is_logged_in? assert_redirected_to root_url # 2番目のウィンドウでログアウトをクリックするユーザーをシミュレートする delete logout_path follow_redirect! assert_select "a[href=?]", login_path assert_select "a[href=?]", logout_path, count: 0 assert_select "a[href=?]", user_path(@user), count: 0 end endcurrent_userがないために2回目のdelete logout_pathの呼び出しでエラーが発生し、テストスイートは redになる。
次にこのテストを成功させます。具体的にはリスト 9.16のコードで、logged_in?がtrueの場合に限ってlog_outを呼び出すように変更します。
ログイン中の場合のみログアウトする green
app/controllers/sessions_controller.rb class SessionsController < ApplicationController . . . def destroy log_out if logged_in? redirect_to root_url end end2番目の問題は、統合テストで2種類のブラウザをシミュレートは困難である。その代わり、同じ問題をUserモデルで直接テストするだけなら簡単に行えます。記憶ダイジェストを持たないユーザーを用意し (setupメソッドで定義した@userインスタンス変数ではtrueになります)、続いてauthenticated?を呼び出す。この中で、記憶トークンを空欄のままにしていることにご注目ください。記憶トークンが使われる前にエラーが発生するので、記憶トークンの値は何でも構わないのです。
ダイジェストが存在しない場合のauthenticated?のテスト red
test/models/user_test.rb require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end . . . test "authenticated? should return false for a user with nil digest" do assert_not @user.authenticated?('') end end上のコードではBCrypt::Password.new(nil)でエラーが発生するため、テストスイートは redになる。
このテストを greenにするためには、記憶ダイジェストがnilの場合にfalseを返すようにすれば良いauthenticated?を更新して、ダイジェストが存在しない場合に対応 green
app/models/user.rb class User < ApplicationRecord . . . # 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end# ユーザーのログイン情報を破棄する
def forget update_attribute(:remember_digest, nil) end endここでは、記憶ダイジェストがnilの場合にはreturnキーワードで即座にメソッドを終了している。処理を中途で終了する場合によく使われるテクニックである。
- 投稿日:2020-04-03T10:37:46+09:00
herokuでデプロイしたDBにSequelProで接続してみる
- 投稿日:2020-04-03T07:51:13+09:00
Active Storageでファイルアップロード時に403エラー
現象
Active StorageでS3にファイルアップロードした際に以下のエラーダイアログを表示し、エラー。
3回に1回くらいの割合で失敗しているため、CORSやパーミッションのエラーではない。Error storing "ファイル名.pdf". Status: 403Chrome Developer ToolsのNetworkタブ
Request URL: 割愛(ActiveStorageのurl_forヘルパー押下により作成されたリンク) Request Method: PUT Status Code: 403 Forbidden Remote Address: XXX.XXX.XXX.XXX:443 Referrer Policy: strict-origin-when-cross-origin環境
- Ruby 2.7.0
- Rails 6.0.2.1
- Active Storage 6.0.2.1
- S3
原因
セキュリティ対策として、S3の署名付きURLの有効期限を1秒にしていたことが原因と思われる。
active_storage.rbRails.application.config.active_storage.service_urls_expire_in = 1.second対応
有効期限を適切に(今回は10秒)変更した。
active_storage.rbRails.application.config.active_storage.service_urls_expire_in = 10.second
- 投稿日:2020-04-03T05:01:48+09:00
【Rails】ユーザーページのURLをユーザーネームにする(twitter.com/hanamichi10みたいにする)
【結論】
ルーティング
routes.rbget '/mypage' => 'users#mypage' get '/:username' => 'users#show' get '/:username/edit' => 'users#edit' patch '/:username' => 'users#update'リンクの作り方
<div><%= link_to "#{user.username}のユーザーページ", "/#{user.username}" %></div> <div><%= link_to 'マイページ', "/#{current_user.username}" %></div> <div><%= link_to "プロフィール編集", "/#{current_user.username}/edit" %></div>こんな感じ。
現状どうで、どんなことしたくて、どうやったか
の順番に述べます。誰かの役に立てることを願って。【現状】
【Rails】Deviseを使ってユーザーページ/マイページを作る。プロフィール編集機能も実装する。
を参照してください。テーブル
- Userテーブル
カラム: email, password, username
ルーティング
routes.rbget '/mypage' => 'users#mypage' resources :users, only: [:show, :edit, :update]コントローラー
users_controller.rbbefore_action :authenticate_user!, only: [:mypage, :edit, :update] before_action :set_user, only: [:show, :edit, :update] def mypage redirect_to user_path(current_user) end def show end def edit unless @user == current_user redirect_to user_path(@user) end end def update if current_user.update(user_params) redirect_to user_path(current_user) else redirect_to edit_user_path(current_user) end end private def set_user @user = User.find([:id]) end def user_params params.fetch(:user, {}).permit(:username) endusers/show.html.erb<% if user_signed_in? && @user == current_user %> <h1>マイページ</h1> <% else %> <h1>ユーザーページ</h1> <% end %> <h1><%= @user.username %></h1> <% if user_signed_in? && @user == current_user %> <%= link_to "プロフィール編集", edit_user_path(current_user) %> <% else %>【】やりたいこと
ユーザーページを表示するには、現状
appname.com/users/1
にアクセスする仕組みになっている。
これを、appname.com/hanamichi10
のように、appname.com/ユーザーネーム
にアクセスして表示できるようにしたい。
twitter.comとかinstagram.comみたいに.【手順】
ルーティング
routes.rbget '/mypage' => 'users#mypage' get '/:username' => 'users#show' get '/:username/edit' => 'users#edit' patch '/:username' => 'users#update'ここが重要。
まずはルーティングを変更する。
urlのparamsを:usernameで受け取る仕様にした。自分のアプリケーションに追加で実装する場合は、書く位置にも注意。
これらをこの順番で一番下に書くことをおすすめする。
一番下に書かないと、他の全てのurlはget '/:username'
として処理されてしまう。コントローラー
ルーティングの変更に伴ってパスが変わる。
users_controller.rbdef mypage - redirect_to user_path(current_user) + redirect_to "/#{current_user.username}" end def show end def edit unless @user == current_user - redirect_to user_path(@user) + redirect_to "/#{@user.username}" end end def update if current_user.update(user_params) - redirect_to user_path(current_user) + redirect_to "/#{current_user.username}" else - redirect_to edit_user_path(current_user) + redirect_to "/#{current_user.username}/edit" end end private def set_user - @user = User.find([:id]) + @user = User.find_by(username: params[:username]) end def user_params params.fetch(:user, {}).permit(:username) endビュー
- マイページへのリンク
<div><%= link_to 'マイページ', "/#{current_user.username}" %></div>
- 他人のユーザーページへのリンク
<div><%= link_to "#{user.username}のユーザーページ", "/#{user.username}" %></div>
- プロフィール編集
users/show.html.erb<div><%= link_to "プロフィール編集", "/#{current_user.username}/edit" %></div>
- プロフィール編集ページ
users/edit.html.erb<h1>プロフィール編集</h1> <%= form_with(model: @user, url: "/#{@user.username}") do |form| %> <div>username<%= form.text_field :username %></div> <div><%= form.submit "保存" %></div> <% end %>
- 投稿日:2020-04-03T04:17:54+09:00
【Rails】Deviseを使ってユーザーページ/マイページを作る。プロフィール編集機能も実装する。
Deviseを使ってユーザーページを作る
deviseをインストール
Gemfilegem 'devise'$ bundle install $ rails g devise:installdeviseでUserモデルを作成(usernameカラムを持つ)
$ rails g devise User username $ rails db:migrateユーザーページのために、deviseと別でUsersコントローラーを作成
$ rails g controller Users showユーザーページのルーティングを編集
routes.rb- get 'users/show' => 'users#show' + resources :users, only: [:show]Usersコントローラーを編集
user_controller.rbbefore_action :set_user, only: [:show] def show end private def set_user @user = User.find([:id]) endユーザーページを編集
users/show.html.erb<h1><%= @user.username %>のユーザーページ</h1>動作確認
アカウントを2人作成
password username a@a aaaaaa hanamichi10 b@b bbbbbb kaede11 $ rails c > User.create(email: "a@a", password: "aaaaaa", usename: "hanamichi10") > User.create(email: "b@b", password: "bbbbbb", username: "kaede11")表示
$ rails s
して、localhost:3000/users/1'
にアクセスすれば、hanamichi10のユーザーページ
localhost:3000/users/2'
にアクセスすれば、kaede11のユーザーページ
と表示されるはず。
【+1】ユーザーページのビューを使ってマイページを作ろう
マイページがユーザーページと違うのは、
- 表示するにはログインが必要
- プロフィール編集機能がある
大きくはこの2点だろう。
順番に実装していく。マイページを表示する前にログインを要求する
mypageアクションを追加
deviseのauthenticate_userを使いたいが、
ユーザーページはログインなしでも表示できるようにしたいので、Usersコントローラーのshowアクションには付けられない。よって、仕方なく、アクションをもう一つ作る。
users_controller.rbdef mypage endブサイクだけど我慢。
ルーティング
ルーティングは、
routes.rbget '/mypage' => 'users#mypage'とする。
コントローラー編集
mypageアクションにauthentivcate_userを付ける。
users_controller.rbbefore_action :authenticate_user!, only: [:mypage] def mypage endそして、mypageアクションからshowアクションにリダイレクトする。
全体はこうなる.users_controller.rbbefore_action :authenticate_user!, only: [:mypage] before_action :set_user, only: [:show] def mypage redirect_to user_path(current_user) end def show end private def set_user @user = User.find([:id]) endビューも少しいじる
users/show.html.erb<% if user_signed_in? && @user == current_user %> <h1>マイページ</h1> <% else %> <h1>ユーザーページ</h1> <% end %> <h1><%= @user.username %></h1>動作確認
これで、ただユーザーページを表示したい時は、ログイン不要。
マイページを表示したい時は、ログインを要求する仕組みになったはず。
$ rails s
して、ログアウトしていることを確認してから、
localhost:3000/mypage
にアクセスする.
ログイン画面を経由してマイページが表示されればOK
ログアウトするには、ログアウトボタンを画面に用意しておくのが便利。(次項)【番外編】application.html.erbに追記しておくと便利な代物
application.html.erb<div><%= link_to 'マイページ', mypage_path %></div> <% if user_signed_in? %> <div><%= link_to 'ログアウト', destroy_user_session_path, method: :delete %></div> <% else %> <div><%= link_to 'ログイン', new_user_session_path %></div> <div><%= link_to 'アカウント作成', new_user_registration_path %></div> <% end %>プロフィール編集機能を実装する
マイページが完成したので、プロフィール編集機能を実装する。
editアクションとupdateアクションを追加する
users_controller.rbdef edit end def update endルーティング
routes.rb- resources :users, only: [:show] + resources :users, only: [:show, :edit, :update]コントローラーを編集
users_controller.rb- before_action :authenticate_user!, only: [:mypage] + before_action :authenticate_user!, only: [:mypage, :edit, :update] - before_action :set_user, only: [:show] + before_action :set_user, only: [:show, :edit, :update] def mypage redirect_to user_path(current_user) end def show end def edit # 編集するユーザーが本人じゃない場合はユーザーページにリダイレクトする # これをしないと、ログインさえしていれば、"/users/5/edit"みたいな適当なurlにアクセスすると、他の人のプロフィール編集画面を表示できてしまう + unless @user == current_user + redirect_to user_path(@user) + end end def update # アップデートがうまくいけば、マイページに戻利、 # うまくいかなければ、もう一度編集ページを表示する + if current_user.update(user_params) + redirect_to user_path(current_user) + else + redirect_to edit_user_path(current_user) + end end private def set_user @user = User.find([:id]) end + def user_params + params.fetch(:user, {}).permit(:username) + endビュー編集・追加
編集
users/show.html.erb<% if user_signed_in? && @user == current_user %> <h1>マイページ</h1> <% else %> <h1>ユーザーページ</h1> <% end %> <h1><%= @user.username %></h1> + <% if user_signed_in? && @user == current_user %> + <%= link_to "プロフィール編集", edit_user_path(current_user) %> + <% else %>追加
users/edit.html.erb<h1>プロフィール編集</h1> <%= form_with model: @user do |form| %> <div>username<%= form.text_field :username %></div> <div><%= form.submit "保存" %></div> <% end %>動作確認
マイページにアクセス > プロフィール編集ボタン > フォーム入力、保存ボタン >
マイページが表示されて、ユーザーネームが更新されていればOK
- 投稿日:2020-04-03T04:17:54+09:00
【Rails】Deviseを使ってユーザーページ/マイページを作成・プロフィール編集機能も実装(usernameカラムを編集)
Deviseを使ってユーザーページを作る
deviseをインストール
Gemfilegem 'devise'$ bundle install $ rails g devise:installdeviseでUserモデルを作成(usernameカラムを持つ)
$ rails g devise User username $ rails db:migrateユーザーページのために、deviseと別でUsersコントローラーを作成
$ rails g controller Users showユーザーページのルーティングを編集
routes.rb- get 'users/show' => 'users#show' + resources :users, only: [:show]Usersコントローラーを編集
user_controller.rbbefore_action :set_user, only: [:show] def show end private def set_user @user = User.find([:id]) endユーザーページを編集
users/show.html.erb<h1><%= @user.username %>のユーザーページ</h1>動作確認
アカウントを2人作成
password username a@a aaaaaa hanamichi10 b@b bbbbbb kaede11 $ rails c > User.create(email: "a@a", password: "aaaaaa", usename: "hanamichi10") > User.create(email: "b@b", password: "bbbbbb", username: "kaede11")表示
$ rails s
して、localhost:3000/users/1'
にアクセスすれば、hanamichi10のユーザーページ
localhost:3000/users/2'
にアクセスすれば、kaede11のユーザーページ
と表示されるはず。
【+1】ユーザーページのビューを使ってマイページを作ろう
マイページがユーザーページと違うのは、
- 表示するにはログインが必要
- プロフィール編集機能がある
大きくはこの2点だろう。
順番に実装していく。マイページを表示する前にログインを要求する
mypageアクションを追加
deviseのauthenticate_userを使いたいが、
ユーザーページはログインなしでも表示できるようにしたいので、Usersコントローラーのshowアクションには付けられない。よって、仕方なく、アクションをもう一つ作る。
users_controller.rbdef mypage endブサイクだけど我慢。
ルーティング
ルーティングは、
routes.rbget '/mypage' => 'users#mypage'とする。
コントローラー編集
mypageアクションにauthentivcate_userを付ける。
users_controller.rbbefore_action :authenticate_user!, only: [:mypage] def mypage endそして、mypageアクションからshowアクションにリダイレクトする。
全体はこうなる.users_controller.rbbefore_action :authenticate_user!, only: [:mypage] before_action :set_user, only: [:show] def mypage redirect_to user_path(current_user) end def show end private def set_user @user = User.find([:id]) endビューも少しいじる
users/show.html.erb<% if user_signed_in? && @user == current_user %> <h1>マイページ</h1> <% else %> <h1>ユーザーページ</h1> <% end %> <h1><%= @user.username %></h1>動作確認
これで、ただユーザーページを表示したい時は、ログイン不要。
マイページを表示したい時は、ログインを要求する仕組みになったはず。
$ rails s
して、ログアウトしていることを確認してから、
localhost:3000/mypage
にアクセスする.
ログイン画面を経由してマイページが表示されればOK
ログアウトするには、ログアウトボタンを画面に用意しておくのが便利。(次項)【番外編】application.html.erbに追記しておくと便利な代物
application.html.erb<div><%= link_to 'マイページ', mypage_path %></div> <% if user_signed_in? %> <div><%= link_to 'ログアウト', destroy_user_session_path, method: :delete %></div> <% else %> <div><%= link_to 'ログイン', new_user_session_path %></div> <div><%= link_to 'アカウント作成', new_user_registration_path %></div> <% end %>プロフィール編集機能を実装する
マイページが完成したので、プロフィール編集機能を実装する。
editアクションとupdateアクションを追加する
users_controller.rbdef edit end def update endルーティング
routes.rb- resources :users, only: [:show] + resources :users, only: [:show, :edit, :update]コントローラーを編集
users_controller.rb- before_action :authenticate_user!, only: [:mypage] + before_action :authenticate_user!, only: [:mypage, :edit, :update] - before_action :set_user, only: [:show] + before_action :set_user, only: [:show, :edit, :update] def mypage redirect_to user_path(current_user) end def show end def edit # 編集するユーザーが本人じゃない場合はユーザーページにリダイレクトする # これをしないと、ログインさえしていれば、"/users/5/edit"みたいな適当なurlにアクセスすると、他の人のプロフィール編集画面を表示できてしまう + unless @user == current_user + redirect_to user_path(@user) + end end def update # アップデートがうまくいけば、マイページに戻利、 # うまくいかなければ、もう一度編集ページを表示する + if current_user.update(user_params) + redirect_to user_path(current_user) + else + redirect_to edit_user_path(current_user) + end end private def set_user @user = User.find([:id]) end + def user_params + params.fetch(:user, {}).permit(:username) + endビュー編集・追加
編集
users/show.html.erb<% if user_signed_in? && @user == current_user %> <h1>マイページ</h1> <% else %> <h1>ユーザーページ</h1> <% end %> <h1><%= @user.username %></h1> + <% if user_signed_in? && @user == current_user %> + <%= link_to "プロフィール編集", edit_user_path(current_user) %> + <% else %>追加
users/edit.html.erb<h1>プロフィール編集</h1> <%= form_with model: @user do |form| %> <div>username<%= form.text_field :username %></div> <div><%= form.submit "保存" %></div> <% end %>動作確認
マイページにアクセス > プロフィール編集ボタン > フォーム入力、保存ボタン >
マイページが表示されて、ユーザーネームが更新されていればOK
- 投稿日:2020-04-03T01:31:53+09:00
[refile][heroku]refileを使った本番環境での画像投稿機能[AWS][s3]
はじめに
refileを使って、画像投稿機能を作って、herokuにデプロイして、オリジナルサービス完成!
と思ってたら、思わぬことに。
なぜか投稿した画像が一定時間過ぎると見れなくなる。 とか
パソコンでは見れるのに、iPhoneで画像が見れない。
という問題に直面しました。ので、その状況に陥ったときの対処方法を今回は書いていきます。そもそも
herokuにアップした画像は一定時間しか保存してくれないとのこと(どっかの記事で見ました)。
なので、どこかに保存する場所を作ってあげないといけない。そこで見つけたのが、AWSのs3という機能(?)。
s3のバケットというところに保存すればこの問題は解決しました。
ちょっと長くなりますが、方法をまとめていきたいと思います。どうするか
細かく伝えいきますので、順にやっていってください。
gemの追加
refileには、AWSのs3と連携してくれる便利なgemがあります。それがこれ↓です。(refile/github)
Gemfilegem "refile-s3"これをGemfileに記述した後、
$ bundle install
します。ファイルの追加
config/initializers
の中にrefile.rb
というファイルを作成します。
その後config/initializers/refile.rbrequire "refile/s3" aws = { access_key_id: "AWS_ACCESS_KEY_ID", secret_access_key: "AWS_SECRET_ACCESS_KEY", region: "リージョン", bucket: "バケット名", } Refile.cache = Refile::S3.new(prefix: "cache", **aws) Refile.store = Refile::S3.new(prefix: "store", **aws)これをまるっと書きます。
記載してある"AWS_ACCESS_KEY_ID"
,"AWS_SECRET_ACCESS_KEY"
,リージョン
,バケット名
はまだ置いといていいです。AWSのs3にバケットを作る
このバケットというところに画像を保存していくのですが、まずはAWSのアカウントを作らなくてなりません。
参考になった記事がこちら → Railsでcarrierwaveを使ってAWS S3に画像をアップロードする手順を画像付きで説明するこの記事のAWS/IAM ~ S3バケット作成まで進めてください。かなり分かりやすく、僕でも楽々できました!
!注意!
作成途中で、アクセスキーとシークレットアクセスキーは後で使うので、どこか自分しかわからないところに控えておいてください。
バケットができたら次です。
config/initializers/refile.rb
ここに戻ってきます。
先ほど作成したrefile.rb
の中にあったAWS_ACCESS_KEY_ID
とAWS_SECRET_ACCESS_KEY
ですが、
控えておいたアクセスキーとシークレットアクセスキーを入れることになります。
ただ、ここに直接は書かず、別のファイルに書いていきます。(書いてもいいのかもしれないけど。)
ひとまず、先ほどバケットを作った際にきめたリージョン
とバケット名
を記述してください。
ちなみにリージョンを東京にした場合はap-northeast-1
と記述すればいいです。config/initializers/refile.rbrequire "refile/s3" aws = { access_key_id: "AWS_ACCESS_KEY_ID", secret_access_key: "AWS_SECRET_ACCESS_KEY", region: "リージョン", ←ここと bucket: "バケット名", ←ここ } Refile.cache = Refile::S3.new(prefix: "cache", **aws) Refile.store = Refile::S3.new(prefix: "store", **aws)gemの追加
次に環境変数というものを使うために
gem 'dotenv-rails'を追加し、
$ bundle install
します。
環境変数とはパスワードを公開しないための変数とのこと。(あまりわかっていません。)そのあとホームディレクトリ直下(gemfileとかがあるところ)に、
.env
というファイル名でファイルを作成します。
dotenv
はこの.env
というファイルの中に書いてあるデータを参照します。
なのでここで環境変数を定義していきます。 さきほど控えておいたキーをここに記述します。.envAWS_ACCESS_KEY_ID = アクセスキー AWS_SECRET_ACCESS_KEY = シークレットアクセスキー記述が終わったら、
.gitignore
に先ほどの.env
を記述します。
これはgithubにデプロイしたときに誰にも見られたくない環境変数を隠すためのファイルです。.gitignore/.env
heroku
次にherokuに移動してください。作成したアプリを選択して、
settings
→Config Vars
にあるReveal Config Vars
を押すと
KEY と VALUE を追加する画面が出てきます。
ここで
KEYにはAWS_ACCESS_KEY_ID
, VALUEにはアクセスキー
KEYにはAWS_SECRET_ACCESS_KEY
, VALUEにはシークレットアクセスキー
の2つを記述してください。最後に
$ git add . $ git commit -m "" $ git push heroku master $ heroku run rails db:migrate $ heroku openをして、デプロイできればOKです!
そのあと、画像投稿をしてみて先ほど作成したバケットを確認すると、投稿した画像がそこに入っていると思います!
お疲れ様でした!まとめ
ほんと、これをするためだけに丸2日くらいかかりました…。センスないのかなってめっちゃ思いました。
知らない言葉とか多いけど、やっていくうちに慣れると思うので、今はひたすらにいろんな事を吸収します。とりあえずこの手順でやれば本番環境での画像投稿機能はつけられると思います!
参考記事