- 投稿日:2019-06-27T21:43:30+09:00
オリアプ設計諸々 確定版
サービス概要
ブラック企業での経験を記事にして投稿、誰でも自由に閲覧できる。
記事には、qiitaのようにタグ(例として、業界や賃金の未払いなど、どんなブラック要素があったのか)をつけられるようにする。ターゲット
Aさん(10代後半~20代前半、就労経験なし。これから就職をするにあたり、どんなブラック企業、ブラック要素があるか知りたい。記事を読んでもらいたい人)
Bさん(20代半ば~30代前半、若手社員。自身の労働環境がブラックではないかと疑念を抱いている。記事を読んでもらいたい人)
Cさん(年齢は特に指定なし。ブラック企業での就業経験あり。ブラック企業での経験や問題などを多くの人に知ってもらいたいと思っている。記事を書いてもらいたい人)ログインユーザーと非ログインユーザー
- 非ログインユーザー
- 投稿の閲覧
- 検索
- サイトに対する質問
- ユーザー詳細(記事を書いた人の)
- 感想の閲覧
- ログインユーザー
- 記事の投稿
- 改稿
- 削除
- 閲覧履歴
- 記事のブックマーク(お気に入り)登録
- 自身の投稿の履歴
- 感想の投稿
機能
- 記事の投稿
- 記事の閲覧
- 記事の削除
- 記事の改稿
- 記事の検索
- 記事の閲覧履歴
- ログイン
- ログアウト
- 記事のお気に入り登録
- 自身の投稿の履歴などを含むユーザーの詳細(「プロフィールや一言」の入力欄あり)
- ユーザー詳細の閲覧
- 記事への感想の投稿
- 記事への感想の閲覧
- 記事への感想の閲覧の制限(非ログインユーザーに対して)
- サイトに対する質問等の投稿
必要なテーブル
- usersテーブル
- ユーザーのテーブル
- work_experiencesテーブル
- ブラック企業での経験の記事のテーブル
- work_experience_comentsテーブル
- ブラック企業での経験の記事への感想のテーブル
- likesテーブル
- お気に入りのテーブル
- work_experience_recordsテーブル
- ブラック企業での経験の記事の閲覧履歴のテーブル
- faqsテーブル
- よくある質問のテーブル
- questionsテーブル
- サイトに対する質問等のテーブル
テーブルの表
usersテーブルの表
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK ユーザーのid id integer あり Yes No ユーザーの名前 name string あり limit No No ユーザーのパスワード password_digest string あり unique:true, limit No No ユーザーのメールアドレス string あり unique:true No No ユーザーのプロフィール profile text なし limit No No ユーザーの投稿日時 created_at datetime あり No No ユーザーの更新日時 updated_at datetime あり No No work_experiencesテーブルの表
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK 記事のid id integer あり Yes No 記事の題名 title string あり unique:true, limit No No 記事のタグ tag string なし limit No No 記事の本文 body text あり unique:true, limit No No ユーザーのid user_id integer あり No Yes 記事の投稿日時 created_at datetime あり No No 記事の更新日時 updated_at datetime あり No No work_experience_comentsテーブルの表
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK 感想のid id integer あり Yes No 感想の本文 body text あり limit No No 記事のid work_experience_id integer あり No Yes ユーザーのid user_id integer あり No Yes 感想の投稿日時 created_at datetime あり No No 感想の更新日時 updated_at datetime あり No No likesテーブルの表
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK お気に入りのid id integer あり Yes No 記事のid work_experience_id integer あり No Yes ユーザーのid user_id integer あり No Yes お気に入りの投稿日時 created_at datetime あり No No お気に入りの更新日時 updated_at datetime あり No No work_experience_recordsテーブルの表
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK 閲覧履歴のid id integer あり Yes No 記事のid work_experience_id integer あり No Yes ユーザーのid user_id integer あり No Yes 閲覧履歴の投稿日時 created_at datetime あり No No 閲覧履歴の更新日時 updated_at datetime あり No No faqsテーブルの表
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK faqのid id integer あり Yes No faqの種類 faq_kind string あり No No faqの投稿日時 created_at datetime あり No No faqの更新日時 updated_at datetime あり No No questionsテーブルの表
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK 質問等のid id integer あり Yes No メールアドレス string あり No No 質問等の本文 body text あり limit No No faqのid faq_id integer あり No Yes 質問等の投稿日時 created_at datetime あり No No 質問等の更新日時 updated_at datetime あり No No アソシエーション
- usersテーブルとwork_experiencesテーブルの関係は、1対多
- usersテーブルとwork_experience_comentsテーブルの関係は、1対多
- work_experiencesテーブルとwork_experience_comentsテーブルの関係は、1対多
- work_experiencesテーブルとlikesテーブルの関係は、1対多
- usersテーブルとlikesテーブルの関係は、1対多
- work_experiencesテーブルとwork_experience_recordsテーブルの関係は、1対多
- usersテーブルとwork_experience_recordsテーブルの関係は、1対多
- faqsテーブルとquestionsテーブルの関係は、1対多
非ログインユーザーの感想の閲覧機能の制限
記事を未投稿のユーザーは、閲覧できる感想の数を制限することにする。
方向性としては、記事を未投稿のユーザーは最初の何件かのみを閲覧できるようにし、それ以上の感想を閲覧したい場合は、記事の投稿が必須であることをビューで表示する。
ただし、感想の投稿は問題なく使用できる。
おおよその実装手順は以下の通り。
- 現在ログインしているユーザーをcurrent_userという変数に代入する
- current_userに代入したユーザーに紐づいた記事があるか調べる
- current_userのidカラムの値とwork_experiencesテーブルのuser_idカラムの値が一致するデータがあるか調べる
- 無かった場合は、感想の閲覧部分のビューで表示件数に制限をかける。同時に感想の閲覧を制限なく使用する場合は、記事の投稿が必要だと表示する。
- データがあった場合は、制限なく、全ての感想の閲覧ができる
indexについて
実際にインデックスを貼るカラム
usersテーブル
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK ユーザーのパスワード password_digest string あり unique:true, limit No No ユーザーのメールアドレス string あり unique:true No No work_experiencesテーブル
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK 記事の題名 title string あり unique:true, limit No No 記事のタグ tag string なし limit No No ユーザーのid user_id integer あり No Yes work_experience_comentsテーブル
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK 記事のid work_experience_id integer あり No Yes ユーザーのid user_id integer あり No Yes likesテーブル
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK 記事のid work_experience_id integer あり No Yes ユーザーのid user_id integer あり No Yes work_experience_recordsテーブル
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK 記事のid work_experience_id integer あり No Yes ユーザーのid user_id integer あり No Yes faqsテーブル
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK faqの種類 faq_kind string あり No No questionsテーブル
役割対象 カラム名 データ型 NOT NULL その他の制約 PK FK faqのid faq_id integer あり No Yes tagカラムに関して
動的なタグ生成をするgem「acts-as-taggable-on」を使ってみました
Rails gem acts-as-taggable-onを使ってみた
rails5でタグ機能をacts-as-taggable-onを用いて実装する方法
上記の4つの記事を参考にする。
今回のオリアプではgemを使用する。
使用するgem名は「acts-as-taggable-on」。
必要な要件は以下の通り。
- 使うテーブルは、work_experiencesテーブル
- 新規投稿時にタグを追加する
- 改稿時にタグも変更、更新できるようにする
- タグは検索でも使用できるようにする
- 投稿閲覧(詳細)、投稿一覧ページ等にタグのリンクを作る。リンクをふむと、そのタグが付けられた投稿の一覧が表示される。
実際の実装手順としては、おおよそ以下の通り。
- gemのインストール
- タグ用のテーブル作成及び、マイグレーションの適用
- work_experiencesテーブルのモデルに「acts_as_taggable」を追記
- work_experiencesのコントローラのStrong Parametersに「:tag_list」の追記
- work_experiencesのモデルでransackの検索条件に「:tag_list」の追記
- アクションで「params[:tag]」で条件分岐(ある場合は「Labor.tagged_with(params[:tag])」、ない場合は全記事を取得)
- タグ用のフォームをビューに追加
- リンク付きタグを投稿一覧等に追加
- リンク付きタグ用のルーティングの追加
また、英文だがリファレンスサイトもあったため、これも適宜参考にする。
https://github.com/mbleigh/acts-as-taggable-on
その他今回のオリアプで使えそうなgemのまとめ
「Ruby on Rails5 速習実践ガイド」で使用したgemに関しては確定的に使用するので、ここではgem名だけ載せる。
- gem 'slim-rails'
- gem 'html2slim'
- gem 'bootstrap'
- gem 'rails_autolink'
- gem 'ransack'
- gem 'kaminari'
- gem 'sidekiq'
- gem 'webpacker'
- gem 'mailcatcher'
- gem 'rspec-rails'
- gem 'factory_bot_rails'
- gem 'pry-rails'
それ以外で使えそうなgemをここでは挙げる。ただし、まだ、使用するかどうかわからないので、gem名と何に対して使用するgemなのかを簡単に纏める程度とする。
- devise
- ログイン機能の実装を簡単にできるgem
- better_errors
- エラー画面に、デフォルトのエラー画面より詳しい情報を表示してくれるgem
- acts-as-taggable-on
- タグ機能の実装で使用するgem(ほぼ確実で使用する)
- Active Admin
- 管理画面作成に役立つgem
- font-awesome-rails
- font-awesome(を使えるようにするため)のgem
- factory_girl_rails - テスト用のデータが簡単に生成できるようになるgem
- Faker
- 「factory_girl_rails」と同様に一般的にありがちなテストデータを簡単に作成できるようになるgem
- whenever
- 定期実行ができるgem。オリアプで言えば、記事の投稿、改稿時に自動で下書きの保存をする処理に使用できるかもしれない。
現段階では、acts-as-taggable-on、font-awesome-railsの二つはほぼ確実に使用する。
他にも使えそうなgemがあれば適宜修正していく。
- 投稿日:2019-06-27T20:26:09+09:00
RSpecを起動ようとしたら、「unknown command: Cannot call non W3C standard command while in W3C mode」と言われて怒られた
導入
以下の書籍を読みながら、RSpecについて学んでいます。
写経しながら、RSpecでテストコードを書いて、いざはじめてのRSpecと思って、
ワクワクしながらコマンドを叩くと、以下のエラーが出た。Selenium::WebDriver::Error::UnknownCommandError: unknown command: Cannot call non W3C standard command while in W3C modeGoogle Chromeとchromedriverのバージョンを最新の75にアップデートしたことで、W3CモードがデフォルトONになったみたい。
解決策
オプションに「w3c:false」を渡してやると、うまく解決が出来た。
自分の場合はオプションの設定方法が分からなくてハマったけど、何とかテストコードが動いて、
All Greenになった!RSpec.configure do |config| config.before(:each, type: :system) do # 修正前 driven_by :selenium_chrome_headless # 修正後 caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"w3c" => false}) driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400], options: { desired_capabilities: caps } end参考にしたURL
- 投稿日:2019-06-27T18:30:48+09:00
jQueryとsave時のエラー
はじめに
- 条件を満たしているはずなのに新規登録時に登録に失敗しましたと出る
- 登録失敗時のメッセージ「登録に失敗しました」が消えずにずっと表示されたままになっている。
この二つの問題解決にあたるが,いざ取り組むとあっさり解決した。途中まで書いてしまっていたので,一応投稿しておく。
内容はかなりしょぼいです。
JavaScript
「登録に失敗しました」が5秒で消えない。
とりあえず各ファイルの記述を見ていく。users_controller.rbclass UsersController < ApplicationController def new @user = User.new end def create @user = User.new(user_params) if @user.save redirect_to root_path, success: '登録が完了しました' else flash.now[:danger] = "登録に失敗しました" render :new end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end endapplication.html.erb<!DOCTYPE html> <html> <head> <title>Pictgram</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <% flash.each do |key, value| %> <div class="alert alert-<%= key %>" role="alert"><%= value %></div> <% end %> ~ ~ <%= yield %> <script> $(function(){ $(".alert").fadeOut(5000); }); </script> </body> </html>group :development do # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 3.3.0' gem 'listen', '>= 3.0.5', '< 3.2' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' gem 'pry-rails' gem 'pry-doc' gem 'pry-byebug' gem 'pry-stack_explorer' gem 'jquery-rails' gem 'bcrypt' gem 'carrierwave' endapplication.js// This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's // vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. JavaScript code in this file should be added after the last require_* statement. // // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details // about supported directives. // //= require rails-ujs //= require activestorage //= require turbolinks //= require bootstrap-sprockets //= require jquery //= require_tree .各記述に加え,もちろんbundle installも行っている。
そこで少し調べたところ,Gemfileの記述に
gem 'jquery-ui-rails'application.jsに
application.js//= require jquery_ujs
を加えることで解決することがあるらしいので試してみるとあっさり解決。
Rubyのバージョンが古かったのかな?また今度調べてみる。ROLLBACK
一番の問題はここ。
条件を満たしているはずの新規登録時にターミナルに以下のメッセージが出る。Started POST "/users" for ::1 at 2019-06-26 17:51:29 +0900 Processing by UsersController#create as HTML Parameters: {"utf8"=>"✓", "authenticity_token"=>"VKkdJMhrlEzltFp cD4OJLPN/LhgoyBHmIRj7R6Y+1Z/vn2QXJ60dLptQuvzdmYi/FBCRlSHsBljz/BpW9 PaQQg==", "user"=>{"name"=>"***", "email"=>"***", "passeword"=>"***", "password_confimation"=>"[FILTERED]"}, "commit"=>"登録"} Unpermitted parameters: :passeword, :password_confimation (0.5ms) BEGIN ↳ app/controllers/users_controller.rb:8 (0.3ms) ROLLBACK ↳ app/controllers/users_controller.rb:8 Rendering users/new.html.erb within layouts/application Rendered users/new.html.erb within layouts/application (2.1ms) User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`. `id` IS NULL LIMIT 1 ↳ app/controllers/concerns/common_actions.rb:5 Completed 200 OK in 69ms (Views: 58.3ms | ActiveRecord: 1.1ms) Started GET "/users/new" for ::1 at 2019-06-26 17:54:52 +0900 Processing by UsersController#new as HTML Rendering users/new.html.erb within layouts/application Rendered users/new.html.erb within layouts/application (2.3ms) User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`. `id` IS NULL LIMIT 1 ↳ app/controllers/concerns/common_actions.rb:5 Completed 200 OK in 83ms (Views: 80.0ms | ActiveRecord: 0.4ms)ここで以下の記述に注目した
(0.3ms) ROLLBACK ↳ app/controllers/users_controller.rb:8 Rendering users/new.html.erb within layouts/application Rendered users/new.html.erb within layouts/application (2.1ms)
何かがロールバックしている。追っていくとまずはusers_controller.rbの8行目に何かあるよと。
users_controller.rb# 8行目↓ if @user.saveじゃあこの処理はどこへ行ってるのかというとnew.html.erbである。このファイル内の記述を見ていく。
new.html.erb<div class="users-new-wrapper"> <div class="container"> <div class="row"> <div class="col-md-offset-4 col-md-4 users-new-container"> <h1 class="text-center text-white">Sign up</h1> <%= form_for @user do |f| %> <div class="form-group"> <%= f.label :name, class: 'text-white' %> <%= f.text_field :name, class: 'form-control' %> </div> <div class="form-group"> <%= f.label :email, class: 'text-white' %> <%= f.text_field :email, class: 'form-control' %> </div> <div class="form-group"> <%= f.label :password, class: 'text-white' %> <%= f.passeword_field :password, class: 'form-control' %> </div> <div class="form-group"> <%= f.label :password_confirmation, class: 'text-white' %> <%= f.password_field :password_confimation, class: 'form-control' %> </div> <%= f.submit "登録", class: 'btn-block btn-white' %> <% end %> <%= link_to 'ログインはこちら', login_path, class: 'text-white' %> </div> </div> </div> </div>パッと見では問題がなさそう。
そこで調べてみるとusers_controller.rbif @user.save ↓ if @user.save!へ変更してみるとエラー個所がわかるよという記事を見つけた。
早速変更してrailsサーバーを再起動,新規登録を行ってみるとターミナルに新しい動きがあった。ActiveRecord::RecordInvalid (Validation failed: Password can't be blank, Password is too short (minimum is 8 characters), Password i s invalid): app/controllers/users_controller.rb:8:in `create'
なにやらパスワードがおかしいのかなと思う。下記の通りにValidationを設定しており,条件は満たしているはずなのに「空白はダメだよ(8文字以下になっているよ)」と怒られている。
user.rbclass User < ApplicationRecord validates :name, presence: true, length: { maximum: 15 } VALID_EMAIL_REGEX = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/ validates :email, presence: true, format: { with: VALID_EMAIL_REGEX } VALID_PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,32}+\z/i validates :password, presence: true, length: { minimum: 8, maximum: 32 }, format: { with: VALID_PASSWORD_REGEX } has_secure_password has_many :topics endここでは特になにもなさそうなのでpasswordの定義を行っているnew.html.erbへ戻って問題を確認する。
new.html.erb<%= form_for @user do |f| %> ~ ~ <div class="form-group"> <%= f.label :password, class: 'text-white' %> <%= f.password_field :passeword, class: 'form-control' %> </div> <div class="form-group"> <%= f.label :password_confirmation, class: 'text-white' %> <%= f.password_field :password_confimation, class: 'form-control' %> </div>ここで間違いに気がつく。一箇所passwordがpassewordになっている。これを修正するとあっさり登録完了。問題は解決した。
おわりに
次回こそは
- 投稿する画像の制限
- 非ログイン時の投稿ボタンの非表示
を実装していく。
- 投稿日:2019-06-27T17:49:02+09:00
[Ruby on Rails]メールの送信結果をブラウザ上で確認する
メール送信結果をブラウザ上で確認する方法です。
letter_openerというgemを使います。
本番環境では実際に送信することになるので、開発環境でのみ確認できる状態を作ります。環境
- MacOS 10.14.5(18F132)
- ruby 2.3.7p456
- rails 5.2.3
手順
1.
letter_opener
をインストール今回は開発環境でのみ確認できる状態を作るため、groupの設定をします。
すでにdevelopmentの設定が出来ている場合は、gem名のみを追記してください。Gemfilegroup :development do gem 'letter_opener_web' end追記したらコマンド
$ bundle install2. 各種設定ファイルの編集
- config/routes.rb
config/routes.rb# 追記 if Rails.env.development? mount LetterOpenerWeb::Engine, at: "/letter_opener" end
- config/envirronments/development.rb
config/envirronments/development.rb#追記 ActionMailer::Base.delivery_method = :letter_opener_web #他にaction_mailerの設定をしている場合はコメントアウトする #config.action_mailer.delivery_method = :smtp #config.action_mailer.smtp_settings = { #省略 #}3. ブラウザから確認する
$ rails s
でサーバーを起動して、ブラウザでlocalhost:3000/letter_openerにアクセスします。
このような画面が表示されればOKです。
4. メールを送信する
メールを送信する処理を実行します。
5. 再度ブラウザから確認する
ブラウザでlocalhost:3000/letter_openerにアクセスして、メールを確認します。
※deviseのパスワード再設定メールを送ってみました。
- 投稿日:2019-06-27T16:22:51+09:00
params[:id]とfind_byメソッドの意味~ヘルスケアwebサービスを自分で作る医者の日記~
progate 9回 ラスト
URLの指定で、他人の投稿にアクセスできてしまいのを防ぐ回
def ensure_correct_user @post = Post.find_by(id: params[:id]) if @post.user_id != @current_user.id flash[:notice] = "権限がありません" redirect_to("/posts/index") endfind_byメソッドの意味がスッキリした
SQLの id というカラムから、params[:id]に合致するものを探し、
それをインスタンス変数として@post に渡している。
これは投稿そのもの、一列丸ごとここでparams[:id]にする意味がわからなかったが
params[:id]はルートの指定を格納している
posts/1
みたいなユーザー各ページのURLを入れ込むことによって、ここにアクセスすることを防いでいる
と判明!!
- 投稿日:2019-06-27T15:56:25+09:00
配列によるメソッドの可変長引数!
可変長引数とは???
個数に制限のない引数のこと!
自分で、定義するメソッドで可変長引数を使いたい場合は、引数名の手順に*をつけます!sample.rbdef メソッド名(引数1,引数2,*可変長引数) #メソッドの処理 end可変長引数は配列として受け取ることができます!
具体例を見ていきましょう!
次のコードは、引数として渡されたメニューの名前を、注文していくメソッドです。sample.rbdef order(*food) puts "#{food.join('と')},お願いします!" end order('ハンバーガー') #ハンバーガー,お願いします! order('ハンバーガー','ポテト') #ハンバーガーとポテト,お願いします! order('ハンバーガー','ポテト','コーラ') #ハンバーガーとポテトとコーラ,お願いします!
- 投稿日:2019-06-27T14:48:35+09:00
dependentはどうやって動いているのか?
後述する仕様を実現するためにactiverecordのdependent周りのソースを読んだのでまとめました。
前提
railsのアソシエーションには
dependent
というオプションを設定することができます。
これによりレコードを削除した時に関連レコードに対して自動的に様々な動作をさせることができます。
例えばhas_many
の場合は下記を指定できます。# レコード削除時に関連するbooksも削除する(callbackあり) has_many :books, dependent: :destroy # レコード削除時に関連するbooksも削除する(callbackなし) has_many :books, dependent: :delete_all # レコード削除時に関連するbooksの外部キーをnilに更新する has_many :books, dependent: :nullify # レコード削除時に関連するbooksが存在する場合例外を発生させる has_many :books, dependent: :restrict_with_exception # レコード削除時に関連するbooksが存在する場合errorsを追加して削除を失敗させる has_many :books, dependent: :restrict_with_error詳細はrailsドキュメント参照してください。
https://railsguides.jp/association_basics.html#dependentやりたかったこと
(色々端折ってはいますが)下記の仕様を満たすモデルを作成しようとしていました。
- お店には本を追加・削除できる。ただし、必ず1冊以上は本がある状態をキープする。
- お店を削除した時は全ての本を削除する。
簡単に実装したものが下記。
ソースコメントに記載していますが、before_destory
の中で①「shop
を削除してdependent: :destroy
で削除されようとしている」のか、②「books
を単独で消そうとした」のかを判断する方法がぱっとわかりませんでした。# お店 class Shop < ApplicationRecord has_many :books, dependent: :destroy end # 本 class Books < ApplicationRecord belongs_to :shop before_destroy :before_destroy_action def before_destroy_action # 最後の1冊の場合は削除できない。 # ただし、お店ごと削除する場合はすべて削除する。 <- これどう判定するの???? raise unless Books.not.where(id: id).exists? end end考えた解決案
dependent: :delete_all
を使う
dependent: :destroy
の部分をdependent: :delete_all
を変更することでcallbackが呼ばれなくなるのでbefore_destroy
はbooks
を直接削除したときのことだけ考えれば良くなる。
これで動作は問題ないが、今後books
に他のcallbackが追加された時や子テーブルが追加されてdependent: :destory
としたい時などに支障がでるためこの方法はパス。# お店 class Shop < ApplicationRecord has_many :books, dependent: :delete_all end # 本 class Books < ApplicationRecord belongs_to :shop before_destroy :before_destroy_action def before_destroy_action # 最後の1冊の場合は削除できない。 # shopを削除した場合はbefore_destroyは実行されないので考慮不要 raise unless Books.not.where(id: id).exists? end end
dependent: :destroy
で削除する場合にshopレコードに判定するためのフラグを持たせる下記のように
shop
を削除する時にフラグを立ててbook
のbefore_destroy
内でそのフラグで判定する。
この方法でも動作は問題ないが、そもそもこの微妙なフラグをわざわざ自前で定義する必要があるのか?railsのことだから判定する方法があるのではないか?と考えて保留にしました。# お店 class Shop < ApplicationRecord before_destroy: :before_destroy_action has_many :books, dependent: :destroy def before_destroy_action @destroying = true end def destroying? @destroying end end # 本 class Books < ApplicationRecord belongs_to :shop before_destroy :before_destroy_action def before_destroy_action # shopを削除した場合は何もしない return if shop.destroying? # 最後の1冊の場合は削除できない。 raise unless Books.not.where(id: id).exists? end endrailsに組み込まれている機能を使って判定する
ということでrailsのソースを読んで使えそうな機能を探すことにしました。
destroy
について最初に
destroy
のソースを見てみました。
@destroyed
ってフラグを立てているじゃないか。これ使えないかな?
https://github.com/rails/rails/blob/93a6500baa6bbb331bb93ccdc14fdda5769f5ef9/activerecord/lib/active_record/persistence.rb#L172結論を先に書きますが
dependent: :destroy
による削除が先に動いてしまい、関連データのbefore_destroy
が呼ばれる時には@destroyed
はtrueになっていません。
dependent
について動作を確認して
destroy
より先にdependent: :destroy
が動いていることはわかりましたが、そもそもdependent
って何をしているんだろうということでソースをみてみました。https://github.com/rails/rails/blob/fc35da76e93f8a5d5ace595b4819e19cc0512edd/activerecord/lib/active_record/associations/builder/association.rb#L32
↓
https://github.com/rails/rails/blob/fc35da76e93f8a5d5ace595b4819e19cc0512edd/activerecord/lib/active_record/associations/builder/association.rb#L76
↓
https://github.com/rails/rails/blob/fc35da76e93f8a5d5ace595b4819e19cc0512edd/activerecord/lib/active_record/associations/builder/association.rb#L129
↓
https://github.com/rails/rails/blob/47e3bbeb9057b37c244330cc4e745c8a8090e8c5/activerecord/lib/active_record/associations/has_many_association.rb#L13上記の順番に追っていけばわかりますが、dependentで指定したオプションを判定して削除処理の
before_destroy
を追加しています。ということで、dependentは
before_destroy
を追加しているだけなのでdestroy
より前に動いちゃうんですね。じゃあどうする?
下記のソースを見てみると
dependent: :destroy
の時にdestroyed_by_association
に値を入れていることがわかります。
https://github.com/rails/rails/blob/47e3bbeb9057b37c244330cc4e745c8a8090e8c5/activerecord/lib/active_record/associations/has_many_association.rb#L27これを判定に使えそうなので検証したところ下記のようにチェック可能でした。
自前フラグを追加するよりこちらの方がスマートな気がします。# お店 class Shop < ApplicationRecord has_many :books, dependent: :destroy end # 本 class Books < ApplicationRecord belongs_to :shop before_destroy :before_destroy_action def before_destroy_action # shopを削除した場合は何もしない return if destroyed_by_association.present? # 最後の1冊の場合は削除できない。 raise unless Books.not.where(id: id).exists? end endあとがき
railsのようなオープンソースのフレームワークはソースを簡単にみることができるので仕様を細かく知りたくなったら積極的に読んでみることをお勧めします。
自分でごちゃごちゃやらなくても大抵のことは他の人も困っていて、すでに実装されていたりします。
また、コードリーディングはコーディング力向上に繋がると思いますし、理解が深まればその言語を使うことがより楽しくなると思います。あとがきのあとがき
ソースを読んで
destroyed_by_association
が使えそうとわかったところで、あらためてググってみたら似たような記事をいくつか見つけました。下記は一例(qiitaにもあった)
https://qiita.com/mishiwata1015/items/ac7c33b5f116111d8568私は
destroyed_by_association
というキーワードを見つけるまでは関連記事には辿り着けなかったけど、ググり力が高い人はさらっと見つけて時短できるんだろうな。ググり力もエンジニアには重要だなと感じました。
- 投稿日:2019-06-27T14:07:49+09:00
カラムの型について
integer:数を扱う
string:文字に使用する。mysqlでは256で転ける仕様になっている?
text:長い文字に使用。ある程度長い文章を取り扱い場合は明示的にするためこちらを利用するほうが吉か。
boolean:真偽
datetime:日付時刻
- 投稿日:2019-06-27T13:55:31+09:00
rails のモデルを生成/削除する
rails g model モデル名 で生成 g は generateの略。
rails d model モデル名 で削除 d は destroyの略。
- 投稿日:2019-06-27T13:49:02+09:00
何も知らないがActive Jobの基礎 from Rails Guide
Active Jobとは
ジョブを宣言し、それによってバックエンドでさまざまな方法によるキュー操作を実行するためのフレームワーク。
キューとは
first in, first outのデータ構造。スタックの逆。
ジョブとは
定期的なクリーンアップを始めとして、請求書発行やメール配信など、あらゆる処理がジョブになる。これらのジョブをより細かな作業単位に分割して並列実行することもできる。
ジョブとは、コマンドやプログラムがまとまった、ひとかたまりの処理のことだ。
Active Jobの目的
Railsアプリケーションにジョブ管理インフラを配置すること。
これにより、
- Delayed JobとResqueなどのように、さまざまなジョブ実行機能のAPIの違いを気にせずにジョブフレームワーク機能やその他のgemを搭載することができるようになる。
- バックエンドでのキューイング作業では、操作方法以外のことを気にせずに済む。
- ジョブ管理フレームワークを切り替える際にジョブを書き直さずに済む。
ジョブキュー処理のResqueとDelayed Jobの使い分けの方針などはありますか?
以下の3つはジョブキュー処理の主要gem。
Resque
良い点
- RMagickなどのメモリリークが存在するコードでも不安なくデプロイできる。
- エコシステムが出来上がってる。悪い点
- 毎回forkするので長時間ジョブ向き。
- キューのストレージとしてRedisが必要。
- メンテが追いついてない。Delayed Job
良い点
- 特になし。悪い点
- DMに専用テーブル作成が必要。
- メモリリークしてるコードがあれば定期的な再起動が必要。Sidekiq
良い点
- Resque互換API。
- 並列に動作するので、外部サイトへのAPI呼び出しなど、I/O待ちの比率が大きいような用途で使うのに便利。
- 1個のプロセスで動作させられるのでメモリ使用量が少なく経済的。悪い点
- プロセス肥大化には弱い。
- コネクションプールの扱い。
- 並列以外の扱い。ジョブの作成(飛ばす)
ジェネレータ使ったり。
ジョブの実行
production環境でのジョブのキュー登録と実行では、キューイングのバックエンドを用意しておく必要があります。=> Railsで使うべきサードパーティのキューイングライブラリ(Sidekiq, Resque, Delayed Jobなど)を決める必要があります。
Rails自身が提供するのは、ジョブをメモリに保持するインプロセスのキューイングシステムだけです。プロセスがクラッシュしたりコンピュータをリセットしたりすると、デフォルトの非同期バックエンドの振る舞いによって主要なジョブが失われてしまいます。
アプリケーションが小規模な場合やミッションクリティカルでないジョブであればこれでも構いませんが、多くのproductionでは永続的なバックエンドを選ぶ必要があります。バックエンド
キューイングバックエンドに接続できるアダプタ(キューイングライブラリ)がビルトインで用意されている。
ジョブはRailsアプリケーションに対して並列で実行されるので、多くのキューイングライブラリでは、ジョブを処理すためにライブラリ固有のキューイングサービスを(Railsアプリケーションの起動とは別に)起動しておくことが求められます。
キュー
多くのアダプタでは複数のキューを扱えます。Active Jobを使って、特定のキューに入っているジョブをスケジューリングできます。
その他
コールバック
Action Mailer
代表的なジョブ。Active JobはAction Mailerと統合されているので、非同期メール送信を簡単に行える。
国際化(i18n)
など
参考リンク
- 投稿日:2019-06-27T13:14:17+09:00
【Rails 備忘録】has_many先が存在しない場合、除外して格納する書き方。
はじめに
忘れてしまいがちなので備忘録。
条件
Project
モデルに、has_manyでParticipation
モデルが関連付けされている。createされた
project
にuserが参加アクションをすると、participation
が該当のprojectにぶら下がる形でcreateされる。なので、Projectのインスタンスであるprojectがparticipationを持っていない(
@project.participations => []
)ということがありえる。project.rbclass Project < ApplicationRecord has_many :participations ・ ・ ・viewでparticipationを持っているprojectだけを表示したい。
やり方として一番簡単そうなのは、例えばcontrollerで
@projects = Project.where(user: current_user)
みたいな感じにして、viewテンプレートでshow.html.erb<%= @projects.each do |project| %> <% if project.participations.present? %> <% project.name %> <% end %> <% end %>↑と書いてしまえばよいが、if文で毎回チェックさせるのもイマイチなので、
controllerで@project
を格納するタイミングでparticipation
がないものを除外させたい。participationを持っているprojectだけを格納したい
答えを先に書くと、
includes
とwhere
を駆使して、
.includes(:participations).where(participations: Participation.all)
みたいな書き方をすればよい。↓例:
participations_controller.rbclass ParticipationsController < ApplicationController def show @participation = Participation.find(params[:id]) @projects = Project.where(client: current_client).where.not(status: "finished"). includes(:participations).where(participations: @participations) end
- 投稿日:2019-06-27T13:14:17+09:00
【Rails 備忘録】has_many先が存在しない場合に除外してインスタンス変数に格納する書き方。
はじめに
忘れてしまいがちなので備忘録。
条件
Project
モデルに、has_manyでParticipation
モデルが関連付けされている。createされた
project
にuserが参加アクションをすると、participation
が該当のprojectにぶら下がる形でcreateされる。なので、Projectのインスタンスであるprojectがparticipationを持っていない(
@project.participations => []
)ということがありえる。project.rbclass Project < ApplicationRecord has_many :participations ・ ・ ・viewでparticipationを持っているprojectだけを表示したい。
やり方として一番簡単そうなのは、例えばcontrollerで
@projects = Project.where(user: current_user)
みたいな感じにして、viewテンプレートでshow.html.erb<%= @projects.each do |project| %> <% if project.participations.present? %> <% project.name %> <% end %> <% end %>↑と書いてしまえばよいが、if文で毎回チェックさせるのもイマイチなので、
controllerで@project
を格納するタイミングでparticipation
がないものを除外させたい。participationを持っているprojectだけを格納したい
答えを先に書くと、
includes
とwhere
を駆使して、
.includes(:participations).where(participations: Participation.all)
みたいな書き方をすればよい。↓例:
participations_controller.rbclass ParticipationsController < ApplicationController def show @participation = Participation.find(params[:id]) @participations = Participation.search_by_client(current_client) @projects = Project.where(client: current_client).where.not(status: "finished"). includes(:participations).where(participations: @participations) endshow.html.erb<%= @projects.each do |project| %> <% project.name %> <% end %>
- 投稿日:2019-06-27T12:35:37+09:00
Rails6 のちょい足しな新機能を試す42(multi-db db:migrate:status編)
はじめに
Rails 6 に追加されそうな新機能を試す第42段。 今回は、
multi-db db:migrate:status
編です。
Rails 6 では、 複数データベースに対応しているため、db:migrate:status
も複数データベースに対応しています。Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は
gem install rails --prerelease
でインストールできます。$ rails --version Rails 6.0.0.rc1今回の準備
今回は、 Rails6 のちょい足しな新機能を試す35(複数データベース migration --database オプション編) をベースとして進めます。第35段まで終わっている状態を前提として話しを進めます。
一旦DBを作り直す
必要ないかも知れませんが、念のため、綺麗な状態から作業したいため、DBを作り直すことにしました。
bin/rails db:drop db:create db:migrateカラムを追加するマイグレーションを作成する
これもまあ、必要ないと言えば必要ない気がするのですが、
backbone
のusers
テーブル に
library
のbooks
テーブルにisbn
カラムを追加するマイグレーションを作成します。
library
側に追加するためには、--database (--db)
オプションが必要です。$ bin/rails g migration AddEmailToUser email $ bin/rails g migration AddIsbnToBook isbn --database=library
db:migrate:status
で確認する
db:migrate:status
で確認すると、両方のデータベースのマイグレーションの状況が確認できます。$ bin/rails db:migrate:status database: backbone_development Status Migration ID Migration Name -------------------------------------------------- up 20190608225735 Create user down 20190622004922 Add email to user database: library_development Status Migration ID Migration Name -------------------------------------------------- up 20190608225808 Create book down 20190622005021 Add isbn to bookどちらか1つのデータベースのマイグレーションの状態を知りたい場合は、
status
に続けて:backbone
か:library
を指定します。$ bin/rails db:migrate:status:backbone database: backbone_development Status Migration ID Migration Name -------------------------------------------------- up 20190608225735 Create user down 20190622004922 Add email to user$ bin/rails db:migrate:status:library database: library_development Status Migration ID Migration Name -------------------------------------------------- up 20190608225808 Create book down 20190622005021 Add isbn to book試したソース
試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try042_multidb_migrate_status注意
上のブランチで試す場合は、以下のように実行すれば、
bin/rails db:migrate:status
の結果が同じになります。$ bin/rails db:drop db:create $ bin/rails db:migrate:backbone VERSION=20190608225735 $ bin/rails db:migrate:library VERSION=20190608225808参考情報
- 投稿日:2019-06-27T12:02:48+09:00
【Rails】ER図 と アソシエーションを100%理解する
概要
モデル同士の繋がりを指すアソシエーション(関連付け)
定義しておくとモデルをまたいだデータの呼び出しが可能になります。また親と子の関係や、外部キーの設定などを理解していないと、
「親モデルのデータを先に追加しないと子のデータが追加できない。」なんて
DB設計の見直しが起きる可能性も・・データベースを可視化できるER図はとても大切。学習メモとして残します
参考
(1)感動の分かりやすさ。ER図の基礎の基礎知識もあり。初心者必見
【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】(2) 理解に苦しんだ・・ 中間テーブルってやつです。
【初心者・独学者向け】Ruby on Railsで中間テーブルを作成し、多対多を実現する
親と子の関係
【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】 がとても分かりやすかったので引用します
モデル同士の親子関係とは
図で考えてみましょう。
Aさん(User)は、自分が作ったブログサイトでたくさんの記事(Article)を投稿します。Bさんも少しは投稿します。
つまり、User一人一人は沢山のArticleを持っている(User has many articles.)、と考えることができます。(この突然出てきた英文は伏線ですよ!!!!)
逆の立場(Article)も考えてみましょう。
ある日投稿された記事(Article)は、Aさん(User)によって書かれました。次の日投稿された記事はBさんによって書かれました。
他の日に投稿された記事も、それぞれAさん、Bさんどちらかによって書かれた記事です。Aさん、Bさんが共作で書いた記事というのはありえません。つまり、Articleは誰か一人のUserに所属している(Article belongs to a user.)と考えることができます。
UserがいなくてはArticleは生まれないし、Articleは必ず誰か一人のUserから生まれます。そう、Userが親でArticleが子となっているわけなのです。これが親子関係です。
このような関係をUserとArticleは一対多の関係または1:Nの関係といいます。
もちろんUserが1でArticleが多です。この親と子の関係をつくるためのデータベース上でのモデルの関連付けをアソシエーションといい
アソシエーションしないと、どのユーザーがどの記事が書いたのか分かりません。多対多の関係と中間テーブル
多対多の関係とは ?
どちらのモデルから見ても、自分も相手も複数×複数になる状態です。
記事から見るとカテゴリーを複数持っているので、記事に対して、カテゴリーは多になります。
カテゴリーから見ると1カテゴリーに複数の記事が関連しているので、カテゴリーから見ると記事は多になります。 この>関係が多対多になります。
【初心者・独学者向け】Ruby on Railsで中間テーブルを作成し、多対多を実現する
中間テーブルとは?
多対多の関係は2つのモデルでは実現できないんです。
え、なぜ?例えば、先ほどの記事とカテゴリーの例。
【記事テーブル】
id 記事タイトル ❶ ❷ 記事ができる度に、自動生成のidが付いていきます。
主キー(primary key)ですね。【カテゴリーテーブル】
id カテゴリー名 ① 日常 ② Rails 【中間テーブル】
id 記事タイトル カテゴリー名 1 ❶ ① 2 ❶ ② 2 ❷ ② 2 ❷ ① 可能性の話ですが、上記のように 1つの記事がもしかしたら何個もカテゴリーを持つかもしれないわけです。
「記事タイトル」: ライフスタイル / アウトドア / 子育て とかそんな感じでしょうか。今はまだ記事タイトルもカテゴリーもどちらも2つしかないので、
中間テーブルいらなくない?ってなると思います。でも記事とカテゴリーはどちらもどんどんどんどん増えていく可能性がある・・そうするとこの組み合わせは無限大になるし、
カラム数は最初に設定したらもう増やせないので、多対多の間は中間テーブルで対応してあげます。モデルの関連性設定に
has_many, through:
が加わります!上の例で引き続き。
中間テーブルができるとモデルの関連性設定が変わりますまずはマイグレーションを実行。
20190627075545_create_categories_articles.rbclass CreateCategoriesArticles < ActiveRecord::Migration def change create_table :categories_articles do |t| t.integer :category_id t.integer :article_id t.timestamps null: false end end end次に中間テーブルで記事モデルとカテゴリーモデルとの関連付けを行う。
models/categories_ariticles.rbclass CategoriesArticle < ActiveRecord::Base belongs_to :category belongs_to :article end記事モデルの関連付け
models/article.rbclass Article < ActiveRecord::Base has_many :categories_articles has_many :categories, through: :articles_categories endカテゴリーモデルの関連付け
models/category.rbclass Category < ActiveRecord::Base has_many :categories_articles has_many :articles, through: :categories_articles end中間テーブルを通して繋がっているものには、
through::
というkeyをつけます。throughオプションによりarticles経由でcategoryにアクセスできるようになります
外部キーとは? (foreign key)
リレーショナルデータベース(RDB)で、テーブルのある列に、別のテーブルの特定の列に含まれる項目しか入力できないようにする制約。
商品が消えたら、いいねも消える
例えば、あるECサイト
ユーザーのマイページには自分がいいねした 「いいね一覧」 があるとします。
個人的には商品がもう存在しない場合には、いいねも消えてしまっていいのでは?と思います。そんな時は
dependent: :destroy
を使いますdependent: :destroy の役割
商品といいねのモデルには、以下のようなリレーションの記載があります。
models/product.rbclass Product < ApplicationRecord has_many :favorites, dependent: :destroy endmodels/favorite.rbclass Favorite < ApplicationRecord belongs_to :user belongs_to :product end
dependent: :destroy
と言う箇所は、親にあたる投稿が削除されたら、
子をどう扱うのかオプションを設定できるという感じです。今回は消去するので:destroy
残しておいて良いのであれば、何も記載しなくてOkay【Rails】ActiveRecordの:dependent使い分けまとめ【: destroy, :delete, :nullify】
- 投稿日:2019-06-27T11:12:02+09:00
【Rails】Action Mailer でメール送信機能をつくる
概要
Action Mailerを使ってメールの送信機能をつくります
今回は管理者がユーザーからのお問い合わせに対して、管理者画面から返事をすると、
ユーザーにメールで送信される機能を実装していきます。学習メモとして記録
参考
(1)RAILS GUIDES: Action Mailer の基礎
(2)【Ruby on Rails】メール送信の実装手順(ActionMailer)とはまったエラーなど
(3)Railsでメール自動配信機能をつくるまでの道程
(4)Rails の ActionMailer でメール送信処理
(5)RailsのAction Mailerでメール送信Action Mailerとは
Ruby on Rails に組み込まれているメール送信機能のこと。
Action Mailer を使うと、Ruby on Rails からメールを送信してくれます・メールマガジンの一斉送信
・ウェブサイトに会員登録した時のthank youメール
・お問い合わせフォームの記入内容が管理者にメールでも送信されるのような時にアプリケーションからメールを送信する機能と解釈してます。
アプリケーションのメーラークラスやビューからメールを送信することができる便利な機能です!導入手順
1. メイラーを生成
railsコマンド(
rails generate
)で生成します。 ContactMailer は任意クラス名。
今回はお問い合わせ関係のメール送信機能を作成したいためContactMailerです。terminal.生成$ rails generate mailer ContactMailer↓
terminal.結果Running via Spring preloader in process 1893 create app/mailers/contact_mailer.rb invoke erb create app/views/contact_mailer invoke rspec create spec/mailers/contact_mailer_spec.rb create spec/mailers/previews/contact_mailer_preview.rb無事に生成されましたね
2. サーバーを設定
メールを送信するときは、送信するサーバーが必要。と言うことで、
config/environments/development.rb
にメール送信設定を記述します。
今回はg-mailを使う記載方法です。development.rbRails.application.configure do #--- 中略 ---# config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { port: 587, address: 'smtp.gmail.com', domain: 'gmail.com', user_name: '<YOUR EMAIL ADDRESS>', password: '<YOUR EMAIL PASSWORD>', authentication: 'login', enable_starttls_auto: true } end
port:
も上記のままで大丈夫です。上から順々に見ていきます。
ここでは、config.action_mailer
というパラメーターに色んなオプションを指定してます。1行目
raise_delivery_errors
メールの送信に失敗した時にエラーを出すかどうか (出したいので true)2行目
delivery_method
メールの送信方法。 デフォルトで:smtd
なので気にする必要もないのですが、
わたしみたいに「なにそれ!?」ってなった方は以下の引用を読んでみてください。「SMTP」とは「Simple Mail Transfer Protocol(シンプル・メール・トランスファー・プロトコル)」の略で、あえて>訳せば「簡単なメールの送信の手順」というところだろうか。お約束ごとと考えてもいい。
あなたがメールを書き、宛先のアドレスを入力して「送信」アイコンをクリックする。このとき、あなたのスマホやパソコン>は、この「SMTP」のお約束ごとに従って、あなたが契約しているメールサーバーと、こんなやり取りをするのである。
「メールを送るよ〜」「ええで!」「宛先は〇◯だよ」「りょ」「本文はかくかくしかじかだよ」「受け取ったで!」――とまぁそんな具合。出典: メール設定で最初につまずく「SMTP」「POP」「IMAP」。その意味&設定方法は?
3行目
smtp_settings
smtpの詳細設定って感じです。
- port => SMTPサーバーのポート番号
- address => SMTPサーバーのホスト名
- domain => HELOドメイン
- user_name => メール送信に使用するgmailのアカウント
- password => メール送信に使用するgmailのパスワード
- authentication => 認証方法
- enable_starttls_auto => メールの送信にTLS認証を使用するか
3. メーラーを編集
メーラーってRailsのコントローラーと似てるんですね。
「アクション」と呼ばれるメソッドがあり、メールの内容をつくるのにビューを使います。生成直後は、以下のような application_mailer.rb と
app/mailer/application_mailer.rbclass ApplicationMailer < ActionMailer::Base default from: 'from@example.com' layout 'mailer' end空のメーラー
app/mailer/contact_mailer.rbclass ContactMailer < ApplicationMailer endがあるはずです。
application_mailer
には全メーラー共通の設定を、
sample_mailer
にはメーラー個別の設定をします。application_mailerを編集します
app/mailer/contact_mailer.rbclass ApplicationMailer < ActionMailer::Base default from: "管理人 <from@example.com>", layout 'mailer' end共通の処理・設定を記述する場合には
defaultメソッド
を使用します。
プロパティ 役割 to 送信先の指定 cc 一斉送信先の指定 bcc 非表示送信先の指定 from メールの送信元名 subject メールタイトル date メールの送信日時 reply_to 返信用アドレスの指定 などが指定できます。
contact_mailer.rb を編集します
今回は管理者の返信がユーザーのe-mailに届くようにします。
なのでメソッドをsend_when_admin_reply
と定義しました。app/mailer/contact_mailer.rbclass ContactMailer < ApplicationMailer def send_when_admin_reply(user, contact) #メソッドに対して引数を設定 @user = user #ユーザー情報 @answer = contact.reply_text #返信内容 mail to: user.email, subject: '【サイト名】 お問い合わせありがとうございます' end end個別の設定には
mailメソッド
を使用します。
send_when_replyedメソッド
を呼び出す時に渡されるユーザーの情報から、
emailアドレスだけを取り出してメールの送信先としてします。
mailメソッド
が呼び出されると、メール本文が記載されているビューが読み込まれます。
インスタンス変数(@xxx)でメーラービューに値を渡してあげたいので、インスタンス変数を用意してる感じです。次は、そのメールの本文を作成していきます
4. メールの本文を作成する(メーラービューの作成)
app/views/contact_mailerディレクトリ
下にファイルを2つ作成します。1つはHTMLフォーマット、もう一つはテキストメール。
顧客によってはHTMLフォーマットのメールを受け取ることができない / 受け取りたくない人もいるので、テキストメールも作成しておくのが最善です。HTMLファイルの作成
views/contact_mailer/send_when_admin_reply.html.erb<h2><%= @user.name %> 様</h2> <p>この度は、お問い合わせありがとうございました。<br> 以下でご質問の回答となっておりますでしょうか。</p> <p><%= @reply %></p> <p>今後とも XXX をよろしくお願いいたします。</p>テキストファイルの作成
views/contact_mailer/send_when_admin_replyd.text.erb=============================== <%= @user.name %> 様 =============================== この度は、お問い合わせありがとうございました。 以下でご質問の回答となっておりますでしょうか。 <%= @reply %> 今後とも XXX をよろしくお願いいたします。5. 実際に処理を走らせるアクションにメール送信処理をさせる
メールの設定を記述しただけではメールは送信できません。
メーラー(ここではapplication_mailer.rb
/contact_mailer.rb
)は各コントローラーのアクションからの呼び出しによって起動します。管理者がアプリケーション管理画面フォームより返信を送信し、その内容をメールでユーザーに送信します。
実際に処理を走らせるアクションはadmin/contacts_controller.rb
となります。app/controllers/contacts_controller.rbclass Admin::ContactsController < Admin::ApplicationController def update contact = Contact.find(params[:id]) #contact_mailer.rbの引数を指定 contact.update(contact_params) user = contact.user ContactMailer.send_when_admin_reply(user, contact).deliver end end例えば、ユーザーが新規アカウント登録をしてメールを送信する場合は、
app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :set_user def create if @user.save #ユーザーのインスタンスが新しく生成されて保存されたら NotificationMailer.send_when_signup(@user).deliver #確認メールを送信 redirect_to @user else render 'new' end end endcreateアクションに処理を走らせることになります。
わたしのアプリケーションでは、DBの設計上
ユーザーが問い合わせをした時点で、Contactsテーブルの中に、
1つレコードが準備されていて、その中に'返信'というカラムが用意されています。なので、updateアクションを使って
すでにあるレコードの中に管理者の返信のデータだけを更新してあげる形になります6. テストしてみる
以上でメーラーができるはずです・・!
実際にアプリケーションを動かしてみて、メールを受信できたら成功以上、学習メモでした
- 投稿日:2019-06-27T10:24:27+09:00
RailsでFontAwesomeを使う
内容
- FontAwesomeとは
- FontAwesomeを使う準備
- FontAwesomeをhamlで
- FontAwesomeをボタンとして使う(最終目標)
- link
- file選択
初学者の復習も兼ねた投稿ですので、間違い等ありましたらご指摘いただけると嬉しいです。
FontAwesomeとは
こちらが公式ページです。
アイコンを自由に使えます!一気にサイトがいい感じになります。FontAwesomeを使う準備
導入方法はいくつかありますが、今回はRailsで使うのでgemをインストールします。
Gemfilegem 'font-awesome-sass', '~> 5.4.1'terminalbundle install/assets/stylesheets/application.scss@import 'font-awesome-sprockets'; @import 'font-awesome';これで準備完了です。
FontAwesomeをhamlで
= fa_icon 'edit'これでOKです。
'edit'の部分は、FontAwesomeのページから使いたいアイコンを探して、
そのアイコン名を入れます。デベロッパーツールで確認してみると、
<i class="fa fa-edit"></i>こんな感じで反映してくれてます。
FontAwesomeをボタンとして使う
link_toで使う
ペンのアイコンが新規作成ページへのリンク、
歯車のアイコンが編集ページへのリンクになってます。通常のlink_toは
= link_to "New", root_pathのような形で第一引数にリンクの名前、第二引数に行き先を書くと思いますが、
doを使ってブロックにしてあげることでFontAwesomeが使えます。= link_to new_path do = fa_icon 'edit' = link_to edit_path(current_user) do = fa_icon 'cog'(_pathの部分は仮です)
インデントにご注意!
(そもそもhamlにはdoに対するendが不要ということを知らずにしばらく悩んでしまいました…)先ほど示したように
<i class="fa fa-edit"></i>
このような形になるので、i { font-size: 1.3em; color: black; }こんな感じでcssを書いてあげるとスタイルを変更できます。
file選択のボタンとして使う
フォームの端にある写真アイコンを押すと、ファイル選択の画面が開くようになってます。デフォルトの画像選択ボタンを変更
編集の都合上、ちがうサイトを参照します。分かりにくくてすみません。
元々はこういう「ファイルを選択」ボタンになっていると思います。
これをdisplay:none;
で見えなくしちゃいます。で、labelタグをつけて、そこにスタイルをあててあげます。
haml.field = f.label :image, "ここを押したら画像選択できるよ", class: "label" = f.file_field :image, class: "file"scss.label { cursor: pointer; font-size: 16px; border: 1px solid gray; border-radius: 10px; padding: 10px; } .file { display: none; }参考:https://proengineer.internous.co.jp/content/columnfeature/7605
FontAwesomeに置き換え
ここまで来たらあとは簡単です。
先ほどラベルタグをつけたところを do でブロックにしてFontAwesomeにするだけです。
(くれぐれもインデントにご注意!(二度目))haml= form_for [@group, @message] do |f| = f.label :image, class: "main-form__image" do = fa_icon "image" = f.file_field :image, class: "main-form__image--default"scss.main-form__image { cursor: pointer; i { font-size: 1.3em; color: black; } &--default { display: none; } }余談
個人的にこの実装に結構悩んで時間を使ってしまったのですが、
なぜかというと、やりたい実装を分解して考えられなかったからです。検索するときに
「FontAwesomee file選択」
など、いきなりFontAwesomeをfile選択ボタンにするやり方を調べてしまっていました。が、分解して考えると、
1. デフォルトの選択ボタンを変更する
2. 変更したものをFontAwesomeに置き換える
という手順を踏むことになります。なので検索も
「file選択 変更」
などにすれば一気に答えに近づきます。まだプログラミング学習一ヶ月ですが、
エンジニア力はググり力、というのを実感しました。
- 投稿日:2019-06-27T09:57:14+09:00
【Rails】factory_botの使い方について
はじめに
Railsにはfactory_botという便利なgemがあり、そのgemについてまとめました。
factory_botというgemを一言で言うとRspecで用いるテストデータの作成を楽にしてくれるgemです。
Rspecを入れていない方は下記の記事を参考にして入れると良いと思います。@Mosacさんの記事
RSpecをRailsにインストールするfactory_botを使用する場合は下記のような形でGemfileに記述します。
Gemfilegem "factory_bot_rails"使用方法
導入方法
spec/配下にsupportフォルダを作成し、support配下にfactory_bot.rbを作成し、以下を記述する。
factory_bot.rbRSpec.configure do |config| config.include FactoryBot::Syntax::Methods endspec/rails_helper.rbにコメントアウトしてある下記のコメントアウトを外し、
support配下のファイルを読み込むように設定する。rails_helper.rbDir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }テストデータの作成方法
spec/factories/配下にテストしたい該当モデルのファイルを作成する。
Userモデルならusers.rbという形でファイルを作成する。
カラムに仮の値を定義して作成する。spec/factories/users.rbFactoryBot.define do factory :user do name { "hoge" } email { "foo@bar.com" } end end上記の場合、userとしてnameがhoge,emailがfoo@bar.comのデータが仮作成される。
モデル名以外の名前をつける場合は下記のように記述する。
spec/factories/users.rbFactoryBot.define do factory :adminuser, class: User do name { "hoge" } email { "foo@bar.com" } end endRspecでのテストデータ使用方法
定義の仕方は下記の通り。
spec/models/users_spec.rbRSpec.describe User, type: :model do let(:user){ FactoryBot.create(:user) } #通常の書き方 DB登録される let(:user){ create :user} #省略記法 let(:user){ FactoryBot.build (:user)} #通常の書き方 DB登録されない let(:user){ build :user} #省略記法 let(:user){ build :user, name: "hogehoge"} #上書きすることも可能 end上記のようにデータを定義することによって、モデルやコントローラー上で
仮のデータを使ったテストをすることが可能になる。参考記事
- 投稿日:2019-06-27T02:23:40+09:00
gem impressionist を使ってユニークpv数を計測【Rails】
ruby 2.5.1
rails 5.2.1はじめに
impressionistを使ってpv数を計測しようと思い、色々調べながら実装するもなかなかユニークpv数が取得できずに苦戦していました。結果的にreadme(https://github.com/charlotte-ruby/impressionist) を見て解決したので、その記録として残しておきます。最初からreadme見ればよかった。。。
導入方法
Gemfilegem 'impressionist'$ bundle install次にpvを保存するテーブルを作成します。
$ rails g impressionistこのようになっていれば大丈夫です。
Running via Spring preloader in process 40810 invoke active_record create db/migrate/20190626153731_create_impressions_table.rb create config/initializers/impression.rbmigrateします。
$ rails db:migrateModel
今回はQ&Aサイトの質問ページのpv数を取得したいためQestionモデルに以下のように記載します。
question.rbclass Question < ApplicationRecord is_impressionable endControllers
セッションハッシュでフィルタリングされたモデルからユニークpv数を取得します。IPで判別すると同じIPを使用する訪問者を計測できないのでセッションハッシュで判別するのがいいっぽいです。
question_controller.rbclass QuestionsController < ApplicationController impressionist unique: [:session_hash] def show @question = Question.find(params[:id]) impressionist(@question, nil, unique: [:session_hash]) end endViews
show.html.erb<%= @question.impressionist_count %>更新してもテーブルのレコードや表示されるpv数が増えていなければ完璧!
他の記事を参考したけど全然できなかったのでreadmeを読んだのですが、これが足りなかったようです。
impressionist unique: [:session_hash]
公式リファレンスを見る習慣をつけます。
- 投稿日:2019-06-27T01:25:50+09:00