- 投稿日:2020-03-15T22:46:40+09:00
多対多
多対多
『Instagram』を例とします。『Instagram』はひとつの写真に複数のタグ付けができます。こうしたタグ付け機能を実現するには3つのテーブルが必要です。タグを保存するテーブル、写真を保存するテーブル、そして「どの写真にどのタグが登録されているか」を保存するテーブルです。
このうち写真やタグを保存するテーブルの名前はそのままtagsテーブル、photosテーブルとします。「どの写真にどのタグが登録されているか」のテーブルは、慣習的にphotos_tagsテーブル、と名付けます。関係する2つのテーブルをアンダーバーでくっつけています。
例ですがそれぞれのテーブルに保存されているレコードの関係性は以下のようにしたとします。
tagsテーブル photosテーブル photos_tagsテーブル id id id name name tag_id ..... location photos_id ..... ...... ..... ひとつの写真には「東京/カフェ/おしゃれ」のように複数のタグが属しています。
また同じ「東京」のタグに対してもたくさんの写真が投稿されるので、ひとつのタグに複数の写真が属しているとも言えます。
このような関係が多対多です。多対多の関係を表現するには、photos_tagsテーブルのような中間テーブルが必要です。中間テーブル
中間テーブルには、「どの写真がどのタグと関連づいているか」という情報が記載されていることになります。
ひとつのレコードには「photos_id × tags_id」の組み合わせが記録され、全ての写真とタグの組み合わせの数分、レコードが蓄積されていきます。10個の写真にそれぞれ3つずつタグが付いている場合、全ての関係性を表すのに中間テーブルのレコードは30個生成されるということになります。has_many throughオプション
モデルに多対多を定義するときに利用します。
throughは、「〜を経由する」という意味です。
【例】# app/models/photo.rb class Photo < ActiveRecord::Base has_many :photos_tags has_many :tags, through: :photos_tags end # app/models/tag.rb class Tag < ActiveRecord::Base has_many :photos_tags has_many :photos, through: :photos_tags end # app/models/photos_tag.rb class PhotosTag < ActiveRecord::Base belongs_to :photo belongs_to :tag end上記のようなアソシエーションを定義することで、自動的に以下のようなメソッドが使えるようになります。
【例】# 通常レコードの作成 tag1 = Tag.create(name: "東京") tag2 = Tag.create(name: "おしゃれ") photo1 = Photo.create(image: "cool_cafe.jpg", location: "渋谷") # 多対多関係を定義したレコードの作成 tag1.photos.create(image: "cute_shop.jpg", location: "原宿") # リレーションの追加 tag1.photos << photo1 photo1.tags << tag2 # リレーションを利用したレコードの取得 photo1.tags # => #<Tag id:1, name:"東京">,#<Tag id:2, name:"おしゃれ">「通常レコードの作成」のところでは単体のインスタンスを生成しています。【例】は、nameプロパティを持ったtagインスタンスとimage及びlocationプロパティを持ったphotoインスタンスです。これらの間には多対多の関係が定義されています。
「多対多関係を定義したレコードの作成」のところでは多対多の関係性を利用し、tag1に関係したphotoインスタンスを新たに生成しています。photosと、複数形になります。
「リレーションの追加」のところでは後からインスタンス同士を関連付けることも可能です。<<メソッド「配列オブジェクト << 追加する要素」を使用すると実現することができます。
tag1と関係しているphotosの配列に、新たにPhotoクラスのインスタンスを追加することでリレーションを生成しています。
photo1と関係しているtagsの配列に対し、新たにtag2とのアソシエーションを追加しています。「リレーションを利用したレコードの取得」のところではリレーションしている要素を全て出力することもできます。【例】だとphoto1は「リレーションの追加」でそれぞれtag1・tag2に関連付けられているので、返り値としてtag1ならびにtag2のインスタンスが出力されています。
has_manyメソッドの他のオプション指定
オプション名 用途 class_name 関連するモデルのクラス名を指定でき、関連名(photos_tags等、has_manyの直後に書くもの)と参照先のクラス名(PhotosTagのようなモデル名)を異なるものにできる foreign_key 参照先を参照する外部キーの名前を指定できる(デフォルトは、参照先のモデル名_id) dependent 親モデルのデータを消したら関連するモデルのデータも連動して消したいときに使用します。destroyとdelete_allでひとつひとつ消していくか、一気に消すかを指定できる source 関連テーブルから先のモデルにアクセスするための(関連モデルから見た)関連名を指定できる
- 投稿日:2020-03-15T22:34:17+09:00
マイクロポストにタグ付けする
はじめに
ご訪問いただきありがとうございます。レベル的にはrailsチュートリアルを終えたくらいのレベルです。もし間違えているところ等あればアドバイスいただけると幸いです。
作るもの
今回はマイクロポストに自由にタグ付けと編集の機能を追加します。
(題材は自分のポートフォリオサイトです。尚、すでにUserテーブルとMicropostテーブルは作成済みです。)対象読者
railsチュートリアルに機能を追加したい等自分と同じ位のレベルの人を対象としています。
作成の流れ
1.TagモデルとTag_relationshipモデルの作成
2.モデルの関連付けとバリテーション
3.対応するビューの作成
4.コントローラーのアクション作成
5.タグ編集機能の作成1.TagモデルとTag_relationshipモデルの作成
今回はタグ付機能を作ります。マイクロポストから見ると一つのポストに一つ以上のタグをつけることができる。タグから見ると一つのタグは一つ以上のマイクロポストに関連づくということになります。つまり多対多の関連付けとなり、MicropostもでるとTagモデルの中間にTagrelationテーブルを設ける方法で今回は進めます。それぞれの中身はこんな感じです。
Tag_relationship
カラム 型 id integer micropost_id integer tag_id integer Tag
カラム 型 id integer name string モデルを作成します。
rails g model tag name:stringrails g model tag_relationship micropost:references tag:references作成されたマイグレーションファイルを確認します。
db/migrateclass CreateTags < ActiveRecord::Migration[5.1] def change create_table :tags do |t| t.string :name, null: false t.timestamps end end endnameを空で保存されるのを防ぐためにnull:falseを追記してます。
db/migrateclass CreateTagRelationships < class CreateTagRelationships < ActiveRecord::Migration[5.1] def change create_table :tag_relationships do |t| t.references :micropost, foreign_key: true t.references :tag, foreign_key: true t.timestamps end add_index :tag_relationships, [:micropost_id,:tag_id],unique: true end endこちらはreferencesを付けて作成したので自動的にreferencesが設定されています。また、後々nameで検索する可能性を踏まえindexをnameに追記しました。unipue:tureは同じ名前のタグを保存できないようにしています。
ちなみにadd_index :テーブル名, :カラム名で指定します。
※referencesはbelongs_toのエイリアスです。(tag_idは一つのTag.idと紐づく)
migrateを行います。rails db:migrate2モデルの関連付けとバリレーション
各モデルの関連付けは以下のようになります。
app/model/tag.rbclass Tag < ApplicationRecord has_many :tag_relationships, dependent: :destroy has_many :microposts, through: :tag_relationships validates :name, uniqueness: true endruby/app/model/tag_relationship.rbclass TagRelationship < ApplicationRecord belongs_to :micropost belongs_to :tag endapp/model/micropost.rbhas_many :tag_relationships, dependent: :destroy has_many :tags, through: :tag_relationshipsここで「has_many:microposts, through: :tag_relationships」
とすることによって、tag_relationshipモデルを通してタグに紐づくマイクロポストを取得することができるようになります。(マイクロポスト側も同じ)
注意点として、スルーの関連付けの前にrelationshipsの中間テーブルの関連付けを記載しないとうまく動作しません。(先にhas_many:tags~とすると中間テーブルが関連づいてないので中間テーブルがありませんとエラーが出ます。)
validates :name, uniqueness: trueでタグの名前が重複して保存されるのを防ぎます。3対応するビューの作成
今回はマイクロポストを作成する際にタグをつけるような設定とします。
app/views/microposts/new<%= form_with model:@micropost, local: true do |f| %> ※タグの部分のみ抜粋 <%= f.text_field :tag_ids, class: "form-control", id:'tag_ids',\ placeholder: "タグをつける。複数つけるには','で区切ってください。" %> <% end %>タグを複数つけるときは(,)で区切ってもらうようにします。
「f.text_field :tag_ids」と記載することでマイクロポストを作成するタイミングでタグの情報も一緒に送ります。
※tag_idsはマイクロポストで「has_many :tags, through: :tag_relationships」の関連付けをすることよってマイクロポストオブジェクトに使用できるようになります。例 Micropost.first.tag_ids => [2, 3, 4, 5]タグはマイクロポストの中に表示するので、マイクロポストのビューの中にタグの表示を追記します。
app/views/microposts/_micropost.html.erb※タグを表示する部分のみ抜粋 <% micropost.tags.each do |tag| %> <%= tag.name %> <% end %>4コントローラのアクション作成
マイクロポストの情報と一緒にタグの情報も送られてくるようになったのでマイクロポストのcreateアクションでタグも保存されるようにします。
app/controllers/microposts_controller.rbdef create @micropost = current_user.microposts.build(micropost_params) tag_list = params[:micropost][:tag_ids].split(',') if @micropost.save @micropost.save_tags(tag_list) flash[:success] = '投稿しました!' redirect_to root_url else render 'new' end end「tag_list」で送られてくるタグの値を取得します。複数のタグ付を行うときにsplit(',')で区切るようにします。
「@micropost.save_tags(tag_list)」でマイクロポストにタグを関連付けます。save_tagsメゾットの中身はこんな感じです。app/model/micropost.rbdef save_tags(savemicropost_tags) savemicropost_tags.each do |new_name| micropost_tag = Tag.find_or_create_by(name: new_name) self.tags << micropost_tag end「find_or_create_by」メゾットはカラムの中から同じ値がないか探して、あればそのままfindの動き、なければcreateの動きで新たにカラムに保存します。
これでタグ付機能は作成完了です。タグ編集機能の作成
ここからはタグの編集機能を付けていきます。
タグの編集はマイクロポストの編集ページで行います。(すでにマイクロポスト編集ページ作成済みの前提で行います)※タグの部分のみ抜粋(form_withを使用しているのでrenderでマイクロポストのフォームページと共有できます。)
app/micropost/edit<%= f.text_field :tag_ids,value: @tag_list,\ placeholder: "タグをつける。複数つけるには','で区切ってください。" %>ここで「@tag_list」で既存の値を表示しています。
それを踏まえたコントローラのアクションがこちらです。app/controllers/microposts_controller.rbdef edit @micropost =Micropost.find(params[:id]) @tag_list =@micropost.tags.pluck(:name).join(",") end def update @micropost =Micropost.find(params[:id]) tag_list = params[:micropost][:tag_ids].split(',') if @micropost.update_attributes(micropost_params) @micropost.save_tags(tag_list) flash[:success] = '投稿を編集しました‼' redirect_to @micropost else render 'edit' end endまずeditアクションには既存のタグを取得すために「@tag_list」を追記します。
tagsでマイクロポストに関連するタグを取得
pluck(:name)でタグのnameの配列を取得
join(",")で配列を,で区切った文字列として取得します。「save_tags」を更新用に編集します。
※マイクロポスト編集ページへの「link_to」で行き先を「edit_micropost_path」としたときにマイクロポストshowページ以外からリンクを踏んでアクセスするとタグフォームに既存の値が入らずうまくタグの更新ができなかったので「/microposts/#{micropost.id}/editでリンクを設定してます。
app/model/micropost.rbdef save_tags(savemicropost_tags) current_tags = self.tags.pluck(:name) unless self.tags.nil? old_tags = current_tags - savemicropost_tags new_tags = savemicropost_tags - current_tags old_tags.each do |old_name| self.tags.delete Tag.find_by(name: old_name) end new_tags.each do |new_name| micropost_tag = Tag.find_or_create_by(name: new_name) self.tags << micropost_tag end end動作は既存のタグを残すものと消すものに分けて、新しいタグを既存の残すタグに追加していくといった形です。
「current_tags」既存のタグを取得
「old_tags」消すタグを取得。
「new_tags」新たに追加するタグを取得。
old_tagsとnew_tagsにある「ー」は引き算です。
それぞれ引き算して残ったタグを繰り返し処理して完了です。
例
current_tags - savemicropost_tags =old_tags
「test, test2, test3」 - [test, test3] =[test2]以上でタグ付機能は完成となります。最後までお読みいただきありがとうございました。
アドバイス等いただけるととても喜びます。次は今回作ったタグを利用した検索機能を作ろうと思います。
ありがとうございました。
- 投稿日:2020-03-15T21:49:33+09:00
【Rails6・楽天API】商品検索機能の実装手順
はじめに
Railsで楽天APIを使用して商品(本)の検索、商品情報の取得・表示をする方法をまとめました。
今回は、本を題名で検索し、ヒットした本の情報を一覧にして下に表示する形です。バージョン
・Ruby 2.7.0
・Rails 6.0.2.1実装手順
アプリケーションIDの取得
楽天API情報を使用するには、Rakuten Developersより
「新規アプリ登録」を行いアプリケーションIDを取得する必要があります。
簡単な情報を入力してすぐに取得することができます。
AmazonAPIは申請を行い審査を受ける(落ちることも多いようです)ので、この点が違いますね。gemをインストール
Ruby SDK(公式)に従って進めます。
Gemfileに下記追記して、bundle
でインストール。Gemfilegem 'rakuten_web_service'アプリケーションIDを設定
取得したアプリケーションIDを設定します。
config/initializers
フォルダの中に下記ファイルを作成。rakuten.rbRakutenWebService.configure do |c| # (必須) アプリケーションID c.application_id = '*******************' # (任意) 楽天アフィリエイトID c.affiliate_id = '*******************' endこちらも、Ruby SDK(公式)を参照してください。
タイミングによっては書き方が微妙に異なるようです。ビューの作成
search.html.slim.search-box = form_tag(books_search_path, method: :get) .form-group = text_field_tag :keyword, '', id: "book_search", class: "form-control", name: "keyword", placeholder: "題名を入力してください" button title='検索' class="form-control" type='submit' | 本の題名を検索 - if @books.present? = render 'books/book'_book.html.slim- @books.each do |book| = book.title = book.author = book.item_caption = link_to (image_tag(book.medium_image_url)), book.item_url※フォーマットはかなり省いているので、適宜調整してください。
コントローラーの作成
books_controller.rbclass BooksController < ApplicationController def search if params[:keyword] @books = RakutenWebService::Books::Book.search(title: params[:keyword]) end end endルーティングの設定
routes.rbget 'books/search'題名検索→商品情報表示の流れ
上記コードで最低限の設定はOKのはずです。
本の題名を入力してから商品情報が表示されるまでの流れは下記のようになります。①
text_field_tag
に本の題名を入力して検索ボタンをおす
= form_tag(books_search_path, method: :get)
により、
books
コントローラーのsearch
アクションにいきます。
その際、入力した内容はparams[:keyword]
に格納されています。②楽天APIを使用した検索が行われる
入力した値params[:keyword]
を引数としてsearch
メソッドで検索をかけ、
ヒットしたものを@books
に格納します。
今回は本の検索で絞りたかったため、RakutenWebService::Books::Book
としてます。③再度ビューに展開する
検索結果が@books
に格納されたことにより、
ビューの- if @books.present?
がtrue
となり、パーシャルが表示されます。
パーシャルでは、上から本の「題名」「著者」「説明文」「画像(商品リンク)」を表示しております。
使用できるプロパティはまだまだたくさんあり、楽天ブックス書籍検索API に一覧があります。まとめ
検索結果から商品情報をビューの上でひっぱりだすところで苦戦しましたが、
公式に記載のパラメーターを落ち着いて確認することで対応ができました。
ご参考になりましたら幸いです。参考
●【Rails基礎】楽天APIで商品検索アプリを作ってみた
●Ruby on Rails で楽天商品ページを取得してみた
- 投稿日:2020-03-15T20:28:49+09:00
Railsでhamlファイルに動画を埋めこむ
今回はhamlファイル内に動画を埋め込む方法をご紹介します!
背景に動画を流したいときにオススメです。publicフォルダにvideosフォルダを作成
上記で作成したフォルダ内にあらかじめ用意した動画ファイルを保存
今回は拡張子mp4のファイル、sample.mp4を用います。
hamlファイルに以下を記述
○○.html.haml= video_tag("/videos/sample.mp4", autoplay: true, loop: true, muted: true, class: 'content__box__video')video_tag
ビデオタグを用いて動画ファイルを呼び出します。
/videos/sample.mp4
参照先のフォルダを指定します。
publicフォルダは指定する必要ありません。autoplay: true
ページ更新時に自動再生されます。
loop: true
繰り返し再生になります。
muted: true
無音になります。
class: 'content__box__video'
クラス名をつけてscssファイルでレイアウトを調整します。
参考
→ 投稿した動画を表示するにはまた別の方法が必要なので次回ご紹介します!
- 投稿日:2020-03-15T20:26:55+09:00
【1日目】今日の学び fontawesomeをeach文の中で繰り返して表示させる
【1日目】プログラミング初学者の今日の学び
プログラミング初学者が、ポートフォリオを作っている中で得た気づきを書いていきます。Fontawesomeをhtml.erbのループ内で使う
Microposts一覧表示ページにおいて、各micropostを削除するボタンを、Fontawesomeのゴミ箱アイコンで表現したかった。
埋め込みrubyの繰り返し内でhtmlタグを使いたい時は、ブロックを受け渡せば良いらしい。app/views/microposts/_micropost.html.erb<% @microposts.each do |micropost| %> <div> <%= link_to micropost.name, "#" %> <%= link_to micropost_path(micropost), :method => :delete do %> <i class="far fa-trash-alt"></i> <% end %> </div> <% end %>
- 投稿日:2020-03-15T20:26:55+09:00
【1日目】初学者の今日の学び ストロングパラメータ, fontawesomeについて
【1日目】プログラミング初学者の今日の学び
プログラミング初学者が、ポートフォリオを作っている中で得た気づきを書いていきます。Fontawesomeをhtml.erbのループ内で使う
Microposts一覧表示ページにおいて、各micropostを削除するボタンを、Fontawesomeのゴミ箱アイコンで表現したかった。
埋め込みrubyの繰り返し内でhtmlタグを使いたい時は、ブロックを受け渡せば良いらしい。app/views/microposts/_micropost.html.erb<% @microposts.each do |micropost| %> <div> <%= link_to micropost.name, "#" %> <%= link_to micropost_path(micropost), :method => :delete do %> <i class="far fa-trash-alt"></i> <% end %> </div> <% end %>
- 投稿日:2020-03-15T19:13:09+09:00
【Rails】where
whereメソッド
与えられた条件に対して合っていたレコードを全て返す。
#titleが「test」にマッチするレコードを全て取得 Book.where(title: "test") => [#<Book:0x007f978ebe0960 id: 2, title: "test", price: 960, publish: "test", created_at: Sat, 05 May 2018 05:58:20 UTC +00:00, updated_at: Sat, 05 May 2018 05:58:20 UTC +00:00>, #<Book:0x007f978ebe0780 id: 10, title: "test", price: 800, publish: "fuga", created_at: Mon, 17 Sep 2018 15:30:55 UTC +00:00, updated_at: Mon, 17 Sep 2018 15:30:55 UTC +00:00>]
- 投稿日:2020-03-15T18:33:17+09:00
Railsアプリをcloneした後にDocker上で動かすまで
GitHubからソースをcloneした後、Dockerを使った開発環境を構築するまでにやることをまとめます。
Dockerfile
やdocker-compose.yml
はすでにあるものとします。git管理外の必要なファイルを作成
database.yml
.rspec
.env
master.key
node_modules
などのファイルを必要に応じて作成
ビルド
Dockerfileを元にDockerイメージを作成します
$ docker-compose buildコンテナを立ち上げる
$ docker-compose up -dデータベースを作成
$ docker-compose exec <サービス名> /bin/bash $ rails db:create $ rails db:migrate $ rails db:seedここでブラウザにアクセスして、表示を確認しましょう。テストが書いてあればテストを走らせる。
DBクライアントアプリでDBに接続
docker-compose.yml
やdatabase.yml
の記述を参考に、データベースとTablePlusやSequelProなどのアプリを接続します。デプロイ周りの環境を構築する
Herokuとの接続や、capistranoを使ったデプロイができるように調整します。
- 投稿日:2020-03-15T18:26:03+09:00
form_withの記述
form_with
form_tagとform_forというのもあるのですが、Rails 5.1系以降ではform_withが推奨されています。
自動でパスを選択してくれて、HTTPメソッドを指定する必要がありません。
コントローラーから渡された、ActiveRecordを継承するモデルのインスタンスが利用できます。
inputタグは入りません。
これらの特徴はform_forとform_withの共通する部分です。そもそもform_tagとform_forとは
簡単にいうとモデルの有無です。例えば検索機能の場合はモデルが必要ないのでform_tagを使います。
【例】form_tag<%= form_tag('XXXX_path', method: :post) do %> (中身) <% end %>上記みたいにパスを指定したりします。まだいろいろと記述の仕方はあるのですが今回はform_withなので省きます。
【例】form_for%= form_for @XXXX do |form| %> (中身) <% end %>コントローラーで@XXXXを定義しています。内容はこちらも省いていきます。
form_withの記述
form_withは、form_forとform_tagの機能を組み合わせたものです。なのでform_withひとつでどちらも対応できます。
【例】form_with<%= form_with(model: @XXXX, local: true) do |form| %> <h3> 投稿する </h3> <%= form.text_field :name, placeholder: "ニックネーム" %> <%= form.text_field :image, placeholder: "Url" %> <%= form.text_area :text, placeholder: "text", rows: "10" %> <%= form.submit "送信" %> <% end %>コントローラーで@XXXXを定義しています。
local: trueオプション
form_withでは、デフォルトの状態ではremote: trueというajaxでの通信が行われる設定になっています。それをキャンセルするためにlocal: trueというオプションを付与します。local: trueオプションを付与すれば、Railsのフォームと同様に使用することが出来ます。
- 投稿日:2020-03-15T18:13:58+09:00
【Rails】time_ago_in_words(ヘルパーメソッド)
- 投稿日:2020-03-15T17:52:05+09:00
Rails~パスワードの取り扱いについて~
パスワードの取り扱い
パスワード機能がついているSNSアプリやサービスにはデータベースにパスワードが保存されている。このままだとPCを覗き見されたりハッキングでデータを盗まれた場合そのパスワードを使って他人のアカウントに不正にログインできてしまう。そのため万が一パスワードを覗き見されたりデータを盗まれてもパスワードの内容がわからない状態を目標とする。
gemファイルを使用しパスワードを暗号化する。
gemとは
gemとはRubyやRailsでプログラミングをする際に「よく使う機能」をパッケージ化したもの。「検索機能を作るgem」や「暗号化するためのgem」など様々なgemが存在し、Railsにインストールすることで使用することができる。
今回使用するgem= bcrypt(暗号化)するためのgemgemfiles
Railsにはインストールしたいgemを記述するGemfileというファイルが存在する。
「gem 'rails','5.0.3'」等と記載する。「rails new」コマンドで生成されたgemfileにはすでにいくつかのgemが書かれてインストールされている。数字の部分はgemのバージョンを意味する。バージョンを指定しない場合は最新のgemがインストールされる。bcryptのインストール
Gemfile
gem 'bcrypt'ターミナル
bundle installGemfileへインストールしたいgemを入力→ターミナルにてbundle installと入力する。
bcryptをインストールすると「has_secure_password」というメソッドが使える用になるパスワードを扱うモデルにhas_secure_passwordを追加する。これによりユーザーを保存する際に自動的にパスワードを暗号化してくれる。password_digestカラムを追加する。
上記のメソッドは暗号化したパスワードをpassword_digestカラムに保存する。そのためpasswordカラムは不要となるので削除を行う。
マイグレーションファイルの作成
ターミナル
rails g migration change_users_columns rails db:migrateマイグレーションファイル
def change add_column :users,:password_digest,:string remove_column:users, :password, :string endauthenticateメソッド
has_secure_passwordメソッドを有効にするとauthenticateメソッドを使える様になる。このメソッドは渡された引数を暗号化し、password_digestの値と一致するかどうかを判定してくれる。authenticateメソッドを使用し「送信されたメールアドレスと一致するユーザー」のパスワードと送信されたパスワードが一致するかどうかログイン処理を行う。
controller
def login @user = User.find_by(email:params[:email]) if @usergets && @user.authenticate(params[:password]) && @user.authenticate(params[:password]) end end
- 投稿日:2020-03-15T17:17:41+09:00
Rails 6 Grapeを利用したAPI作成、Swaggerでの確認 続編
メモ続編 もう少しAPIらしくしてみる
前回
JSONのヘッダに こんなのをつけてみるresult: true, message: "取得しました",config/initializers/locale.rbRails.application.config.i18n.default_locale = :jaconfig/locales/api.ymlja: api: success_message: get: 取得しました create: 作成しました update: 更新しました delete: 削除しましたapp/views/api/v1/task_displays/index.jbuilderjson.result true json.message I18n.t('api.success_message.get') json.tasks @tasks do |task| json.(task, :id, :name, :description) endエラー対応
- エラー対応のためエラー用のモジュールを定義
app/api/api/versions/v1/api.rbmodule Versions module V1 class API < Grape::API version 'v1', using: :path format :json formatter :json, Grape::Formatter::Jbuilder prefix :api # エラー対応 rescue_from :all, backtrace: true error_formatter :json, ::MediaSite::ErrorFormatter include ::Versions::V1::TaskDisplays # :nocov: if Rails.env.development? || Rails.env.staging? add_swagger_documentation add_version: true end # :nocov: end end end
- エラー発生時に共通で呼ばれるモジュール
app/api/api/media_site/error_formatter.rbmodule MediaSite module ErrorFormatter # error!メソッド実行時にJSONを出力する # 5つのパラメーターが渡されるのでそれを元にエラー出力 # @param [Object] message メッセージまたはメッセージ+エラーコードのHash # @param [Array] _backtrace backtrace # @param [Hash] _options options # @params [Symbol] _env env # @params [不明] _other # @return [String] JSON文字列 def self.call(message, _backtrace, _options, _env, _other) if message.is_a?(Hash) { result: false }.merge(message).to_json else { result: false, message: message }.to_json end end end endパラメーター追加
- idを指定してタスクを取れるように
app/api/versions/v1/task_displays.rbmodule Versions module V1 module TaskDisplays extend ActiveSupport::Concern included do namespace :tasks do namespace :displays do desc 'タスク一覧を取得する' get '', jbuilder: 'v1/task_displays/index' do @tasks = Task.all end desc "個別タスクの取得" params do requires :id, type: Integer end # http://localhost:3000/api/1/task_displays/{:id} get ':id', jbuilder: 'v1/task_displays/detail' do @task = Task.find(params[:id]) end end end end end end endapp/views/api/v1/task_displays/detail.jbuilderjson.result true json.message I18n.t('api.success_message.get') json.task do json.(@task, :id, :name, :description) end
- 投稿日:2020-03-15T16:51:46+09:00
Rails ~いいね機能を作る~
テーブルの作成
モデルとマイグレーションファイルの作成を準備する。今回作成するテーブルにはuser_idとpost_idの2つのデータをもたせるように処理を行う。
STEP1 マイグレーションファイルを作成
STEP2 マイグレーションファイルの内容をデータベースに反映させるターミナル
マイグレーションファイルの作成 rails g model Like user_id:integer post_id:integer データベースに変更を反映。データ名:integerでデータ作成 rails db:migrateバリデーションの追加
class LIKE<ApplicationRecord validates:user_id,{presence:true} validates:post_id,{presence:true} どちらも存在しないと不完全なデータとなってしまう。 endコンソールにて仮作成
rails console > like=Like.new(user_id:1,post_id:2) IDが「1」のユーザーがIDが「2」の投稿をいいねした、というデータを作成する。 > like.saveいいねした投稿かどうかを表示する
ログインしているユーザーがその投稿にいいね舌データが存在するという条件のために、user_idとpost_idが合致するデータがlikesテーブルに存在するかどうか、find_byを用いてチェックする(find_by には該当する条件が存在しなかったときにNILを返す性質がある。)
view
<% if Like.find_by(user_id:@current_user.id,post_id:@post.id)%> いいね済み <%else%> いいね!していません <%end%>いいねボタンの準備
いままでのコントローラーは「rails g controller」コマンドで自動生成してきた。コマンドを用いるとviewファイルも自動生成されるが今回はそれらのファイルが必要ないため手動でのコントローラ作成を行う。[likes_controller]を作成する。
createアクションを用意
作成したlikesコントローラに新たにLikeデータを作成するためにcreateアクションを用意する。
routes
Rails.applicaion.routes.draw do post"likes/:post_id/create"=>"likes#create" endcontroller
def create @like = Like.new( user_id:@current_user.id, post_id:params[:post_id] ) @like.save redirect_to("/posts/#{params[:post_id]}") endview
<%Like.find_by(...)%> <%= link_to("いいね!済み","/likes/#{@post/id}/destroy",)%> <% else %> <%=link_to("いいね!","/likes/#{@post.id}/create",{method:"post"})%> <% end %>いいね!を削除する
destroyアクションの用意
「いいね!」を取り消す機能を作るために、まずはlikesコントローラにdestroyアクションを作成する。destroyアクション内では、受け取った@current_user.idとparams[:post_id]をもとに削除すべきlikeデータを取得しdestroyメソッドを用いて削除する。
controller
def destroy @like = Like.find_by( user_id:@current_user.id, post_id:params[:post_id] ) @like.destroy redirect_to("/posts/#{params[:post_id]}") endroutes
Rails.application.routes.draw do post"likes/:post_id/destroy"=>"likes#destroy" endcountメソッド
likesテーブルからデータの件数を取得するにはcountメソッドを用いる。
countメソッドは配列の要素数を取得するメソッドですがテーブルのデータ数を取得するためにも利用することができる。likesアクションを用意する
routes
get"users/:id/likes"=>"users#likes"controller
def likes @user = User.find_by(id:params[:id]) @likes = Likes.where(user_id: @user.id) endview
<% @likes.each do |like|%> <% post = Post.find_by(id:like.post_id)%> <% end %>
- 投稿日:2020-03-15T16:36:17+09:00
RailsでRspecの導入
「Everyday Rails - RSpecによるRailsテスト入門」を一通り行ったので少しずつまとめていく。今回は導入について
Gemfile インストール
group :development, :test do gem 'rspec-rails', 'varsion' endrspec-railsのライブラリは、rspec-coreというrspecの核的なもの以外に、独立したgemが含まれており、Rails向けの便利機能などをパッケージとして簡単みインストールできる。
Rails以外のアプリケーションなどの場合は、これらを個別にインストールする必要がある。記入後、bundle install を実行。
テストデータベース
まずはテストデータベースが存在するか確認。存在しなければ作成する必要がある。config/databese.ymlを開き、自分のアプリケーションが、どのデータベースに接続しているか確認。
config/database.yml test: <<: *default database: db/test.sqlite3初期設定を変更して居なければ、このように表示されるはず。
大事なのはdatabase: db/test.sqliteの箇所で、必要であれば自分で変更。そして、次はターミナルからコマンドを実行しデータベースを作成。コマンドは以下の通り。
$ bin/rails db:create:allテストデータベースが無ければ作成され、存在するなら教えてくれる。すでに存在した状態で実行しても教えてくれるだけで、データベースが消去されることはないので心配いらない。
むしろ、確認できる。RSpecのインストール
$ bin/rails generate rspec:installこれによってspecフォルダが作成される。ここに、テストファイルなど追加していく。その他には、spec_hepler.rb,rails_helper.rbなどのカスタマイズ するファイルが作られる。
補足:出力を読みやすくする
そのままでは読みづらいRSpecの出力を文書にして読みやすくする設定。
.rspecファイルに以下を記入。--require spec_helper --format documentationbinstubによる高速化
binstubをインストールするとSpringの恩恵が受けられ、テストスイートが早くなる。
Gemfile group :development do gem 'spring-commands-rspec' endbundle install後に以下のコマンドを実行。
$ bundle exec spring binstub rspecこれにより、binディレクトリにrpecという実行ファイルが作られる。以上で完了。
まとめ
- Gemfile追加
- テストデータベース作成
- Rspecインストール
- binstubによる高速化
参考文献 『Everyday Rails - RSpecによるRailsテスト入門』 著:Aaron Sumner 訳:伊藤淳一・秋元利春・魚振江
- 投稿日:2020-03-15T16:27:46+09:00
Rails 登録した住所をGoogle Mapで表示させる
実現したい事
railsで登録した住所を元にgoogle map上に位置を表示するまでの実装方法をまとめました。
以下のデモのように入力フォームに登録した住所から詳細ページでGoogle Map上にその位置を表示させます。実装までの流れ
実装の流れとしては以下の通りです。
1.Google Mapの機能を使うためにAPIキーを取得する
2.住所(address)、緯度(latitude)、経度(longitude)を登録するためのカラムを用意する
3.住所を登録するためのフォームを作成
4.gemのgeocoderを用意する→これによって住所の情報を元に緯度、経度を割り出してくれます
5.viewにGoogle Mapを表示させるための記述をする
--ここまでで住所を元にGoogle Mapの表示はひとまず出来ます。
以下の6,7は追加で自分がつまずいた事、気をつける事をまとめました。6.住所を登録できてるのにMapにきちんと表示されない時
7.Github等で管理する場合、取得したAPIキーをpushしないために
それでは、流れに沿って進めていきます!
1.Google Mapの機能を使うためにAPIキーを取得する
こちらに関してはこの記事内では省略します。以下の方の記事がとても分かりやすかったです。
手順通りに進めれば問題なくできると思います。
https://qiita.com/tiara/items/4a1c98418917a0e74cbb2.住所、緯度、経度を登録するためのカラムを用意
住所(address)、緯度(latitude)、経度(longitude)のカラムを追加します。
db/migrate/...
マイグレーションファイルへの記述でカラムを追加します。class AddMapInfoToShops < ActiveRecord::Migration[5.2] def change add_column :shops, :address, :string //すでにaddressカラムが登録されている場合は必要ありません add_column :shops, :latitude, :float add_column :shops, :longitude, :float end end緯度、経度はfloat型にします。floatとは浮動小数点数型です。
難しい名前ですがざっくり言えば小数を扱える型です。記述したら
rails db:migrate
してください。3.住所を登録するためのフォームを作成
Mapに表示させたい住所(
:address
)を登録できるようにします。
カラムの:address
に住所が登録できればどんな形での実装でも大丈夫です。
以下は一例です。new.html.erb<%= form_for(@shop) do |f| %> ... <h4>住所</h4> <%= f.text_field :address, placeholder: "住所" %> ... <%= f.submit "登録する" %> <% end %>4.gemのgeocoderを用意する
Map表示する際に、Google Mapでは緯度、経度から位置を取得します。
しかし、今の段階ではaddress
に住所は入っているものの緯度、経度が入力されていません。住所は簡単に調べられても緯度、経度までユーザー側で登録するのは面倒です。そこでgemの
geocoder
を使用します。
geocoder
を使用すると住所の情報を元に緯度、経度を割り出してくれます。Gemfilegem "geocoder"記述したら
bundle install
してください。次に、geocoderを使うために適用するモデルに以下の記述をします。
今回は店舗の位置情報を表示したいので/app/models/shop.rb
に記述しました。shop.rbclass Shop < ApplicationRecord geocoded_by :address after_validation :geocode, if: :address_changed? endこれで
:address
を登録した際にgeocoder
が緯度、経度のカラムにも自動的に値を入れてくれるようになります。
この記述がないとaddress
を登録してもlatitude
(緯度),longitude
(経度)が登録されなくなってしまう(nillになる)ので注意です。5.viewにGoogle Mapを表示させる記述をする
では、実際にMapを表示させたいviewファイルにMapを表示させるための記述をしていきます。
show.html.erb... #shopとなっている部分の記述は自身の実装に合わせて変えてください。 <script type="text/javascript"> function initMap() { #latitude,longitudeから位置を特定 var test ={lat: <%= shop.latitude %>, lng: <%= shop.longitude %>}; var map = new google.maps.Map(document.getElementById('map'), { zoom: 15, center: test }); var transitLayer = new google.maps.TransitLayer(); transitLayer.setMap(map); var contentString = '住所:<%= shop.address %>'; var infowindow = new google.maps.InfoWindow({ content: contentString }); #Map上の指定した位置にピンを挿して表示する var marker = new google.maps.Marker({ position:test, map: map, title: contentString }); marker.addListener('click', function() { infowindow.open(map, marker); }); } </script> #以下の記述の中にあるYOUR_API_KEYには取得したご自身のAPIキーを記述してください <script async defer src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=YOUR_API_KEY&callback=initMap"> </script> #表示するmapのcssです。ご自身でカスタマイズしてください。高さが設定されていないと表示されないことがあります。 <style type="text/css"> #map { height: 200px; width: 70%;} </style> #mapの表示 <div id="map"></div> ...実際の表示画面↓ (上記コードの記述は以下の画面のMap表示部分のみのコードです)
これで登録した住所を元にGoogle Mapへの表示ができるようになりました。
ただし、上の画面で指定した住所は東京都渋谷区とざっくりしています。これが東京都渋谷区◯-◯◯-◯◯◯というように詳細な地点の表示となると上手くいかない場合があります。
自分はこれで詰まりました。どうやらデフォルトの
geocoder
のままではより詳細な住所から経度、緯度を持ってくることが出来ないようです。以下では、この問題を含めた表示がうまくいかない場合に確認すべきことをまとめました。
6.住所を登録できてるのにMapが表示されない時
まず手順5の最後に触れた自分も詰まった東京都渋谷区◯-◯◯-◯◯◯のような細かい位置の指定での検索方法です。
なぜこれで指定できないのかというと、geocoder
の検索精度が原因です。
中にはデフォルト状態のgeocoder
でも細かく住所を指定して検索できる地域もあるのですが、場所によっては検索できなくなります。ここでいう
geocoder
の検索精度とは、住所から緯度、経度の二つを検索する精度という意味です。つまり住所によってデフォルトのgeocoder
では緯度、経度が検索しきれない場所が出てくるということです。ではどうすればいいか、それは最も地図情報が豊富な情報を元に緯度、経度を検索できるようにします。
地図情報を豊富に持ってるものとは何か、それはGoogleです!
そのためgeocoder
でもGoogle Map APIの情報源を使えるように設定すれば解決します。
config
フォルダ内にgeocoder.rb
ファイルを作成します。$ bin/rails g geocoder:configターミナルで上のコマンドを行うことによって
config/initializers/geocoder.rb
ファイルができます。
作成されたファイルの中身を変更して
geocoder
でgoogle mapのAPIを使って緯度、経度を検索できるようにします。geocoder.rbGeocoder.configure( # Geocoding options # timeout: 3, # geocoding service timeout (secs) lookup: :google, # name of geocoding service (symbol) # ip_lookup: :ipinfo_io, # name of IP address geocoding service (symbol) # language: :en, # ISO-639 language code use_https: true, # use HTTPS for lookup requests? (if supported) # http_proxy: nil, # HTTP proxy server (user:pass@host:port) # https_proxy: nil, # HTTPS proxy server (user:pass@host:port) #YOUR_API_KEYにはご自身のAPIキーを記述してください。 api_key: YOUR_API_KEY, # API key for geocoding service # cache: nil, # cache object (must respond to #[], #[]=, and #del) # cache_prefix: 'geocoder:', # prefix (string) to use for all cache keys # Exceptions that should not be rescued by default # (if you want to implement custom error handling); # supports SocketError and Timeout::Error # always_raise: [], # Calculation options # units: :mi, # :km for kilometers or :mi for miles # distances: :linear # :spherical or :linear )これでGoogle Map APIを用いて
geocoder
の精度をあげることができます。より詳細な場所の指定ができるようになりました。それでも上手くいかない場合に確認すること↓
・必要なカラムがきちんと追加できているか?
→スペルミス、緯度、経度がfloat型になっていない、rails db:migrate
していない等を確認。・
geocoder
はきちんと機能しているか?
→手順の4番をきちんと実行できているか、bundle install
しているか?・そもそも
address
に値が入力、取得できているか?
→コントローラーで:address
カラムに値を入力、取得はきちんとできているか?、スペルミス等がないか?7.Github等でのAPIキーの管理
地図表示の実装が上手くいった。よしとりあえずここまでGithubにpushしよう!と考える時に、気をつけなければならないのが取得したAPIキーの管理です。
上手く地図表示できたそのコードをそのままpushしてしまうとコード内に記述した自分のAPIキーまでpushしてしまい、悪用されてしまうかもしれません。その為、キーを管理するために一工夫が必要となります。
その方法はgemの
dotenv-rails
を用いてGithubにpushするコマンドを打ってもpushされないファイルを作り、その中でAPIキーを管理するようにします。まずは、gem
dotenv-rails
を導入します。Gemfilegem "dotenv-rails"記述したら忘れず
bundle install
してください。次にpushされないファイルを作成します。
実装したいアプリケーションの直下に.env
ファイルを作成します。app
等と同じステージに作成してください。
作成したら
.env
ファイルに隠したいAPIキーの中身を記述します。.env#YOUR_API_KEYには自身の取得したAPIキーを入力してください GOOGLE_MAP_API_KEY=YOUR_API_KEYこの作成した
.env
ファイルをGithubにpushした時、pushされないファイルとして扱うようにします。
そのためにgitignore
というファイルの中に記述をしていきます。
gitignore
ファイルの最後の行に以下の記述をしてください。
これでpushした時に.env
ファイルは文字通り無視(ignore)されてpushされるようになります。.gitignore# Ignore master key for decrypting credentials and more. /.envこれでpushしてもAPIキーはpushせずに済むようになりました。
最後に今までAPIキーを直接書き込んでいた全てのファイルでAPIキーの記述を
.env
ファイル内で定義したGOOGLE_MAP_API_KEY
に書き換えて完了です。例:この記事の
show.html.erb
、geocoder.rb
ファイルそれぞれ書き換えた場合↓show.html.erb#以下のコードの YOUR_API_KEY → <%= ENV['GOOGLE_MAP_API_KEY'] %> に変更されています。 #以下の記述の中にあるYOUR_API_KEYには取得したご自身のAPIキーを入力してください <script async defer src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=<%= ENV['GOOGLE_MAP_API_KEY'] %>&callback=initMap"> </script>geocoder.rbGeocoder.configure( ... api_key: ENV['GOOGLE_MAP_API_KEY'], ... )余談:APIキーを記述したファイルをうっかりGithubにpushしてしまったら …
実は自分はこの
.env
ファイルでキーを管理する前にAPIキーを記述したファイルをすっかり忘れてGithubへpushしてしまいました。
その後、すぐにgoogleからメールが届きました。さらにGithubからもメールが。内容はどちらも同じで「あなたのgoogle map APIキーがGithub等で公開されてしまっているよ」というものでした。どうやらAPIキーのような情報を察知するプログラムが企業、個人レベルであちこち働いているようです。
こうなった場合、焦らず対応しましょう。以下の2つを行えば大丈夫です。
1.Googleから届いたメールが対応策をいくつか示してくれるので、それに従います。
自分はこの対応策にしたがって公開してしまったAPIキーを破棄し、新しいAPIキーを発行するという方法を取りました。2.上の1番の対応だけでも解決しますが念のためGithub上のAPIキーを上げてしまったコミットを取り消します。
Githubへpushしたコミットの取り消し方は検索すれば様々出てくるのでここでは割愛します。参考にさせていただいた記事
Google Map API 登録、キーの取得↓
https://qiita.com/tiara/items/4a1c98418917a0e74cbbmapを表示するためのviewの記述↓
https://qiita.com/enzen/items/9a919a75ebf0a34e7b91geocoderの導入、準備↓
https://remonote.jp/rails-googlemapgeocoder 緯度、経度の位置を詳細に検索するために↓
https://qiita.com/roark/items/2fedc1ebac763e72d70bAPIキーの管理、Githubにキーをあげずに管理する↓
https://qiita.com/ryosuketter/items/ceb592dc6b23a20e51b5今回、上記の記事を参考に実装しました。
本当に助かりました。ありがとうございました。まだまだ理解の足りない部分もある(特にviewへの記述方法)ので何かありましたらご指摘いただけますと幸いです。
ここまでお読みいただきありがとうございます。少しでも参考になれたら嬉しいです。
- 投稿日:2020-03-15T15:59:37+09:00
初心者向け、メソッドの用語まとめ(Ruby)
はじめに
- 本記事はRubyでプログラミングを初めてメソッドでいろんな用語が出てきて混乱しているという方が調べる際のヒントになればいいなと思い書きました。
- まずい表現や間違ったことが書いてある場合はコメント等で教えていただけると幸いです。
メソッドってなんだ?
引数、仮引数ってなんだ?
戻り値ってなんだ?、、、
となった人向けに書きました。私はなりました。
メソッドがプログラミングを始めて、初めのつまづきポイントなのではないかなと思っています。
メソッドのおさらい
まずメソッドのおさらいです。
実際のコードを見てみましょう。
例として、税込価格を計算して返してくれるメソッドを書いてみました。####################メソッドを定義############################ #税込価格を計算するメソッドを定義 def calculate_tax_included_price(price) #税込価格 tax_included_priceを変数定義 #税込価格の計算結果を代入 tax_included_price = price * 1.1 #returnで戻り値を返す(省略可能) return tax_included_price end ####################メソッドを呼び出す###################### #支払い額 paymentを変数定義 #calculate_tax_included_price 引数:500 でメソッドを呼び出す。 payment = calculate_tax_included_price(500) #putsメソッドでpaymentをターミナルに出力 puts payment ####################出力結果(コンソール)#################### 550.0
- def ~ end のかたまりがメソッドの定義です。 defは定義 definitionの略
- メソッドは呼び出す場所より上で定義する。(Rubyでは上から読み込んでいくため、メソッドは使用される前に定義する必要があるため。)
- returnの一文は省略が可能
メソッドの用語
メソッドに入ってから用語が増えるので、用語を覚えるまでは混乱することが多いと思います。
ここで復習しましょう。・ メソッド、関数
メソッドと関数という言葉がほとんど同じ意味なのに2通りの呼び方があることが混乱の一因だと思います。
メソッドと関数は厳密には違う意味をもつらしいのですが区別しなくても、あまり困ることはないかなと思います。
Rubyではメソッドの概念しか登場しないそうなのでRubyでは全部メソッドと呼んでおけばOKだと思います。・ メソッドの定義
def ~ endの中に処理を書いて、呼び出して使えるようにすることです。
・ メソッドを呼び出す、使う
メソッド名を記述する、メソッドに仮引数が定義されている場合は引数を渡すとメソッド内の処理が実行されます。
・ (実)引数、仮引数
どっちがどっちかわからなくなる、引数と仮引数ですがここで復習しましょう。
メソッドを呼び出すところで、渡すのが(実)引数
メソッドを定義したときに書いているのが仮引数です。ちなみに、実引数と仮引数の変数名違う名前でもOKです。ここは混乱するポイントだと思いますが、
実引数が仮引数に改名されると考えるのではなく、
仮引数はメソッドが呼ばれたときに新しく定義される変数で、実引数の値が仮引数にコピーして格納されるとイメージしましょう。・ 戻り値、返り値
これも2通りの呼び方がある戻り値、返り値ですが、全く同じものです。好きな方で呼びましょう
returnの後ろに書いたものが戻り値、返り値として呼び出し元に返されます。
calculate_tax_included_price(500)が550.0という値が格納された変数tax_included_priceに置き換わると考えましょう。Rubyではreturnを省略するとメソッドの最後に記述した式が戻り値として返されます。(今回の例ではreturnを省略しても挙動に変化なし)
・ レシーバ
"こんにちは".size #=> 5メソッドの実行主体のこと、「 . 」の左側
なぜレシーバという名前かというと、、、
オブジェクト指向では処理をメッセージのやり取りによって行うという考え方があり、メソッドの実行側がこのメッセージの受け取り側と捉えることができます。そのためレシーバと呼ばれます。
だそうです。・ (カーネル)メソッド、組み込み関数
標準で組み込まれているメソッド・関数
レシーバが不要なメソッド
よく使うputsメソッドなどが含まれます。・ 公開範囲
Railsprivate def set_user @user = User.find(params[:id]) endpublic, protected, privateがある。(protectedはあまり使われない)
・public どこからでもアクセス可能、デフォルト設定でされる。
・private 外部からアクセス不可、隠蔽したいメソッドに利用する。・ 継承
子クラスは継承親のメソッドを受け継ぎます。
########## 親クラス runメソッドを定義################################ class Car #runメソッド def run puts '走る' end end ######### 子クラス(Carクラスを継承している) メソッドは何も定義していない###### class SuperCar < Car end ####################実行部分################################### #SuperCarクラスのインスタンス化を行う。 supercar = SuperCar.new #SuperCarではrunメソッドを定義していないがCarクラスのrunメソッドを使用できる。 supercar.run ####################出力結果(コンソール)########################## 走る・ オーバーライド
子クラスで親クラスで定義されたものと同じ名前を使ってメソッドを新しく定義しなおすことです。
########## 親クラス runメソッドを定義################################ class Car #runメソッド def run puts '走る' end end #########子クラス(Carクラスを継承している) runメソッドをオーバーライドしている#### class SuperCar < Car def run puts '超速く走る' end end ####################実行部分################################### #SuperCarクラスのインスタンス化を行う。 supercar = SuperCar.new #SuperCarのオーバーライドされたrunメソッドを使用する。 supercar.run ####################出力結果(コンソール)########################## 超速く走る・ オーバーロード、多重定義
Rubyにはない概念ですが、オーバーライドと字が似ているので誰しもが最新は混乱すると思います。
どういうものかというとメソッドの名前は同じで引数のデータ型や引数の個数を変えたものを複数定義するというものです 。
同じメソッド名が使えるのでメソッドを使うときに楽になる、ポリモーフィズムが実現できると言ったメリットがあります。
- 投稿日:2020-03-15T15:17:36+09:00
RuboCopのAbcSizeチェックの対応
Rubyの静的コード解析をやってくれるRuboCopをRails開発に導入しました。そこで発生したAbcSizeチェックについての作業メモ。
AbcSizeとは
実装したメソッドに対してRuboCop君が計算してくれるスコア。
以下の3つのカウントが評価対象。
- Assignment : 代入
- Branch : メソッド呼び出し
- Condition : 条件
デフォルトで15を超えると警告が出て、もっとスコア下げてねと言われる。
警告が出たメソッド
ユーザーのサインイン機能を担う、こちらの
SessionsController.create
。sessions_controller.rbdef create @user = User.find_by(email: params[:session][:email].downcase) if @user&.authenticate(params[:session][:password]) # ユーザーログイン後にユーザ情報のページにリダイレクトする log_in @user params[:session][:remember_me] == '1' ? remember(@user) : forget(@user) recdirect_back_or @user else # エラーメッセージを作成する flash.now[:danger] = 'Invalid email/password combination' render 'new' end end出た警告はこちら。
Assignment Branch Condition size for create is too high. <2, 19, 4> 19.52/15",
どうやらスコアは19.52らしく、評価項目毎だとこうなってる。
- Assignment => 2
- Branch => 19
- Condition => 4
対策
...とはいえ現状可読性は問題ない(主観)し、メソッド抽出が必要なほどロジックは複雑ではないので、AbcSizeチェックの基準値を調整しておく。
.rubocop.ymlMetrics/AbcSize: Max: 20おわりに
デフォルトの15を下回れるような解決方法があれば後ほど更新したい。
ちなみにRuboCop自体の
.rubocop_todo.yml
では基準値が18になっているという...
- 投稿日:2020-03-15T14:41:14+09:00
ransackで検索機能実装 全然簡単じゃなかった。。。
理解をしていないことを始めようという時に
これは簡単なはずだーと決めつけて初めてしまうのはよくないですね。はい詳しくはニャンコ先生のページが非常に参考になります
Ransackで簡単に検索フォームを作る73のレシピ - 猫Rails完成図
1Gemfileのインストール
Gemfilegem 'ransack'
bundle install
を忘れずに。2コントローラーへの記述
ざっくりいうと2パターンあります
①他のページにも検索画面が出る場合(例えばヘッダーに検索が入ってて他のページにリンクしてもヘッダーはそのまま残るとかのケース)
この時はapplication_controller
に記述しましょう
②他のページには影響ないよーって時は関係のあるcontrollerに書いておけば動きます。
自身は①のパターンだったのでapplication_controllere
に記述しました。
最初は2に記載してNo Ransack::Search object was provided to search_form_for!
というエラーに苦しめられました。application_controllerebefore_action :set_search #中省略 def set_search #以下は検索に使うときの記述(変数やモデル名は変更してもいいですが #".ransack(params[:q])"はそのままで使用します。) @search = Item.ransack(params[:q]) #以下は検索したものを表示する時に使う記述(一番シンプルで基本の形です) @items = @search.result end3検索フォームを記述
書き方はraansack用があるので、form_forやform_tag,form_withでは使えないようです。
検索バー入れたいページのhaml#ここの@searchは上記controllerで指定した検索用の変数を入れてください = search_form_for@search,url: 行きたいページの_path do |f| = f.search_field :name_cont,class:'search',placeholder: '何かお探しですか?' = button_tag type:'submit',class:'btn' do = image_tag"search-solid.svg",class: 'btn'今回はsubmitを画像に置き換えたかったのでこの書き方をしていますが
普通にf.submitでOKです。= f.search_field :以降の
name_cont
はどのカラムの検索条件かを指定しています。
今回自身は
controllerの@searchでItemテーブルを指定していたのでItemテーブルにあるname
カラムのcont
=一部一致
になったものを検索してurlで指定したページへ持っていくよう実装しています。4リンク先の表示用ページ
検索結果を表示したいページのhaml.search .search-container .search-left = render #ここには詳細検索用のページを入れ込んでいますが今回は省きます .search-right %section.items-box-container -if @search.present? #@searchで検索した文字を持っってきます %h2.search-result-head - @items.each do |item| #表示をする時はcontrollerで指定した変数を使います = item.name %span.search-result-head-text の検索結果 .search-result-number ="1-#{@items.count}件表示" -else %h2.search-result-nil 検索結果 .search-result-number ="1-#{@items.count}件表示" .items-box-content = render #検索結果の一覧を表示するページ(今回は文字ではなく複雑に組んである画像とかだったのでrenderしました)検索結果一覧表示画面
一覧ページのhaml.category-items .category-items__item - @items.each do |item| #今回は複数画像表示しなければ行かなかったのでeachを使用しています .category-items__item__link = link_to item_path(item.id) do %figure.category-items__item__figure .category-thumbnail .category-itemsprice{aria: "", label: ""} = "¥#{item.price}" = image_tag "#{item.images[0].src}", alt: 'category-item',class: 'category-image' %figcaption.category-items = item.name #以下は売り切れた商品の時に表示が出るようにしているだけなので記載なくても大丈夫です。 -if item.buyer_id.present? .items-box_photo__sold .items-box_photo__sold__inner SOLD今回、scssの記述は省くのですが1点、上記のeach文を使うと全ての画像が同じサイズで表示されてしまう欠点がございます。
scssで解決方法
2種類くらいあり
直下セレクタ
というものもあるようですが今回は
:nth-chil
というのを使用しました。何か(今回は画像だったのでimg)の何番目のものに対してのみscssを当てるというものです。終わりに
急いでぎゅっと作成したのでまだまだ理解が浅いのですがransackですが、コード自体の記述は少なくて済みますし、煩雑にならずいい面があります!
が、理解が足りないとドツボにはまるgemでした。。。
商品詳細に関してはまだまだ実装部分に研究の余地がございますので、もう少し形になるようでしたら記事にしたいと思います。最後まで読んでいただきありがとうございます。
初学者ですので不備やアドバイスなど頂けると幸いです。
- 投稿日:2020-03-15T14:28:27+09:00
factory_botでモデルのenum別のtraitをまとめて書く
はじめに
enumのtraitをひとつずつ書いていたところ、factory_botでモデルのenum別のtraitを一発で書く小ネタを見つけました。
今回、この記事とは異なる書き方でtraitを書く方法が分かったので、まとめていきます。
コード
モデル
例えば、UserがSpotifyのプランごとのステータスを持っていたとします。
app/models/user.rbclass User < ApplicationRecord validates :name, presence: true enum :plan { free: 0, individual: 1, family: 2, student: 3 } endこの
plan
それぞれにtraitを作ってもいいのですが、enumの数に比例して行数が増えます。
さらに、Spotifyにプランが追加されたときに、設定を忘れる恐れがあります。ファクトリ
上記のモデルのtraitの行数を減らすために、このように書くことができます。
spec/factories/user.rbFactoryBot.define do factory :user do name { "hoge" } User.plans.keys.each do |plan| trait :"#{plan}" do status { plan } end end end end【ポイント】
enumにつけた名前を複数形にする
この例では、plans
と複数形に直していますenumの名前(複数形)のkeyを取り出す
コンソールで表示すると以下のとおりになります。
keys
で取り出すと、enumのkeyとしての名前でtraitを作ることができます![1] pry(main)> Tag.statuses => {"free"=>0, "individual"=>1, "family"=>2, "student"=>3} [2] pry(main)> Tag.statuses.keys => ["free", "individual", "family", "student"] [3] pry(main)> Tag.statuses.values => [0, 1, 2, 3]
- 投稿日:2020-03-15T13:48:44+09:00
FullcalendarをRailsに導入
Fullcalendarについて
カレンダー機能付きのアプリを作りたく調べてみました。
こんな感じのが出来ます。
FullCalendarはjQueryのプラグインでrailsに導入する際は、gemで導入しました。
今回はScaffoldを使って簡単なEventモデルを作り、カレンダーに反映出来るところまでやります。では、
1.Gemの追加
Gemfile.gem 'jquery-rails', '4.3.3' gem 'fullcalendar-rails' gem 'momentjs-rails'そして、
bundle install
2.application.jsに記述
app/assets/javascript/application.js//= require jquery //= require moment //= require fullcalendar $(function () { // 画面遷移を検知 $(document).on('turbolinks:load', function () { // lengthを呼び出すことで、#calendarが存在していた場合はtrueの処理がされ、無い場合はnillを返す if ($('#calendar').length) { function eventCalendar() { return $('#calendar').fullCalendar({ }); }; function clearCalendar() { $('#calendar').html(''); }; $(document).on('turbolinks:load', function () { eventCalendar(); }); $(document).on('turbolinks:before-cache', clearCalendar); $('#calendar').fullCalendar({ events: '/events.json' }); } }); });一つ目の関数ではFullCalendarの設定を読み込み、二つ目の関数ではFullCalendarを削除します。
その下に呼び出すコードを2行書き最後にイベントを表示させるためのコードを書きます。3.application.cssに記述
app/assets/stylesheets/application.css... *= require_tree . *= require_self *= require fullcalendar */これで、あとはviewのerbファイルに
<div id="calendar"></div>
と書き込むとカレンダーが表示されます。4.イベントを表示する
Fullcalendarにイベントの情報を表示するには、JSONファイルを使ってあげます。
イベントにはタイトル、説明、開始日、終了日が必要です。今回はscaffoldで作りました。
rails generate scaffold Event title:string description:text start_date:datetime end_date:datetime
そして、
rails db:migrate
JSONを渡すために、Railsのjbuilderというものを使っていきます。scaffoldによってjson.jbuilderファイルが自動で作られています。
以下のコードを追加app\views\events\index.json.jbuilderjson.array!(@events) do |event| json.extract! event, :id, :title, :description json.start event.start_date json.end event.end_date json.url event_url(event, format: :html) endこうすることで、{"id":"1", "title":"タイトル", "description":"説明", "start":"日付1", "end":"日付2", "url":"some_address"}のようにjsonファイルが作られ、カレンダーが読み込めるデータになります。
url項目があることで、カレンダーの予定にurlが埋め込まれ、クリックすると予定の詳細に飛ぶことができます。現状、ルーティングはこのようになっています。
routes.rbRails.application.routes.draw do resources :events # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html root 'events#index' end一旦、これで表示は出来るようになりました。
あとは、カレンダーのオプションで色々触ってみて下さい。$('#calendar').fullCalendar({ });の{ }内にオプションを加えることで、カレンダーのタイトルをYYYY年MM月のようにしたり、レイアウト、時間の表示を設定できます。
公式ドキュメントには色々な使い方、機能が載っていますので確認してみて下さい。
FullCalendarの使い方
- 投稿日:2020-03-15T13:48:44+09:00
FullCalendarをRailsに導入
FullCalendarについて
カレンダー機能付きのアプリを作りたく調べてみました。
こんな感じのが出来ます。
FullCalendarはjQueryのプラグインでrailsに導入する際は、gemで導入しました。
今回はScaffoldを使って簡単なEventモデルを作り、カレンダーに反映出来るところまでやります。では、
1.Gemの追加
Gemfile.gem 'jquery-rails', '4.3.3' gem 'fullcalendar-rails' gem 'momentjs-rails'そして、
bundle install
2.application.jsに記述
app/assets/javascript/application.js//= require jquery //= require moment //= require fullcalendar $(function () { // 画面遷移を検知 $(document).on('turbolinks:load', function () { // lengthを呼び出すことで、#calendarが存在していた場合はtrueの処理がされ、無い場合はnillを返す if ($('#calendar').length) { function eventCalendar() { return $('#calendar').fullCalendar({ }); }; function clearCalendar() { $('#calendar').html(''); }; $(document).on('turbolinks:load', function () { eventCalendar(); }); $(document).on('turbolinks:before-cache', clearCalendar); $('#calendar').fullCalendar({ events: '/events.json' }); } }); });一つ目の関数ではFullCalendarの設定を読み込み、二つ目の関数ではFullCalendarを削除します。
その下に呼び出すコードを2行書き最後にイベントを表示させるためのコードを書きます。
その他、説明は少しはしょります。3.application.cssに記述
app/assets/stylesheets/application.css... *= require_tree . *= require_self *= require fullcalendar */これで、あとはviewのerbファイルに
<div id="calendar"></div>
と書き込むとカレンダーが表示されます。4.イベントを表示する
Fullcalendarにイベントの情報を表示するには、JSONファイルを使ってあげます。
イベントにはタイトル、説明、開始日、終了日が必要です。今回はscaffoldで作りました。
rails generate scaffold Event title:string description:text start_date:datetime end_date:datetime
そして、
rails db:migrate
JSONを渡すために、Railsのjbuilderというものを使っていきます。scaffoldによってjson.jbuilderファイルが自動で作られています。
以下のコードを追加app\views\events\index.json.jbuilderjson.array!(@events) do |event| json.extract! event, :id, :title, :description json.start event.start_date json.end event.end_date json.url event_url(event, format: :html) endこうすることで、{"id":"1", "title":"タイトル", "description":"説明", "start":"日付1", "end":"日付2", "url":"some_address"}のようにjsonファイルが作られ、カレンダーが読み込めるデータになります。
url項目があることで、カレンダーの予定にurlが埋め込まれ、クリックすると予定の詳細に飛ぶことができます。現状、ルーティングはこのようになっています。
routes.rbRails.application.routes.draw do resources :events # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html root 'events#index' end一旦、これで表示は出来るようになりました。
あとは、カレンダーのオプションで色々触ってみて下さい。$('#calendar').fullCalendar({ });の{ }内にオプションを加えることで、カレンダーのタイトルをYYYY年MM月のようにしたり、レイアウト、時間の表示を設定できます。
公式ドキュメントには色々な使い方、機能が載っていますので確認してみて下さい。
FullCalendarの使い方
- 投稿日:2020-03-15T12:13:43+09:00
unicorn_rails コマンドまとめ
はじめに
EC2にデプロイする際にunicornに関するコマンドを忘れがちなので、メモとしてこの記事を残しておきます。
unicornの起動
terminal$ unicorn_rails -c /var/www/rails/myapp(自分のアプリ名)/config/unicorn.conf.rb -D -E productionunicornの起動確認
terminal$ ps -ef | grep unicorn | grep -v grep上のコマンドを打って、次のようなログが出てきたら、無事起動できています。
takuya 2460 1 0 3月11 ? 00:00:04 unicorn_rails master -c /var/www/rails/myapp/config/unicorn.conf.rb -D -E production takuya 2465 2460 0 3月11 ? 00:00:05 unicorn_rails worker[0] -c /var/www/rails/myapp/config/unicorn.conf.rb -D -E production takuya 2467 2460 0 3月11 ? 00:00:04 unicorn_rails worker[1] -c /var/www/rails/myapp/config/unicorn.conf.rb -D -E productionunicornの停止
上のコマンドで出力されたログに書いてある数字を以下のように入力すると、unicornが停止します。
terminal$ kill 2460以上
- 投稿日:2020-03-15T11:58:20+09:00
AWS EC2デプロイで困ったときに見たところ
- 投稿日:2020-03-15T09:17:33+09:00
駆け出す前に一歩立ち止まって、ユーザー登録機能におけるDB設計を考えよう!
記事内容
メルカリクローンサイトのユーザー登録機能を実装しました。
前回の記事でDB設計について記述しましたが、ユーザー機能の実装中にDB設計との変更点がありましたので、変更内容と変更理由を記載します。
駆け出しエンジニアは「とりあえず進みながら修正していこう!」とういうマインド」で開発がスタートすると思います。自分たちのチームもそうでした。
読んでくれた方がBD設計で手戻りがないようになればと思います。DB設計の修正が結構やっかいだったので...
前回の記事はこちらこの記事DB設計をした時点で考えていたこと。
ユーザー・アイテムモデルに大量のカラムができるから、括れるものは別テーブルを作成し1体1の関係でリレーションする。(テーブルにカラムが多すぎると不便になりそう・・・)
DB設計変更箇所
今回変更した箇所は以下の通りです。
変更しなければ実装できないというわけではないと思いますが、実装難易度(検索してヒットするか)、分かりやすさを考慮しました。
ユーザー/プロフィールテーブルのカラムを変更
生年月日のカラムを3分割(年・月・日)から1つに変更
ユーザー/プロフィールテーブルのカラムを変更
実際に修正したのはプロフィールテーブルに入れる予定だった、氏名、氏名(カナ)、生年月日をユーザーテーブルに移動。
今回ユーザー登録機能をウィザード形式で作成しました。(ウィザード形式について)
ウィザード形式ではページを遷移する時sessionを用いて保存しますが、一つのページで登録するカラムが2つのテーブに別れていると登録できなかったため、1ページ目で入力したいカラムは1つのテーブル(users)にまとめました。
複数のテーブルへ保存する方法はあるようで、チャレンジしてみたのですが、deviseを使用するとできない??実装方法分かる方いましたら教えてください
([Rails]ウィザードフォーム実装でfields_forを使って複数のテーブルに保存する)変更前
users table
Column Type Options nickname string null:false password string null:false string null:false, unique: true, index:true
profiles table
Column Type Options introduction text avatar string user references null: false, foreign_key: true first_name string null:false family_name string null:false first_name_kana string null:false family_name_kana string null:false birth_year date null:false birth_month date null:false birth_day date null:false 変更後
users table
Column Type Options nickname string null:false password string null:false string null:false, unique: true, index:true first_name string null:false family_name string null:false first_name_kana string null:false family_name_kana string null:false birth_year date null:false birth_month date null:false birth_day date null:false
profiles table
Column Type Options introduction text avatar string user references null: false, foreign_key: true 生年月日のカラムを3分割(年・月・日)から1つに変更
続いてはuserモデルに移動した生年月日を登録する際、カラムを3つにわけていたものを1つにまとめたことです。これについては実装内容どうこうの話ではなく、単純に実装が早そうだったからです。
(【Rails】date_selectタグの使い方メモ)【結論】考えることと駆け出すことのバランスを!
走り始める前に「なぜそうするのか」理由を考える
考えた理由を元に走り始める
以上を繰り返して、知識を蓄積することが大切です。修正することも勉強になります!
- 投稿日:2020-03-15T02:03:49+09:00
Rails 6 Grapeを利用したAPI作成、Swaggerでの確認
RoRでのAPI作成メモ
ruby 2.7.0 & Rails 6
Rails 5.x 系の資源を利用する例です。
https://qiita.com/katafuchix/items/8a96e2fa9ddc8bc83545プロジェクト作成
$ mkdir api_sample $ cd api_sample $ bundle initGemfile編集
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem 'rails'インストール
*MySQLを利用する例$ bundle install --path vendor/bundle $ bundle exec rails new . --skip-action-mailer --skip-active-storage --skip-action-cable -d mysqlGemfileに追加
# API gem 'grape' gem 'hashie-forbidden_attributes' gem 'grape-jbuilder' gem 'grape_on_rails_routes' gem 'swagger-ui_rails' gem 'grape-swagger' gem 'grape-swagger-rails'もう一度 bundle install を実行してエラーがないことを確認する
モデルクラス準備、データ投入
$ bin/rails g model Task name:string description:stringdb/seed.rb
seed.rbTask.create(name: 'タスク1', description: 'サンプルタスク1') Task.create(name: 'タスク2', description: 'サンプルタスク2') Task.create(name: 'タスク3', description: 'サンプルタスク3')$ bundle exec rake db:create $ bundle exec rake db:migrate $ bundle exec rake db:seedGrapeによるAPI作成
ルーティング
http://localhost:3000/api/v1/tasks/displays とかでアクセスしたいので各種設定
RailsかGrapeの仕様で「app/api」というディレトクリ以下のパス、ファイル名、クラス名が一致しなければならない。
それまでのRailsで「api/versions/v1」という作りをしている場合は、「api/api/versions/v1」というパスが二重になったフォルダ構成となってしまう。
ここでは以前のシステムを最小限度の手順で最新のRailsで組み直すため、ここはこのままで進めます。config/routes.rbRails.application.routes.draw do mount Versions::V1::Api => '/' mount GrapeSwaggerRails::Engine => '/api/swagger' endAPIディレクトリの作成
プロジェクト直下に api フォルダを作成してそこにAPIのプログラムを配置するために for Grape 以下の記述を追加
config/application.rbrequire_relative 'boot' require 'rails/all' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module ApiSample class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 6.0 # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers # -- all .rb files in that directory are automatically loaded after loading # the framework and any gems in your application. # For Grape config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb') config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')] config.middleware.use(Rack::Config) do |env| env['api.tilt.root'] = Rails.root.join 'app', 'views', 'api' end end end$ mkdir app/api $ mkdir app/api/api $ mkdir app/api/api/versions $ mkdir app/api/api/versions/v1API作成
大元にこういうのを作成してその中でエンドポイント単位のモジュールをincludeするのが管理しやすい
/api/vi/〜のパスにするために内部でこんな風に記述
テンプレートはJbuilderを指定app/api/api/versions/v1/api.rbmodule Versions module V1 class Api < Grape::API version 'v1', using: :path format :json formatter :json, Grape::Formatter::Jbuilder prefix :api include ::Versions::V1::TaskDisplays # :nocov: if Rails.env.development? add_swagger_documentation add_version: true end # :nocov: end end end各APIのコードはこんな感じ。 命名規則に注意。
app/api/api/versions/v1/task_displays.rbmodule Versions module V1 module TaskDisplays extend ActiveSupport::Concern included do namespace :tasks do namespace :displays do desc 'タスク一覧を取得する' get '', jbuilder: 'v1/task_displays/index' do @tasks = Task.all end end end end end end endテンプレードはこんな感じ
app/views/api/v1/task_displays/index.jbuilderjson.tasks @tasks do |task| json.(task, :id, :name, :description) endswagger設定ファイル
config/initializers/grape_swagger_rails.rbunless Rails.env.production? GrapeSwaggerRails.options.app_name = 'Grape API Sample' GrapeSwaggerRails.options.app_url = '/' GrapeSwaggerRails.options.url = 'api/v1/swagger_doc.json' endswagger用にjs, cssをimportするために2行追加
app/assets/config/manifest.js//= link_tree ../images //= link_directory ../stylesheets .css // 下2行追加 //= link grape_swagger_rails/application.css //= link grape_swagger_rails/application.js確認
railsを起動してブラウザで確認
bin/rails sAPI
http://localhost:3000/api/v1/tasks/displays
swagger
http://localhost:3000/api/swagger
あとは ここと同じように・・・
Rails 5.2 Grapeを利用したAPI作成、Swaggerでの確認 続編
- 投稿日:2020-03-15T00:29:34+09:00
【随時更新】Railsでたまに見返したいメモ集
個人的に見返したい資料のメモ書きです。
HTTPメソッド一覧
HTTPメソッド 使用するリクエスト GET ページを表示する操作のみを行う時 POST データを登録する操作をする時 PUT データを変更する操作をする時 DELETE データを削除する操作を行う時 アクション名一覧
アクション名 対応するリクエスト index 一覧表示ページを表示 new 新規投稿ページを表示 create データの投稿 show 個別詳細ページを表示 update データの編集 destroy データの削除 createメソッド
メソッド 用途 all テーブルの全てのデータを取得する find テーブルのレコードの内、ある1つのデータを取得する create テーブルにレコードを追加する 例:
controller.rbdef create Post.create(content: params[:content]) endcontent: テーブルのカラム名
params: paramsとして送られてきたデータ
- 投稿日:2020-03-15T00:27:53+09:00
Railsチュートリアルメモ - 第11章
(メモの目次記事はこちら)[https://qiita.com/yokohama4580/items/dedfd5510080273dc2a0]
(公式Railsチュートリアル第11章へのリンク)[https://railstutorial.jp/chapters/account_activation?version=5.1#cha-account_activation]
サマリ
- メールの送信とアカウント有効化機能の実装
- メタプログラミング
- SendGridを利用した本番環境でのメール送信
ポイント
rails generate mailer
でメーラーの雛形を作成できる
- e.g.
rails generate mailer UserMailer account_activation password_reset
- 生成されるHTML/textメーラーのレイアウトは
app/views/layouts
で定義されている- コントローラー同様、メーラーの中で作成したインスタンス変数はテンプレートの中で使用することができる
- 送信先やメールタイトルなどは
- e.g.
mail to:hoge@sample.com
deliver_now
メソッドを呼び出すことでメールを送信する
- e.g.
UserMailer.account_activation(@user).deliver_now
- 名前付きroot
- 名前付きrootの第一引数は:idになりBase64でエンコードされてURLが生成される
- 第二引数としてハッシュを渡すとURLパラメーターにしてURLを生成してくれる。その際にエスケープも行ってくれる。
- 名前付きrootの引数として指定されたものはコントローラー内で
params[:引数]
でデコードされた状態で取り出すことができる- 以下を行うとメールのプレビューが有効になる
config/environments/development.rb
に以下を追記するtest/mailers/previews/user_mailer_preview.rb
を修正する- プレビューURLは
https://localhost:3000/rails/mailers/user_mailer/
config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :test host = 'localhost:3000' config.action_mailer.default_url_options = { host: host, protocol: 'https' }
assert_match
で正規表現を利用してassertできる
send
メソッドは引数を評価してから実行するので、メタプログラミング(プログラムによるコードの生成)を行う際に利用される
e.g.attribute = "foobar" user.send("#{attribute}_digest") # => user.foobar_digestが実行される
テストの中でのみ
assigns
メソッドを使うと対応するアクション内のインスタンス変数にアクセスできるようになる
- e.g.
user = assigns(:user)
=>@userにアクセス可能になる
heroku addons:create sendgrid:starter
でherokuにsendgridを導入できる感想、詰まった箇所など
config/environments/development.rb
を書き換えた後にサーバーの再起動を行わなかったせいでMissing host to link to! Please provide the :host parameter,...
というエラーが出てしまい少し詰まった。- herokuにSendGridを追加した後、画面からユーザー登録を行うとメールが飛ばずに
We're sorry, but something went wrong.
というエラー画面が表示された。ログを確認したところNet::SMTPAuthenticationError (535 Authentication failed: account disabled
となっていた。- 原因分からず解消しなかったため、herokuの画面からSendGridを「Delete Add-on」した後、再度SendGrid追加しようとしたところ
Error Provisioning User - User status - banned
と表示されてしまった。- herokuアプリ自体を再作成してからsendgridを再追加することで解消した。