- 投稿日:2020-09-17T23:21:13+09:00
常に、自分が投稿したものをログインしている自分だけに表示したい
【概要】
1.結論
2.whereメソッドとは何か
3.どのように使うか
4.ここから学んだこと
1.結論
whereメソッドをつかう!
2.whereメソッドとは何か
欲しい条件を探すメソッドです!
モデル.where(条件名: 条件の内容)で使用できます!
ブログや、ニュース記事での検索ワード欄にて特定の文字を探すプログラムを記載する時に頻繁に使います!
3.どのように使うか
自分は下記のように使用しました!
time_controller.rb@times = Time.where(user_id: current_user.id).includes(:user).order("created_at DESC")現在ログインしているユーザーが投稿した物を、投稿した人=ログインしているユーザー(current_user.id)のみに表示させたかったのでwhereメソッドを使用しました!
includes(:user)はN+1問題解決(プログラムの無駄な処理を軽減)し、order("created_at DESC")は新規投稿順にしています!参考にしたURL:
[Rails]ログインユーザーidを使って、異なるテーブルに存在する値を出力したい[ActiveRecord]
5.ここから学んだこと(エラーの時に使用)
if条件式で場合分けしようとしましたが、いくら条件式作っても表示自体を制限しないと、条件式下で全部表示されてしまいます。whereメソッドは検索ワード欄を作りたいと意識するあまりに、本質は”表示を制限する”ことが抜けていました。具体的な事象➡︎抽象的な事象に変換するともっとメソッドに対しての活用視野が広がると確信しました。
- 投稿日:2020-09-17T22:18:03+09:00
Rails 簡単な簡単ログイン機能の追加
欲しい結果
"簡単ログイン"のボタンを押すと、
登録済みのゲストユーザーでログインするボタンの設置。前提条件
RailsチュートリアルをベースにしたWEBアプリ。
やること
- sessions_controllerのcreateアクションとは別にeasy_loginアクションを作成して登録済みのゲストユーザーの emailを渡す。
- routs.rbにeasy_loginのルーティングを記載。
viewで
簡単ログイン
ボタンを設置する。
sessions_controller.rb
に作成して登録済み(seeds.rb)の
ゲストユーザーでログインできる様にeasy_loginアクションを追記する。sessions_controller.rbdef easy_login user = User.find_by(email: "test@example.com") log_in user #session_helperで事前に定義ずみ。 redirect_back_or user #session_helperで事前に定義ずみ。 end
config/routes.rb
で以下を追記config/routes.rbpost '/easy_login', to: 'sessions#easy_login'
app/views/sessions/new.html.erb
の任意の場所に以下を追記app/views/sessions/new.html.erb<p><%= link_to "簡単ログイン", easy_login_path, method: :post, class: 'btn btn-primary' %></p>結果
とりあえずは簡単ログインボタンから、ゲストユーザーでのログインができた。
- 投稿日:2020-09-17T21:08:31+09:00
《未経験→webエンジニア》実務4日目
【今日やったこと】
APIテスト
SQL文の学習【知らなかったこと】
「クエリ」は、データベースなどへの要求を文字列で表したもの。 「SQL」は、データベースへ問い合わせるための言語仕様。
内部結合は、先にAテーブルの指定カラムを拾った後にBのテーブルを結合する※重なる部分だけ
外部結合は、テーブルを左か右で指定して、その指定したテーブルを基準に結合する※重ならない部分も!
参考URLhttps://qiita.com/naoki_mochizuki/items/3fda1ad6594c11d7b43chttp://www.pursue.ne.jp/jouhousyo/SQLDoc/select22.html・MinIO
S3の互換ツールのイメージ。Docker上で動かせて、コストもかけずにお試しできるので便利らしい※S3だと画像をgitに入れてしまうと重くなってしまうのも難点
参考URLhttps://dev.classmethod.jp/articles/s3-compatible-storage-minio/・冗長な構成冗長構成とは、情報システムなどの構成の一種で、設備や装置を複数用意し、一部が故障しても運用を継続できるようにしたもの。システムなどが持つそのような性質を「冗長性」、そのような構成法を「冗長化」という。
【明日】やるべきこと、読みたい記事など
- 投稿日:2020-09-17T21:05:31+09:00
CarrierWaveで「Nil location provided. Can't build URI.」が出た時の対処法
環境
ubuntu(wsl) Rails 6.0.3 ruby 2.5.1 CarrierWave想定状況と原因
エラーの箇所がこちら
view/user/show.html.slim= link_to image_tag(user.image.url), user一旦Userモデルの中身を調べてみます。
↓Userモデルの中身name: "藤田 翔太", email: "jeromy_weimann@wehner.info", password_digest: [FILTERED], admin: false, image: nil,
image
の値がnil
になっているのが原因のようです。解決策
解決策1. nilとなっているユーザーを削除する。
rails c
などでnilになっているユーザーを探して削除する。解決策2. デフォルト画像の設定をする
image_uploader.rb
に以下を追加します。app/uploaders/image_uploader.rbdef default_url(*args) 'default.png' end
app/assets/images
に画像を置いたら終わりです。参考文献
- 投稿日:2020-09-17T20:42:23+09:00
VSCodeでスペルミスを指摘してくれるCode Spell CheckerをRuby言語に対応させる
はじめに
VSCodeの拡張機能のCode Spell Checkerはスペルミスを指摘してくれる便利な機能ですが初期設定ではRuby言語には対応しておらず、自分で設定しないと動作しません。
この記事ではその設定方法を紹介したいと思います。背景
Railsチュートリアルで学習を進めている時に
rails test
でよくエラーになっていたのですが、そのほとんどがスペルミスによるものでした。
そこでスペルミスをチェックしてくれるCode Spell Checkerという拡張機能を知り、インストールしてみたものの機能しませんでした。
どうやら初期設定ではRubyには対応していないということなので、設定で追加する方法を調べてみました。Code Spell Checkerをインストールしたけど言語が対応していなくて困っている方を対象にこの記事を書きました。
動作環境
macOS Catalina バージョン: 10.15.6
VSCode バージョン: 1.49.0
Code Spell Checker バージョン: 1.9.0設定方法
流れとしては
settings.json
ファイルにcSpell.enabledLanguageIds
を使って言語を追加します。まずは
settings.json
のファイルの開き方。
command+カンマ(,)
で設定画面を開きます。
設定画面の右上にあるアイコンをクリックしてsettings.json
のファイルを開きます。
あとは
settings.json
のファイルに"cSpell.enabledLanguageIds":["言語"]
を追加するだけです。settings.json"cSpell.enabledLanguageIds": [ "css", "html", "javascript", "json", "less", "markdown", "plaintext", "scss", "text", "ruby", "yaml", "yml" ],まとめ
これで追加した言語のファイルにもCode Spell Checkerが動作するようになりました。
詳しくはCode Spell Checkerのサイトに書いてあるので見てみてください。
Code Spell Checker - Visual Studio Marketplace
- 投稿日:2020-09-17T18:41:35+09:00
.erbファイルを.slimに変更してみる
環境
Ruby 2.5.7
Rails 5.2.4経緯
Railsのテンプレートエンジンとして
.erb
を使っているのですが、テンプレートエンジンはそれ以外にも.haml
や.slim
があります。
今回.slim
に興味を持ち、既存の.erb
ファイルの一部を.slim
に変更して動作まで確認できたので、その過程を書いていきます。
ほぼケーススタディとなるので、参考にしていただければと思います。基本構文
基本的な変更点としては次のようなものがあります。
example.html.slim/ 開始タグのみ、終了タグは記述しない <body></body> => body <h1>タイトル</h1> => h1 タイトル / id名class名は続けて書く <ul class="list"></ul> => ul.list <span id="btn"></span> => span#btn / divはdivすらも省略する <div id="main-contents" class="flex container" ></div> => #main-contents.flex.container / rubyコード<%= %>は省略する(=があるものは=だけ書く) <%= link_to '次へ' %> => = link_to '次へ' / <% %> =が付かないものは、先頭に-をつける / <% end %>は全て省略(ループ文など) <% if 条件文 %> <% else %> <% end %> => - if 条件文 - else構文の詳細はこちらを参照してください。
GitHub - slim
Qiita - 速習テンプレートSlim(HTML作成編)
Qiita - RailsのHTMLテンプレートエンジン、Slimの基本的な記法
Qiita - 【爆速で習得】Railsでslimを使う方法から基本文法まで実際のコードで.erbと.slimを比較
ここからは、私が実際に変更したファイルをbefore/after形式で下記の4種類ご紹介します。
*application.html.erb/slim
*_form.html.erb/slim
*new.html.erb/slim
*edit.html.erb/slimこれらのViewファイルは私のGitHub上でも公開しております。
GitHub - matchi_ver.slimapplication.html
application.html.erb<!DOCTYPE html> <html lang="ja"> <head> <%= favicon_link_tag('favicon.ico') %> <%= favicon_link_tag 'home-icon.png', rel: 'apple-touch-icon', size: '180x180', type: 'image/png' %> <%= favicon_link_tag 'home-icon.png', rel: 'android-touch-icon', size: '192x192', type: 'image/png' %> <title>Matchi</title> <script src="//maps.google.com/maps/api/js?key=<%= ENV['GOOGLE_PLATFORM_API_KEY'] %>"></script> <%= include_gon %> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application' %> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet"> <!-- Global site tag (gtag.js) - Google Analytics --> <script async src="https://www.googletagmanager.com/gtag/js?id=<%= ENV['GOOGLE_ANALYTICS_TRACKING_ID'] %>"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', "<%= ENV['GOOGLE_ANALYTICS_TRACKING_ID'] %>"); </script> </head> <body> <header> <div class="header-container"> <%# PC画面ヘッダー %> <div class="flex pc-header"> <%= link_to root_path do %> <div class="logo-image"></div> <% end %> <div class="header-nav"> <nav> <ul class="flex header-ul"> <% url = request.fullpath %> <%# urlに'owner'があれば店舗用ヘッダー %> <% if url.include?('owner') %> <% if master_admin_signed_in? %> <li><%= link_to '管理者トップ', new_master_admin_session_path %></li> <li><%= link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete %></li> <% end %> <% if owner_restaurant_signed_in? %> <li><%= link_to '店舗トップ', owner_restaurant_path(current_owner_restaurant) %></li> <li><%= link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete %></li> <% end %> <% if public_user_signed_in? %> <li><%= link_to '一般会員TOP', mypage_path(current_public_user) %></li> <li><%= link_to '一般会員ログアウト', destroy_public_user_session_path, method: :delete %></li> <% else %> <li><%= link_to '一般会員ログイン', new_public_user_session_path %></li> <% end %> <%# urlに'master'があれば管理者用ヘッダー %> <% elsif url.include?('master') %> <% if master_admin_signed_in? %> <li><%= link_to '店舗新規登録', new_owner_restaurant_registration_path %></li> <li><%= link_to '管理者トップ', new_master_admin_session_path %></li> <li><%= link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete %></li> <% end %> <% if public_user_signed_in? %> <li><%= link_to '一般会員TOP', mypage_path(current_public_user) %></li> <li><%= link_to '一般会員ログアウト', destroy_public_user_session_path, method: :delete %></li> <% else %> <li><%= link_to '一般会員ログイン', new_public_user_session_path %></li> <% end %> <% if owner_restaurant_signed_in? %> <li><%= link_to '店舗トップ', owner_restaurant_path(current_owner_restaurant) %></li> <li><%= link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete %></li> <% else %> <li><%= link_to '店舗ログイン', new_owner_restaurant_session_path %></li> <% end %> <%# 上記以外なら一般ユーザー用ヘッダー %> <% else %> <% if master_admin_signed_in? && owner_restaurant_signed_in? %> <li><%= link_to '管理者トップ', new_master_admin_session_path %></li> <li><%= link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete %></li> <li><%= link_to '店舗TOP', owner_restaurant_path(current_owner_restaurant) %></li> <li><%= link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete %></li> <% elsif owner_restaurant_signed_in? %> <li><%= link_to '店舗TOP', owner_restaurant_path(current_owner_restaurant) %></li> <li><%= link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete %></li> <% elsif master_admin_signed_in? %> <li><%= link_to '管理者トップ', new_master_admin_session_path %></li> <li><%= link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete %></li> <li><%= link_to '店舗ログイン', new_owner_restaurant_session_path %></li> <% end %> <% if public_user_signed_in? %> <li><%= link_to 'MyPage', mypage_path(current_public_user) %></li> <%# if alert.count >= 1 %> <li><%#= link_to 'お知らせがあります。' %></li> <%# end %> <li><%= link_to 'ログアウト', destroy_public_user_session_path, method: :delete %></li> <% else %> <li><%= link_to '新規登録', new_public_user_registration_path %></li> <li><%= link_to 'ログイン', new_public_user_session_path %></li> <% end %> <% end %> </ul> </nav> </div> </div> <%# スマホ画面ヘッダー %> <div class="flex sp-header"> <div class="hamburger"> <span class="bar bar-top"></span> <span class="bar bar-center"></span> <span class="bar bar-bottom"></span> </div> <%= link_to root_path do %> <div class="logo-image"></div> <% end %> <%# 未読のお知らせの通知 %> <div class="alert"> <i id="alert-bell" class="fa-2x far fa-bell"><div class="hidden icon"></div></i> </div> <%# ハンバーガーメーニュー %> <div class="hamburger-menu"> <ul> <% if public_user_signed_in? %> <li><%= link_to 'MyPage', mypage_path(current_public_user) %></li> <% else %> <li><%= link_to '新規登録', new_public_user_registration_path %></li> <li><%= link_to 'ログイン', new_public_user_session_path %></li> <% end %> <li><%= link_to 'サービス紹介', about_path %></li> <li><%= link_to 'レストラン一覧', public_restaurants_path %></li> <li><%= link_to 'メニュー一覧', public_menus_path %></li> <% if public_user_signed_in? %> <li><%= link_to 'ログアウト', destroy_public_user_session_path, method: :delete %></li> <% end %> </ul> </div> </div> </div> </header> <main> <div class="body-container"> <%= yield %> </div> </main> <footer> <div class="flex footer-container"> <%= link_to root_path do %> <div class="logo-image"></div> <% end %> <div class="footer-menu"> <ul class="footer-links"> <li><%= link_to 'お問い合わせ', contacts_new_path %></li> <li><%= link_to '利用規約', terms_path %></li> <li><%= link_to 'プライバシーポリシー', privacy_path %></li> <li><%= link_to '運営者情報', admin_path %></li> </ul> </div> </div> <div class="copyright"> <small>©︎ 2020 MasaoSasaki</small> </div> <div id="move-head"> <div class="circle move-head"><i class="fa-2x fas fa-arrow-up"></i></div> </div> </body> </footer> </html>application.html.slimdoctype html html lang="ja" head = favicon_link_tag('favicon.ico') = favicon_link_tag 'home-icon.png', rel: 'apple-touch-icon', size: '180x180', type: 'image/png' = favicon_link_tag 'home-icon.png', rel: 'android-touch-icon', size: '192x192', type: 'image/png' title Matchi script src="//maps.google.com/maps/api/js?key=#{ENV['GOOGLE_PLATFORM_API_KEY']}" = include_gon = csrf_meta_tags = csp_meta_tag = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = javascript_include_tag 'application' meta name="viewport" content="width=device-width,initial-scale=1" link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet" / Global site tag (gtag.js) - Google Analytics javascript: async src="https://www.googletagmanager.com/gtag/js?id=#{ENV['GOOGLE_ANALYTICS_TRACKING_ID']}" body header .header-container / PC画面ヘッダー .flex.pc-header = link_to root_path .logo-image .header-nav nav ul.flex.header-ul - url = request.fullpath / urlに'owner'があれば店舗用ヘッダー - if url.include?('owner') - if master_admin_signed_in? li = link_to '管理者トップ', new_master_admin_session_path li = link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete - if owner_restaurant_signed_in? li = link_to '店舗トップ', owner_restaurant_path(current_owner_restaurant) li = link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete - if public_user_signed_in? li = link_to '一般会員TOP', mypage_path(current_public_user) li = link_to '一般会員ログアウト', destroy_public_user_session_path, method: :delete - else li = link_to '一般会員ログイン', new_public_user_session_path / urlに'master'があれば管理者用ヘッダー - elsif url.include?('master') - if master_admin_signed_in? li = link_to '店舗新規登録', new_owner_restaurant_registration_path li = link_to '管理者トップ', new_master_admin_session_path li = link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete - if public_user_signed_in? li = link_to '一般会員TOP', mypage_path(current_public_user) li = link_to '一般会員ログアウト', destroy_public_user_session_path, method: :delete - else li = link_to '一般会員ログイン', new_public_user_session_path - if owner_restaurant_signed_in? li = link_to '店舗トップ', owner_restaurant_path(current_owner_restaurant) li = link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete - else li = link_to '店舗ログイン', new_owner_restaurant_session_path / 上記以外なら一般ユーザー用ヘッダー - else - if master_admin_signed_in? && owner_restaurant_signed_in? li = link_to '管理者トップ', new_master_admin_session_path li = link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete li = link_to '店舗TOP', owner_restaurant_path(current_owner_restaurant) li = link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete - elsif owner_restaurant_signed_in? li = link_to '店舗TOP', owner_restaurant_path(current_owner_restaurant) li = link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete - elsif master_admin_signed_in? li = link_to '管理者トップ', new_master_admin_session_path li = link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete li = link_to '店舗ログイン', new_owner_restaurant_session_path - if public_user_signed_in? li = link_to 'MyPage', mypage_path(current_public_user) / if alert.count >= 1 li = link_to 'お知らせがあります。' li = link_to 'ログアウト', destroy_public_user_session_path, method: :delete - else li = link_to '新規登録', new_public_user_registration_path li = link_to 'ログイン', new_public_user_session_path /スマホ画面ヘッダー .flex.sp-header .hamburger span.bar.bar-top span.bar.bar-center span.bar.bar-bottom = link_to root_path .logo-image / 未読のお知らせの通知 .alert i#alert-bell.fa-2x.far.fa-bell .hidden.icon / ハンバーガーメーニュー .hamburger-menu ul - if public_user_signed_in? li = link_to 'MyPage', mypage_path(current_public_user) - else li = link_to '新規登録', new_public_user_registration_path li = link_to 'ログイン', new_public_user_session_path li = link_to 'サービス紹介', about_path li = link_to 'レストラン一覧', public_restaurants_path li = link_to 'メニュー一覧', public_menus_path - if public_user_signed_in? li = link_to 'ログアウト', destroy_public_user_session_path, method: :delete main .body-container == yield footer .flex.footer-container = link_to root_path .logo-image .footer-menu ul.footer-links li = link_to 'お問い合わせ', contacts_new_path li = link_to '利用規約', terms_path li = link_to 'プライバシーポリシー', privacy_path li = link_to '運営者情報', admin_path .copyright small ©︎ 2020 MasaoSasaki #move-head .circle.move-head i.fa-2x.fas.fa-arrow-upnew.html
new.html.erb<div class="contents menus-new"> <h2>メニュー追加</h2> <%= render partial: 'form', locals: { restaurant: @restaurant, menu: @menu, tags: @tags, menu_tags: @menu_tags, path: owner_restaurant_menus_path, truth: false, submit: '作成' } %> </div>new.html.slim.contents.menus-new h2 メニュー追加 == render 'form', restaurant: @restaurant, menu: @menu, tags: @tags, menu_tags: @menu_tags, path: owner_restaurant_menus_path, truth: false, submit: '作成'edit.html
edit.html.erb<div class="contents menus-edit"> <h2 class="menus-edit-h2">メニュー編集</h2> <%= render partial: 'form', locals: { restaurant: @restaurant, menu: @menu, tags: @tags, menu_tags: @menu_tags, path: owner_restaurant_menu_path, truth: true, submit: '更新' } %> </div>edit.html.slimcontents.menus-edit h2.menus-edit-h2 メニュー編集 == render 'form', restaurant: @restaurant, menu: @menu, tags: @tags, menu_tags: @menu_tags, path: owner_restaurant_menu_path, truth: true, submit: '更新'_form.html
_form.html.erb<div class="menu-form"> <%= form_with model: [restaurant, menu], url: path, local: true do |f| %> <section class="menu-status"> <div class="menu-form1"> <h3>メニュー詳細</h3> <table> <tbody> <tr> <td><%= f.label :title, value: 'メニュー名' %></td> <td><%= f.text_field :title %></td> </tr> <tr> <td><%= f.label :regular_price, value: '正規価格(税抜き):' %></td> <td><%= f.number_field :regular_price %> 円</td> </tr> <tr> <td><%= f.label :discount_price, value: '提供価格(税抜き):' %></td> <td><%= f.number_field :discount_price %> 円</td> </tr> <tr> <td><%= f.label :reservation_method, value: '予約方法' %></td> <td><%= f.select :reservation_method, Menu.reservation_methods.keys.map {|method| [method]} %></td> </tr> <tr> <td><%= f.label :is_sale_frag, value: '販売ステータス' %></td> <td><%= f.select :is_sale_frag, [['販売中', true], ['販売停止中', false]] %></td> </tr> </tbody> </table> </div> <div class="menu-form2"> <h3>メニュー画像</h3> <div class="menu-image"> <%= f.attachment_field :menu_image %> <div class="image-preview"></div> <h4>タグの追加(任意)</h4> <%= text_field_tag :tag_name %> <%= button_tag '追加', type: 'button', class: "add-tag-btn" %> <div id="tag-list"></div> </div> </div> </div> </section> <section class="menu-tag-form"> <h3>タグ詳細</h3> <table> <tbody> <%# 編集画面でのみ表示 %> <% if truth %> <tr> <td><h4>現在のタグ一覧</h4></td> <td> <% menu_tags.each do |menu_tag| %> <div class="menu-tag"> <%= Tag.find(menu_tag.tag_id).name %> <%= link_to 'x', {controller: 'menu_tags', action: 'destroy', tag_id: menu_tag, menu_id: params[:id], restaurant_id: params[:restaurant_id]}, method: :delete %> </div> <% end %> </td> </tr> <% end %> <tr> <td><h4>タグの追加<br>(一つ以上選択推奨)</h4></td> <td> <% tag_count = 0 %> <% tags.each do |tag| %> <%# 推奨タグ7個を表示 %> <% if tag_count < 7 %> <div class="check-box"> <% if menu_tags.exists?(tag_id: tag.id) %> <%= check_box :tag_id, tag.id, checked: true %> <%= label_tag :tag_id, "#{tag.name}"%> <% else %> <%= check_box :tag_id, tag.id %> <%= label_tag :tag_id, "#{tag.name}"%> <% end %> </div> <% tag_count += 1 %> <% else %> <% break %> <% end %> <% end %> </td> </tr> </tbody> </table> </section> <section class="menu-form-area"> <div class="content-form"> <p><%= f.label :content, value: '内容' %></p> <%= f.text_area :content %> </div> <div class="cancel-form"> <p><%= f.label :cancel, value: 'キャンセル規定' %></p> <%= f.text_area :cancel %> </div> </section> <div class="submit"><%= f.button "#{submit}", onclick: 'submit();', type: 'button', class: 'btn' %></div> <% end %> </div>_form.html.slim.menu-form = form_with model: [restaurant, menu], url: path, local: true do |f| section.menu-status .menu-form1 h3 メニュー詳細 table tbody tr td = f.label :title, value: 'メニュー名' td = f.text_field :title tr td = f.label :regular_price, value: '正規価格(税抜き):' td = f.number_field :regular_price | 円 tr td = f.label :discount_price, value: '提供価格(税抜き):' td = f.number_field :discount_price | 円 tr td = f.label :reservation_method, value: '予約方法' td = f.select :reservation_method, Menu.reservation_methods.keys.map {|method| [method]} tr td = f.label :is_sale_frag, value: '販売ステータス' td = f.select :is_sale_frag, [['販売中', true], ['販売停止中', false]] .menu-form2 h3 メニュー画像 .menu-image = f.attachment_field :menu_image .image-preview h4 タグの追加(任意) = text_field_tag :tag_name = button_tag '追加', type: 'button', class: "add-tag-btn" #tag-list section.menu-tag-form h3 タグ詳細 table tbody / 編集画面でのみ表示 - if truth tr td: h4 現在のタグ一覧 td - menu_tags.each do |menu_tag| .menu-tag = Tag.find(menu_tag.tag_id).name = link_to 'x', {controller: 'menu_tags', action: 'destroy', tag_id: menu_tag, menu_id: params[:id], restaurant_id: params[:restaurant_id]}, method: :delete tr td: h4 タグの追加<br>(一つ以上選択推奨) td - tag_count = 0 - tags.each do |tag| / 推奨タグ7個を表示 - if tag_count < 7 .check-box - if menu_tags.exists?(tag_id: tag.id) = check_box :tag_id, tag.id, checked: true = label_tag :tag_id, "#{tag.name}" - else = check_box :tag_id, tag.id = label_tag :tag_id, "#{tag.name}" - tag_count += 1 - else - breeak section.menu-form-area .content-form p = f.label :content, value: '内容' = f.text_area :content .cancel-form p = f.label :cancel, value: 'キャンセル規定' = f.text_area :cancel .submit= f.button "#{submit}", onclick: 'submit();', type: 'button', class: 'btn'まとめ・感想
上記のコードは全て動作確認済みです。
.erbと.slimのコード量を比較すると大体2/3ぐらいになります。
インデントがとても大事で、インデントを間違えるだけで普通にsyntax errorになるので、その場合はブロックごとにコメントアウトしながら確認をしました。
application.html.slimなら、bodyは全てコメントアウトして、まずはhead内だけ取り掛かり、head内でもsyntax errorならまたその中でコメントアウトを駆使して問題を切り分けていくような流れです。
これからslimに書き換えようと思っている方の参考になれば幸いです。コードでわからない箇所や不明点、質問、解釈の違い、記述方法に違和感がありましたら、コメント等でご指摘いただけると幸いです。
最後まで読んでいただきありがとうございました。
参考サイト
GitHub - slim
Qiita - 速習テンプレートSlim(HTML作成編)
Qiita - RailsのHTMLテンプレートエンジン、Slimの基本的な記法
Qiita - 【爆速で習得】Railsでslimを使う方法から基本文法まで
Qiita - Slim コードのリファクタリング
GitHub - matchi_ver.slim
- 投稿日:2020-09-17T18:25:28+09:00
【Rails】rails dbコマンドまとめ
rails5.0からは、rakeコマンドとrailsコマンドどちらも使えるようになっている模様
データベースを作成 / 削除する(定義元は、database.yml)
# 作成する $ rails db:create # 削除する $ rails db:drop全てのテーブルをdropして、テーブルを再生成する
# db/schemaを元に再生成 $ rails db:reset # db/migrateファイルを元に再生成 $ rails db:migrate:statusマイグレーションファイルの一つ前の命令をなかったことにする
# 一つ前の命令をなかったことにする $ rails db:rollback # 二つ前までの命令を連続でなかったことにする $ rails db:rollback STEP=2テストデータをデータベースに反映させる
# seedファイルを元に作成 $ rails db:seed開発環境の初期設定を一気に行う
# create / schema:load / seedコマンドを順に行う $ rails db:setup参考記事
- 投稿日:2020-09-17T18:05:52+09:00
Rails:pluckを使う必要性
表記例
current_user.followings.pluck(:id) #=>[1,2,3].plackは指定した引数の値をカラムから、配列で返してくれるメソッド
必要なデータだけを取れるのであればmapメソッドもあるが
何故pluckメソッドを使用するのか?理由
巨大なコレクションを操作する場合、
mapメソッドでは読み込んだ行を一行づつオブジェクトに変換しているが、使用するフィールドは全ては使用はしません。必要なフィールドだけを読み込むことによって
・スピード
・メモリ使用量
の効率化が図ることができるようになる注意
pluckメソッドが返す値は配列を返してくれる
plackメソッド使用後、モデルの更新などを行いたい場合は使用することができないので注意が必要
- 投稿日:2020-09-17T16:23:30+09:00
パスワード無しでdeviseのユーザー情報を更新する方法
環境
ruby (2.6.5)
rails(6.0.0)
devise (4.7.2)ユーザー情報編集ページの実装
まずはルーティングされている users/registrations#edit が実行されるようにリンク先を指定します。
Prefix Verb URI Pattern Controller#Action edit_user_registration GET /users/edit(.:format) users/registrations#edit下記のように。
<%= link_to 'マイページ', edit_user_registration_path(current_user), class: "user-nickname" %>deviseコントローラーの編集
パスワード無しでデータを更新する機能はdeviseのコントローラーに実装するので、
ターミナルでdeviseのコントローラーを生成します。$ rails g devise:controllers users生成された中のRegistrationsControllerを下記のように編集します。
app>controllers>users>registrations_controlle.rbclass Users::RegistrationsController < Devise::RegistrationsController before_action :configure_account_update_params, only: [:update] protected def update_resource(resource, params) resource.update_without_password(params) end def after_update_path_for(_resource) root_path end def configure_account_update_params devise_parameter_sanitizer.permit(:account_update, keys: [:nickname]) end endupdate_resourceでパスワード無しで更新しています。
after_update_path_forで更新後のリダイレクト先を指定しています。
configure_account_update_paramsで今回はUserテーブルのnicknameというカラムだけ更新を許可しています。詳しくは公式のwikiを参照してください。
https://github.com/heartcombo/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-passworddevise editのビューファイル編集
ターミナルでdeviseのビューファイルを生成します。
$ rails g devise:views生成されたviewファイルのedit.html.viewを必要な入力フォームだけになるように編集します。
app>views>devise>registrations>edit.html.erb<h2>Edit <%= resource_name.to_s.humanize %></h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> <div class="field"> <%= f.label :nickname %><br /> <%= f.text_field :nickname, autofocus: true, autocomplete: "nickname" %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div> <% end %> <div class="actions"> <%= f.submit "Update" %> </div> <% end %>routes.rbの編集
下記のように編集し、registration時のコントローラーの指定を行います。
routes.rbdevise_for :users, controllers: { registrations: 'users/registrations' }Userモデルのパスワードのバリデーション編集
updateの際、パスワードのバリデーションに弾かれないように、
on: :createと追記しています。
この記述によってパスワードのバリデーションはcreateアクション実行時にのみ適用されることになります。app>models>user.rbwith_options presence: true do validates :nickname, :birthday validates :email, uniqueness: true validates :first_name, :last_name, format: { with: regexp_name } validates :first_name_read, :last_name_read, format: { with: regexp_name_read } validates :password, format: { with: regexp_password }, on: :create end以上を実装することでパスワード無しでdeviseのUserモデルのテーブル更新を行うことができました。
ここまでご覧いただき、有難うございました。
- 投稿日:2020-09-17T15:34:50+09:00
Railsのlinkd_toタグ
はじめに
railsのlink_toメソッドの書き方をまとめました。
目次
- link_toメソッドとは
- 基本的な書き方
1. link_toメソッドとは
link_toメソッドとはビューで使用するhelperメソッドです。 リンクを表示させたい時に使用し、htmlのaタグを生成してくれます。
link_toメソッドにリンクとして表示する文字列とリンク先を引数として渡すことで、リンクを表示させることができます。
以下にlink_toメソッドの基本的なソースコードの書き方の紹介をしていきます。2. 基本的な書き方
- 第一引数にリンクのテキスト
- 第二引数にパス、URLの指定
これらを引数として渡すことでリンクを作成することができます。
URLやパスを使用する方法
- URLを用いる場合
<%= link_to 'Yahoo', 'http://www.yahoo.co.jp/' %>
- パスを用いる場合
<%= link_to ‘ユーザー一覧’, ‘/users/index’ %>ルーティングを使用する方法
同じアプリケーション内へのリンクを作成する場合は以下を使います。
「config/routes.rb」で設定しているルーティングの名前に「_path」をつけたものをリンク先として指定します。
ルーティングの名前を確認するときは以下のコマンドを使います。作成したアプリケーションで実行します。ターミナル
rails routesPrefix Verb URI Pattern Controller#Action incomes POST /incomes(.:format) incomes#create new_income GET /incomes/new(.:format) incomes#new edit_income GET /incomes/:id/edit(.:format) incomes#edit income PATCH /incomes/:id(.:format) incomes#update PUT /incomes/:id(.:format) incomes#update DELETE /incomes/:id(.:format) incomes#destroy例えば、incomes_controllerのnewアクション(新規作成画面)にリンクを貼りたいときは以下のように書きます。
- Prefixを用いる場合
<%= link_to '新規作成’, new_income_path %>
- URI Patternを用いる場合
<%= link_to '新規作成’, ‘/incomes/new’ %>idを指定する必要があるとき
上記のURI Patternにidを含むものがありますが、これはどの「income」についての編集画面にリンクを設定するのかというのを表しています。
「edit_income_path」にincomeのidを引数としてわたすことで、インカムデータのidを元にリンク先を設定してくれます。methodオプションを使用する方法
link_toメソッドの引数にはHTTPメソッドを指定することができます。何も指定しなければGETとなります。書き方は以下の通りです。
<%= link_to ‘削除’, income_path(params[:id]), method: :delete %>また、id属性やclass属性を設定することもできます。
do~endを使用する方法
link_toメソッドは以下のようにdo~endのブロックを使って記述することもできます。
<%= link_to income_path, class: 'hoge' do %> <div>a</div> <h4>b</h4> <p>c</p> <% end %>link_to do ~ end の中の要素は同時にリンクできるようになります。
参考リンク
- 投稿日:2020-09-17T15:20:19+09:00
入力したtextの改行がviewで表示されない
概要
text_areaで入力した文章に改行が入っていたのに、いざ投稿一覧でみてみると改行が反映されていない時の解決法について共有しておきます。
例えば、postのmessageに
xxx
yyy
zzz
と
入力したつもりが、投稿一覧で確認してみると
xxx yyy zzz
となってしまった時の対処法です。
結論
index.html.erb<%= post.message %>となっていた部分を
index.html.erb<%= simple_format(post.message) %>としましょう。
simple_formatとは?
simple_formatは、改行文字を含むテキストをブラウザ上で表示させる時に使われるヘルパーになります。simple_formatの機能について簡単にまとめます。
文字列を
<p>
で囲む
改行には<br/>
を付与
連続した改行については</p><p>
を付与(他記事を参照)
- 投稿日:2020-09-17T14:35:48+09:00
単体モデルテストで got errors: User must exist と出る
単体モデルテストで got errors: User must exist と出た時の対処法
ruby '2.6.5'
rails '6.0.0'medicine_spec.rbbefore do @medicine = FactoryBot.build(:medicine) @medicine.image = fixture_file_upload('public/images/money.jpg') end describe '薬の新規登録' do context '薬の新規登録がうまくいくとき' do it "medicine,symptom,date,imageが存在すれば登録できる" do expect(@medicine).to be_valid end endこのテストコードで下記のようなエラー分が出ます。
仮説を立てました。
got errors: User must exist
と出たのでuser情報が関係してると仮説を立てました。medicine.rbclass Medicine < ApplicationRecord has_one_attached :image belongs_to :user with_options presence: true do validates :medicine validates :symptom validates :date end enduser.rbhas_many :medicinesモデルのアソシエーションは組めています。次に考えたのは値をいれるテストの時にアソシエーション組めていないと仮説を立てます。
medicines.rbFactoryBot.define do factory :medicine do medicine { 'ロキソニン' } symptom { 'かぜ' } date { Time.now.utc } association :user end end
association :user
を追加しました。テストしてみます。無事テスト通りました!
- 投稿日:2020-09-17T12:41:46+09:00
ポートフォリオ作成 Ruby on Rails
現在Ruby on Railsを用いて作成中のwebアプリについて投稿していきたいと思います。
ポートフォリオ概要
github,heroku URL
https://github.com/ShotaNagato/new_app
https://shota-rails-app.herokuapp.comアプリについて
何かを教えたい先生と教えて欲しい生徒をオンラインでつなげるマッチングサイト。
開発背景
プログラミングを独学していてわからないところやエラーに時間をかけすぎてしまう。スクールに通う余裕はない。聞きたい時に聞きたいことだけ聞いて終わりという関係の先生が必要。
環境
エディタ VScode
Ruby 2.7.1
Rails 6.0.3.2
DB MySQL実装済み機能
生徒と先生それぞれでの登録、ログイン、ログアウト機能
生徒、先生の一覧表示機能
登録内容更新機能実装予定機能
プロフィール作成機能
募集投稿機能
投稿一覧表示機能
メッセージ機能
- 投稿日:2020-09-17T12:18:42+09:00
[rails]Webpacker configuration file not foundエラーの解決法
環境
Rails 6.0.3.1
Vagrant 2.2.4
CentOS Linux release 7.7.1908 (Core)サーバを立ち上げると謎のエラーが
$rails s -b 0.0.0.0Webpacker configuration file not found /home/vagrant/work/arigatou/config/webpacker.yml. Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - /home/vagrant/work/arigatou/config/webpacker.yml (RuntimeError)どうやら「rails webpacker:install」を打てと書かれているので、やってみます。
$ rails webpacker:install Yarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/すると、「Yarn」をインストールしてくださいと出てきたので、インストールします。
$sudo npm install -g yarn念のため、本当にインストールされているか確認。
$yarn -v 1.22.5Yarnがインストールされたので、再度「rails webpacker:install」を実行。
インストールが完了したことを確認できました。$rails webpacker:install (省略) Done in 68.20s. Webpacker successfully installed ? ?これでサーバーを起動すると問題なく動きます。。
$rails s -b 0.0.0.0 => Booting Puma => Rails 6.0.3.2 application starting in development => Run `rails server --help` for more startup options Puma starting in single mode... * Version 4.3.6 (ruby 2.5.7-p206), codename: Mysterious Traveller * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://0.0.0.0:3000 Use Ctrl-C to stop
- 投稿日:2020-09-17T12:02:56+09:00
[Rails6対応, 公式SDK] AWS SESを使ってRailsから送信元が独自ドメインのメールを送ってみた
AWS SESを使ってRailsから送信元が独自ドメインのメールを送ってみた - Rails 6, Credentials 仕様-
IAMユーザーを使用する方法で私の環境と同じエラーが散見され
EC2にroleをアタッチする方法などを試みていたが
公式のSDKを使用することで解決できたので書き留めます環境
Rails: 6.0.3
Ruby: 2.7.1事前準備
- SES、ドメインの設定
- SES関連の権限が付与されたIAMユーザー
- 上記ユーザーのaccess_key_id, secret_access_keyを取得
ここまでは下記ページを参考にしました
【AWS】Amazon SES / Messaging・Route53を用いてドメインメールを送信する - Qiita以下、参考ページからの変更点
- Rails 6のcredentialsを使用する
- SESのリージョンを
ap-northeast-1
に変更AWS公式のSDKを使用
特にここが重要で私の環境では一通り設定した後の送信テストで次のエラーが
AWS::SES::ResponseError (AWS::SES Response Error: InvalidClientTokenId - The security token included in the request is invalid.)以下のSDKが使用されているところを
gem 'aws-ses', '~> 0.6'version 0.6.0が2014年アップデートのため
gem 'aws-ses', '~> 0.7'
0.7.0 - September 03, 2020
を試したものの変化なしAWS公式のSDKがあることがわかったのでこちらを試したところ解決しました
Ruby on Rails で SDK を使用する - AWS SDK for Rubyaws-sdk-rails/README.md at master · aws/aws-sdk-rails
gem 'aws-sdk-rails', '~> 3'credentials.yml.encにAWSにアクセスするための情報を記述
EDITOR=vim rails credentials:editcredentials.yml.encは直接開けず、master keyで復号化する必要があるため上記コマンドで。EDITORの指定は必須、Dockerでalpineイメージを使っているなど、場合によって
EDITOR=vi
aws:
の部分のコメントアウトを解除しaccess_key_id
,secret_access_key
を追加aws: access_key_id: YOUR_KEY_ID secret_access_key: YOUR_ACCESS_KEY # Used as the base secret for all MessageVerifiers in Rails, including the one prote secret_key_base:***********************************Action mailerの設定
config/initializers/aws.rb (新規作成)
credentialsからaccess_key_id, secret_access_keyを呼び出していますcreds = Aws::Credentials.new(Rails.application.credentials[:aws][:access_key_id], Rails.application.credentials[:aws][:secret_access_key]) Aws::Rails.add_action_mailer_delivery_method( :ses, credentials: creds, region: 'ap-northeast-1' #AWS SESで設定したregion )config/environments/development.rb
config.action_mailer.delivery_method = :ses動作確認
テスト用のmailerを作ってみます
rails g mailer TestMailer testapp/mailers/test_mailer.rb
class DefaultMailer < ApplicationMailer default from: 'example.com <noreply@example.com>' #example.comに自分のドメインを追加 # Subject can be set in your I18n file at config/locales/en.yml # with the following lookup: # # en.default_mailer.test.subject # def test @greeting = "Hi" mail to: "テストメールを受信するアドレス" #追加 end endRails cTestMailer.test.deliver_nowこれで
noreply@...
からメールが送信されるはずですお掃除
rails d mailer TestMailerTroubleshoot
テスト送信がうまく行かない場合は以下のオプションでデバックに必要な情報が得られるかもしれません
config/environments/development.rbconfig.action_mailer.raise_delivery_errors = trueインターフェイス
formからpost :send_mailで受けて
controller
def send_mail @user = User.new(user_params) DefaultMailer.test(@user).deliver_now redirect_to ... endapp/mailers/default_mailer.rb
class DefaultMailer < ApplicationMailer default from: 'example.com <noreply@example.com>' # Subject can be set in your I18n file at config/locales/en.yml # with the following lookup: # # en.default_mailer.test.subject # def test(user) @user = user @greeting = "Hi" mail to: @user.email end endapp/views/default_mailer/test.html.erb
<h1>Default#test</h1> <p> <%= @greeting %>, <%= @user.name%> Thank you for counfirm! </p>
- 投稿日:2020-09-17T08:55:07+09:00
Rails 6で認証認可入り掲示板APIを構築する #12 userとpostの関連付け
←Rails 6で認証認可入り掲示板APIを構築する #11 userモデルのテストとバリデーション追加
postからuserへの関連付けをする
postとuserの関連付けを行います。
想定読者がRailsチュートリアル完了済み前提のため意味の説明は割愛しますが、postにbelongs_to :user
を、userにhas_many :posts
を追加しましょう。$ rails g migration AddUserIdToPosts user:referencesレコードがある状態だとnotnull制約に引っかかってmigrationがエラーになるので、db:resetしてしまいます(乱暴)
$ rails db:reset $ rails db:migrateapp/models/post.rb... class Post < ApplicationRecord + belongs_to :user + ...app/models/user.rb... include DeviseTokenAuth::Concerns::User + has_many :posts, dependent: :destroy + ...2つのテーブルの関連付けを行ったら、ちゃんと動作するか
rails c
で実験してみます。$ rails c [1] pry(main)> user = User.create!(name: "hoge", email: "test@example.com", password: "password") [2] pry(main)> post = Post.create!(subject: "test", body: "testtest", user: user) [3] pry(main)> user.posts Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1 [["user_id", 1]] => [#<Post:0x000000000488dbb0 id: 1, subject: "test", body: "testtest", created_at: Tue, 08 Sep 2020 08:36:20 UTC +00:00, updated_at: Tue, 08 Sep 2020 08:36:20 UTC +00:00, user_id: 1>] [4] pry(main)> post.user => #<User id: 1, provider: "email", uid: "test@example.com", name: "hoge", email: "test@example.com", created_at: "2020-09-08 08:36:11", updated_at: "2020-09-08 08:36:11">どうやら、無事userからpostsを呼んだり、postからuserを呼んだりできていますね。
postのserializerを直す
postsのAPIから、ユーザーのIDと名前、メールアドレスを取得したいと思います。
その際に直すべきはserializerとcontroller。
最低限動くにはserializerだけで良いのですが、controllerも手を入れないとN+1問題という無駄なSQLが大量に流れてパフォーマンスを落とす状態になるのでご注意ください。app/serializers/post_serializer.rb... class PostSerializer < ActiveModel::Serializer attributes :id, :subject, :body + belongs_to :userこうするとuserがくっついてきます。
$ curl localhost:8080/v1/posts/1 {"post":{"id":1,"subject":"test","body":"testtest","user":{"id":1,"provider":"email","uid":"test@example.com","name":"hoge","email":"test@example.com","created_at":"2020-09-08T08:36:11.972Z","updated_at":"2020-09-08T08:36:11.972Z"}}}くっついてきたは良いけど、なんかuserの不要な情報までいっぱい取れてきてしまいましたね。
userのserializerがないので追加しましょう。userのserializerを作る
modelを作った際にserializerは自動生成されるのですが、devise_token_authでmodel生成したため手動でコマンドを叩きます。
なお、devise_token_authによって自動生成されたcontrollerのレスポンスjsonはactiveModelSerializerが効きません。もし有効化したい場合はdevise系のcontrollerをオーバーライドする必要があるのですが、今回は割愛します。今後postモデルからuser
$ rails g serializer userapp/serializers/user_serializer.rb# frozen_string_literal: true class UserSerializer < ActiveModel::Serializer attributes :id, :name, :email end$ curl localhost:8080/v1/posts/1 {"post":{"id":1,"subject":"test","body":"testtest","user":{"id":1,"name":"hoge","email":"test@example.com"}}}これでひとまずOK。
N+1問題への対応
それでは、複数ユーザー・複数投稿データをrails cで作ってみて、
curl localhost:8080/v1/posts
を叩いてみます。
無事にデータは取ってこれますが、rails s
で立ち上げているターミナルに移動してみると…Started GET "/v1/posts" for 127.0.0.1 at 2020-09-08 08:48:08 +0000 Processing by V1::PostsController#index as */* Post Load (0.3ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT $1 [["LIMIT", 20]] ↳ app/controllers/v1/posts_controller.rb:12:in `index' [active_model_serializers] User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]] [active_model_serializers] ↳ app/controllers/v1/posts_controller.rb:12:in `index' [active_model_serializers] CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]] [active_model_serializers] ↳ app/controllers/v1/posts_controller.rb:12:in `index' [active_model_serializers] CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]] [active_model_serializers] ↳ app/controllers/v1/posts_controller.rb:12:in `index' [active_model_serializers] User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]] ... [active_model_serializers] ↳ app/controllers/v1/posts_controller.rb:12:in `index' [active_model_serializers] CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]] [active_model_serializers] ↳ app/controllers/v1/posts_controller.rb:12:in `index' [active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Json (30.42ms) Completed 200 OK in 34ms (Views: 32.5ms | ActiveRecord: 0.8ms | Allocations: 21448)省略していますが、こんな感じに大量のSQLが流れています。
これがN+1問題です。postに紐づくuserを、1レコードずつfindして取ってきているので無駄なSQLが大量に流れます。
これは
Post.all
してくるタイミングでincludesしておけばOKです。app/controllers/v1/posts_controller.rbdef index - posts = Post.order(created_at: :desc).limit(20) + posts = Post.includes(:user).order(created_at: :desc).limit(20) render json: posts endStarted GET "/v1/posts" for 127.0.0.1 at 2020-09-08 08:51:50 +0000 Processing by V1::PostsController#index as */* Post Load (0.2ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT $1 [["LIMIT", 20]] ↳ app/controllers/v1/posts_controller.rb:12:in `index' User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IN ($1, $2, $3) [["id", 3], ["id", 2], ["id", 1]] ↳ app/controllers/v1/posts_controller.rb:12:in `index' [active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Json (5.32ms) Completed 200 OK in 41ms (Views: 32.7ms | ActiveRecord: 5.1ms | Allocations: 17394)usersとposts、それぞれのテーブル1回ずつだけの計2本になりました。
とりあえずアプリケーションの動きとしては直ったように見えますが、実はこの状態でrspecを動かすと盛大にコケまくります。
次回はrspecとseedを確認していきます。続き
- 投稿日:2020-09-17T07:49:33+09:00
(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第10章】
前提
・Railsチュートリアルは第4版
・今回の学習は3周目(9章以降は2周目)
・著者はProgate一通りやったぐらいの初学者基本方針
・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる(記事最下段・用語集)。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。
認証システム開発・第5段回目、ついに2ケタ・第10章に突入です。RESTアクションを完成させていきましょう。
本日のBGMはこちら。
PLASTIC GIRL IN CLOSET "TOY"
もう10年前のアルバムになるのか…時が経つのは早いですね。そら私の20代終わっちゃうわ。
【10.1.1 編集フォーム メモと演習】
・リンクのaタグ内に target="_blank"を入れると、リンクを別タブで開くようになる。(セキュリティ対策は演習で)
・Active Recordのnew_recordメソッドで新規ユーザーか既存ユーザーか、Rails内部で判断している。1. 先ほど触れたように、target="_blank"で新しいページを開くときには、セキュリティ上の小さな問題があります。それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点です。具体的には、フィッシング (Phising) サイトのような、悪意のあるコンテンツを導入させられてしまう可能性があります。Gravatarのような著名なサイトではこのような事態は起こらないと思いますが、念のため、このセキュリティ上のリスクも排除しておきましょう。対処方法は、リンク用のaタグのrel (relationship) 属性に、"noopener"と設定するだけです。早速、リスト 10.2で使ったGravatarの編集ページへのリンクにこの設定をしてみましょう。
→ 下記。<a href="http://gravatar.com/emails" target="_blank" rel="noopener">change</a>
2. リスト 10.5のパーシャルを使って、new.html.erbビュー (リスト 10.6) とedit.html.erbビュー (リスト 10.7) をリファクタリングしてみましょう (コードの重複を取り除いてみましょう)。ヒント: 3.4.3で使ったprovideメソッドを使うと、重複を取り除けます3 。(関連するリスト 7.27の演習課題を既に解いている場合、この演習課題をうまく解けない可能性があります。うまく解けない場合は、既存のコードのどこに差異があるのか考えながらこの課題に取り組んでみましょう。例えば筆者であれば、リスト 10.5で用いた変数を渡すテクニックを使って、リスト 10.6やリスト 10.7で必要になるURLをリスト 10.5に渡してみるでしょう。)
→ やるだけよん。(なぜか7章の演習やってたけどビュー同士の違いが発生してなかった。考察してるときに戻してなかったか)
【10.1.2 編集の失敗 演習】
1. 編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。
→ ちゃんと編集ページに戻されてエラーが出ました。
【10.1.3 編集失敗時のテスト 演習】
1. リスト 10.9のテストに1行追加し、正しい数のエラーメッセージが表示されているかテストしてみましょう。ヒント: 表 5.2で紹介したassert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というテキストを精査してみましょう。
→ 下記assert_select "div.alert", "The form contains 4 errors."
【10.1.4 TDDで編集を成功させる 演習】
1. 実際に編集が成功するかどうか、有効な情報を送信して確かめてみましょう。
2. もしGravatarと紐付いていない適当なメールアドレス (foobar@example.comなど) に変更した場合、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームからメールアドレスを変更して、確認してみましょう。
→ まとめて、サンプルユーザーのアドレスを適当に変更→成功、グラバターのデフォ画像が表示される。
【10.2 認可 メモ】
認証(authentication):サイトのユーザーを識別
認可(authorization):ユーザーが実行可能な操作を管理
【10.2.1 ユーザーにログインを要求する メモと演習】
before_action:
コントローラにおいて、何らかのアクションが実行される直前に、特定のメソッドを実行する。オプションにonly: [:アクション]を渡すことで、特定アクションにだけ適用する。1. デフォルトのbeforeフィルターは、すべてのアクションに対して制限を加えます。今回のケースだと、ログインページやユーザー登録ページにも制限の範囲が及んでしまうはずです (結果としてテストも失敗するはずです)。リスト 10.15のonly:オプションをコメントアウトしてみて、テストスイートがそのエラーを検知できるかどうか (テストが失敗するかどうか) 確かめてみましょう。
→ Yes, RED !
【10.2.2 正しいユーザーを要求する 演習】
1. 何故editアクションとupdateアクションを両方とも保護する必要があるのでしょうか? 考えてみてください。
→ usersリソースのURLが異なる(editは/users/1/edit、updateは/users/1)から。第7章の表7.1参照。
2. 上記のアクションのうち、どちらがブラウザで簡単にテストできるアクションでしょうか?
→ editでしょ。HTTPリクエストがGETだから。ブラウザに表示してくれる。
【10.2.3 フレンドリーフォワーディング メモと演習】
requestオブジェクト:Railsガイド参照。URL以外にもいろいろなクライアント側の情報が含まれているよ。
1. フレンドリーフォワーディングで、渡されたURLに初回のみ転送されていることを、テストを書いて確認してみましょう。次回以降のログインのときには、転送先のURLはデフォルト (プロフィール画面) に戻っている必要があります。ヒント: リスト 10.29のsession[:forwarding_url]が正しい値かどうか確認するテストを追加してみましょう。
→ これが分からなかった。調べた結果が以下。このテストではまずedit_user_path(@user)にアクセスしようとしているから、session[:forwarding_url]がそのURLと等しいかチェック。そしてログイン後はアクセスしようとしていたedit_user_url(@user)に戻ってるかチェックして、session[:forwarding_url]がnilか(deleteされているか)を確かめていると。なるほどな〜。users_edit_test.rbtest "successful edit with friendly forwarding" do get edit_user_path(@user) assert_equal session[:forwarding_url], edit_user_url(@user) log_in_as(@user) assert_redirected_to edit_user_url(@user) assert_nil session[:forwarding_url] assert_redirected_to edit_user_url(@user) name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: {name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end
2. 7.1.3で紹介したdebuggerメソッドをSessionsコントローラのnewアクションに置いてみましょう。その後、ログアウトして /users/1/edit にアクセスしてみてください (デバッガーが途中で処理を止めるはずです)。ここでコンソールに移り、session[:forwarding_url]の値が正しいかどうか確認してみましょう。また、newアクションにアクセスしたときのrequest.get?の値も確認してみましょう (デバッガーを使っていると、ときどき予期せぬ箇所でターミナルが止まったり、おかしい挙動を見せたりします。熟練の開発者になった気になって (コラム 1.1)、落ち着いて対処してみましょう)。
→ (byebug)にsession[:forwarding_url]を入れると格納されているURL(~/users/1/edit)が表示され、request.get?を入れるとtrueが返ってきます。
【10.3.1 ユーザーの一覧ページ 演習】
1. レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。ヒント: log_in_asヘルパーを使ってリスト 5.32にテストを追加してみましょう。
→ 自分で試しに書いた下のコードでGREENだったんですがダメ?しかも2パターン。site_layout_testrequire 'test_helper' class SiteLayoutTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "layout links" do get root_path assert_template 'static_pages/home' assert_select "a[href=?]", root_path, count: 2 assert_select "a[href=?]", help_path assert_select "a[href=?]", about_path assert_select "a[href=?]", contact_path assert_select "a[href=?]", signup_path get contact_path assert_select "title", full_title("Contact") get signup_path assert_select "title", full_title("Sign up") log_in_as(@user) follow_redirect! # もしくは get user_path(@user) assert_select "a[href=?]", users_path assert_select "a[href=?]", user_path(@user) assert_select "a[href=?]", edit_user_path(@user) assert_select "a[href=?]", logout_path end end調べてみた他の演習まとめとの違い
・ログイン済みユーザーのテストを非ログイン時のテストと分けている。
⇨これはコードの可読性を考えると分けた方がいいのかも。あとは明確に動作を分けた方がテストとしては確実?・ログイン後にget root_pathしている。
⇨これは不自然じゃないか?ログイン後のデフォルト動作はユーザーページへ飛ぶんやろ?何でわざわざhomeに行く?んで、log_in_asではpostリクエストしてるわけだから、follow_redirect!で実際にそのページへ行ってテストしてもよし、get user_path(@user)でテスト対象のページを指定してもよし、と考えました。後々不具合が出るようであれば見直します。
【10.3.2 サンプルのユーザー メモと演習】
ここでfakerジェム入れる時に赤文字が出てたのでbundle update。こういうのは慣れてきましたね。
1. 試しに他人の編集ページにアクセスしてみて、10.2.2で実装したようにリダイレクトされるかどうかを確かめてみましょう。
→ ためしに~/user/2/editにアクセスしようとすると、homeに飛ばされます。
【10.3.3 ページネーション 演習】
1. Railsコンソールを開き、pageオプションにnilをセットして実行すると、1ページ目のユーザーが取得できることを確認してみましょう。
→ 下記>> User.paginate(page: nil) User Load (1.0ms) SELECT "users".* FROM "users" LIMIT ? OFFSET ? [["LIMIT", 11], ["OFFSET", 0]] (0.2ms) SELECT COUNT(*) FROM "users" => #<ActiveRecord::Relation [#<User id: 1, name: "Example User", email: "example@railstutorial.org", 以下、長いから省略
2. 先ほどの演習課題で取得したpaginationオブジェクトは、何クラスでしょうか? また、User.allのクラスとどこが違うでしょうか? 比較してみてください。
→ User::ActiveRecord_Relationクラス。一緒ですね。>> User.paginate(page: nil).class => User::ActiveRecord_Relation >> User.paginate(page: nil).class.superclass => ActiveRecord::Relation >> User.all.class => User::ActiveRecord_Relation
【10.3.4 ユーザー一覧のテスト メモと演習】
ページネーションには他にもKaminariやPagyなど、いろいろなメソッドがある。(今後試してみよう)
1. 試しにリスト 10.45にあるページネーションのリンク (will_paginateの部分) を2つともコメントアウトしてみて、リスト 10.48のテストが redに変わるかどうか確かめてみましょう。
→ 当然REDです。
2. 先ほどは2つともコメントアウトしましたが、1つだけコメントアウトした場合、テストが greenのままであることを確認してみましょう。will_paginateのリンクが2つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いでしょうか? ヒント: 表 5.2を参考にして、数をカウントするテストを追加してみましょう。
→ GREENのままなので、count: 2を追加。users_index_test.rbtest "index including pagination" do log_in_as(@user) get users_path assert_template 'users/index' assert_select 'div.pagination', count: 2 User.paginate(page: 1).each do |user| assert_select 'a[href=?]', user_path(user), text: user.name end end
【10.3.5 パーシャルのリファクタリング 演習】
1. リスト 10.52にあるrenderの行をコメントアウトし、テストの結果が redに変わることを確認してみましょう。
→ REDになります。
【10.4.1 管理ユーザー 演習】
1. Web経由でadmin属性を変更できないことを確認してみましょう。具体的には、リスト 10.56に示したように、PATCHを直接ユーザーのURL (/users/:id) に送信するテストを作成してみてください。テストが正しい振る舞いをしているかどうか確信を得るために、まずはadminをuser_paramsメソッド内の許可されたパラメータ一覧に追加するところから始めてみましょう。最初のテストの結果は redになるはずです。
→ いまいちFILL_INに何を入れたらいいか分からなかったので調べたところ、下記の解答に。(先にUserコントローラ内のuser_paramsメソッドのpermitに:adminを追加しています)users_controller_test.rbtest "should not allow the admin attribute to be edited via the web" do log_in_as(@other_user) assert_not @other_user.admin? patch user_path(@other_user), params: { user: { password: @other_user.password, password_confirmation: @other_user.password, admin: true } } assert_not @other_user.reload.admin? endあれー?でもGREENになるよ?解答書いてる人たちは本当にREDになったのかな??ってことで調べるとこの記事が。@other_user.passwordやったらあかんやん!ってことで"password"に変えたらREDになりました。その後permitから:adminを消してテストはGREENです。
【10.4.2 destroyアクション 演習】
1. 管理者ユーザーとしてログインし、試しにサンプルユーザを2〜3人削除してみましょう。ユーザーを削除すると、Railsサーバーのログにはどのような情報が表示されるでしょうか?
→ データベースから該当IDのユーザーをDELETEしているのが分かります。
【10.4.3 ユーザー削除のテスト 演習】
1. 試しにリスト 10.59にある管理者ユーザーのbeforeフィルターをコメントアウトしてみて、テストの結果が redに変わることを確認してみましょう。
→ 無事REDでした。
第10章まとめ
・edit,update,deleteを実装。
・indexでユーザー一覧を表示。ページネーションをジェムで実装。
・admin属性を付与しユーザーの管理権限を実装。論理値を返すadmin?が使えるように。
・befoure_actionで特定アクションの前に特定メソッドを実行。(after_actionもあるよ)
・フレンドリーフォワーディング=元行きたかったページリクエスト(GETのみ)をセッションにstoreし、ログイン後にリダイレクト。その後、そのstoreしたセッションは削除。
・db/seeds.rbにサンプルデータ(ユーザー)を作成。
それなりにボリュームのあった第10章が終わりました。これで基本的な機能が一通り実装できました。演習に悩む場面はありますが、調べれば理解できない内容はありません。落ち着いて取り組んでいきましょう。
次だ次!第11章!…あ、メールアドレス使った認証機能かあ…(遠い目)。こうなってる理由は次章以降で説明します。
⇦ 第9章はこちら
学習にあたっての前提・著者ステータスはこちら
なんとなくイメージを掴む用語集
・フォワーディング(forwarding)
何かを転送すること。フレンドリーフォワーディングは「親切な転送」ってところか。
- 投稿日:2020-09-17T05:34:33+09:00
新サービスZennが面白い!
Zennとは
プログラマーのための新しい情報共有コミュニティサービスです。
知見を記事にして公開したり、より深い知見は本として有料(無料)で公開することのできるサービスです。情報を発信するプログラマーが対価を得られるようにというのが、このサービスを作られた方の思いなのだと思います。
Zennってどんな感じ?
ログインはGoogleアカウントのみ!
ログインについては、Googleアカウントでのログインのみのようです。(シンプルで良いね?)
書ける記事は2種類!
Add Newをクリックすると、記事(Article)と本(Book)の2種類から書きたい方を選べます。
記事はマークダウンで書ける!
プレビュー機能!
▷ボタンをクリックするとマークダウンで書いたものをプレビュー出来ます。
豊富な挿入機能!
Twiiter、CodePen、JSFiddle、YouTube、SlideShare、SpeakerDeckと記事執筆に役立つ他サービスを埋め込み出来ます。
記事の設定はシンプル!
アイコンは好きなものに変えられます。カテゴリーはテック系とアイデア系。
トピックスはQiitaを活用している皆さんならよくご存知のやつですね?
本はチャプター式!
本の書き方としては、記事(Article)の書き方と変わらない感じで、記事をいくつか書いたのを合わせていく感じです。
本のプレビューはこんな感じ!
本のプレビューはPCだとこんな感じです。Teckpitのような感じですね?
これは非常に面白いサービスだと思った方も多いのではないでしょうか?(^^)
僕もさっそく使ってみました。本も執筆しようかなと思ってます。良かったらチェックしてね?
https://zenn.dev/engineerhikaru※この記事は勝手に書いたもので、決して何かをもらっているから書いているものではございません笑
- 投稿日:2020-09-17T01:09:18+09:00
Railsチュートリアル備忘録1
環境
macOS Catalina 10.15.5
Rails 6.0.3Railsチュートリアルとそれに付随するいろいろを書いていきます。
Githubに慣れたいので、チュートリアルは第6版に準拠しています。1.5.1 Herokuのセットアップとデプロイ
リスト 1.18
Gemfilesource 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem 'rails', '6.0.3' gem 'puma', '4.3.4' gem 'sass-rails', '5.1.0' gem 'webpacker', '4.0.7' gem 'turbolinks', '5.2.0' gem 'jbuilder', '2.9.1' gem 'bootsnap', '1.4.5', require: false group :development, :test do gem 'sqlite3', '1.4.1' gem 'byebug', '11.0.1', platforms: [:mri, :mingw, :x64_mingw] end group :development do gem 'web-console', '4.0.1' gem 'listen', '3.1.5' gem 'spring', '2.1.0' gem 'spring-watcher-listen', '2.0.1' end group :test do gem 'capybara', '3.28.0' gem 'selenium-webdriver', '3.142.4' gem 'webdrivers', '4.1.2' end group :production do gem 'pg', '1.1.4' end上記のように記載変更して
$ bundle install --without production
実行。しかし以下のエラー発生。
You have requested: spring = 2.1.0 The bundle currently has spring locked at 2.1.1. Try running `bundle update spring` If you are updating multiple gems in your Gemfile at once, try passing them all to `bundle update`エラーに従いbundle update springしたが、
今度はgemlistのpumaに関するエラー。An error occurred while installing puma (4.3.4), and Bundler cannot continue. Make sure that `gem install puma -v '4.3.4' --source 'https://rubygems.org/'` succeeds before bundling.
% gem install puma -v 4.3.4
を実行。Fetching puma-4.3.4.gem Building native extensions. This could take a while... ERROR: Error installing puma: ERROR: Failed to build gem native extension. current directory: /Users/user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/puma-4.3.4/ext/puma_http11 /Users/user/.rbenv/versions/2.7.0/bin/ruby -I /Users/user/.rbenv/versions/2.7.0/lib/ruby/2.7.0 -r ./siteconf20200913-23274-ktgz78.rb extconf.rb checking for BIO_read() in -lcrypto... yes checking for SSL_CTX_new() in -lssl... yes checking for openssl/bio.h... yes checking for DTLS_method() in openssl/ssl.h... yes checking for TLS_server_method() in openssl/ssl.h... yes checking for SSL_CTX_set_min_proto_version in openssl/ssl.h... yes creating Makefile current directory: /Users/user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/puma-4.3.4/ext/puma_http11 make "DESTDIR=" clean current directory: /Users/user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/puma-4.3.4/ext/puma_http11 make "DESTDIR=" compiling http11_parser.c ext/puma_http11/http11_parser.c:44:18: warning: unused variable 'puma_parser_en_main' [-Wunused-const-variable] static const int puma_parser_en_main = 1; ^ 1 warning generated. compiling io_buffer.c compiling mini_ssl.c mini_ssl.c:145:7: warning: unused variable 'min' [-Wunused-variable] int min, ssl_options; ^ mini_ssl.c:299:40: warning: function 'raise_error' could be declared with attribute 'noreturn' [-Wmissing-noreturn] void raise_error(SSL* ssl, int result) { ^ 2 warnings generated. compiling puma_http11.c puma_http11.c:203:22: error: implicitly declaring library function 'isspace' with type 'int (int)' [-Werror,-Wimplicit-function-declaration] while (vlen > 0 && isspace(value[vlen - 1])) vlen--; ^ puma_http11.c:203:22: note: include the header <ctype.h> or explicitly provide a declaration for 'isspace' 1 error generated. make: *** [puma_http11.o] Error 1 make failed, exit code 2 Gem files will remain installed in /Users/user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/puma-4.3.4 for inspection. Results logged to /Users/user/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/extensions/x86_64-darwin-19/2.7.0/puma-4.3.4/gem_make.out改めて
$ bundle install
したが以下ループ。
$ gem list
でもpuma 4.3.4になっている。いろいろ調べた結果、下記記事を参考。
https://qiita.com/aiandrox/items/9389696ebc3cc6d3422epuma4.3.6で対応したそうなのでGemfile変更。
Gemfilegem 'puma', '4.3.6'
$ bundle update
の後、
問題なく$ bundle install
が通った。
- 投稿日:2020-09-17T00:52:36+09:00
rails チュートリアル
6章まで終了
次回7章からmailの大文字小文字問題が消化不良
データベースのindexについてあとで調べる必要あり