20200315のRailsに関する記事は27件です。

多対多

多対多

『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 関連テーブルから先のモデルにアクセスするための(関連モデルから見た)関連名を指定できる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マイクロポストにタグ付けする

はじめに

ご訪問いただきありがとうございます。レベル的には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:string
rails g model tag_relationship micropost:references tag:references 

作成されたマイグレーションファイルを確認します。

db/migrate
class CreateTags < ActiveRecord::Migration[5.1]
  def change
    create_table :tags do |t|
      t.string :name, null: false

      t.timestamps
    end
  end
end

nameを空で保存されるのを防ぐためにnull:falseを追記してます。

db/migrate
class 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:migrate

2モデルの関連付けとバリレーション

各モデルの関連付けは以下のようになります。

app/model/tag.rb
class Tag < ApplicationRecord
  has_many   :tag_relationships, dependent: :destroy
  has_many   :microposts, through: :tag_relationships
  validates :name, uniqueness: true
end
ruby/app/model/tag_relationship.rb
class TagRelationship < ApplicationRecord
  belongs_to :micropost
  belongs_to :tag
end
app/model/micropost.rb
has_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.rb
def 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.rb
def 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.rb
def 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.rb
def 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]

以上でタグ付機能は完成となります。最後までお読みいただきありがとうございました。
アドバイス等いただけるととても喜びます。次は今回作ったタグを利用した検索機能を作ろうと思います。
ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6・楽天API】商品検索機能の実装手順

はじめに

Railsで楽天APIを使用して商品(本)の検索、商品情報の取得・表示をする方法をまとめました。
今回は、本を題名で検索し、ヒットした本の情報を一覧にして下に表示する形です。

バージョン

・Ruby 2.7.0
・Rails 6.0.2.1

実装手順

アプリケーションIDの取得

楽天API情報を使用するには、Rakuten Developersより
「新規アプリ登録」を行いアプリケーションIDを取得する必要があります。
簡単な情報を入力してすぐに取得することができます。
AmazonAPIは申請を行い審査を受ける(落ちることも多いようです)ので、この点が違いますね。

gemをインストール

Ruby SDK(公式)に従って進めます。
Gemfileに下記追記して、bundleでインストール。

Gemfile
gem 'rakuten_web_service'

アプリケーションIDを設定

取得したアプリケーションIDを設定します。
config/initializersフォルダの中に下記ファイルを作成。

rakuten.rb
RakutenWebService.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.rb
class BooksController < ApplicationController
  def search
    if params[:keyword]
      @books = RakutenWebService::Books::Book.search(title: params[:keyword])
    end
  end
end

ルーティングの設定

routes.rb
get '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 で楽天商品ページを取得してみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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ファイルでレイアウトを調整します。

参考

【videoタグ】HTMLで動画を埋め込む方法を徹底まとめ

→ 投稿した動画を表示するにはまた別の方法が必要なので次回ご紹介します!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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 %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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 %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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>]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsアプリをcloneした後にDocker上で動かすまで

GitHubからソースをcloneした後、Dockerを使った開発環境を構築するまでにやることをまとめます。
Dockerfiledocker-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.ymldatabase.ymlの記述を参考に、データベースとTablePlusやSequelProなどのアプリを接続します。

デプロイ周りの環境を構築する

Herokuとの接続や、capistranoを使ったデプロイができるように調整します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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のフォームと同様に使用することが出来ます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】time_ago_in_words(ヘルパーメソッド)

time_ago_in_words

Railsのヘルパーメソッドの一つで、投稿機能で「○分前に投稿」等の表示をさせるために使用されます。

実行例

  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  </span>

このようなコードの場合、投稿時間が5時間前だとPosted about 5 hours ago.と表示されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails~パスワードの取り扱いについて~

パスワードの取り扱い

パスワード機能がついているSNSアプリやサービスにはデータベースにパスワードが保存されている。このままだとPCを覗き見されたりハッキングでデータを盗まれた場合そのパスワードを使って他人のアカウントに不正にログインできてしまう。そのため万が一パスワードを覗き見されたりデータを盗まれてもパスワードの内容がわからない状態を目標とする。

gemファイルを使用しパスワードを暗号化する。

gemとは

gemとはRubyやRailsでプログラミングをする際に「よく使う機能」をパッケージ化したもの。「検索機能を作るgem」や「暗号化するためのgem」など様々なgemが存在し、Railsにインストールすることで使用することができる。
今回使用するgem= bcrypt(暗号化)するためのgem

gemfiles

Railsにはインストールしたいgemを記述するGemfileというファイルが存在する。
「gem 'rails','5.0.3'」等と記載する。「rails new」コマンドで生成されたgemfileにはすでにいくつかのgemが書かれてインストールされている。数字の部分はgemのバージョンを意味する。バージョンを指定しない場合は最新のgemがインストールされる。

bcryptのインストール

Gemfile

gem 'bcrypt'

ターミナル

bundle install

Gemfileへインストールしたい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
end

authenticateメソッド

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 6 Grapeを利用したAPI作成、Swaggerでの確認 続編

メモ続編 もう少しAPIらしくしてみる

前回
JSONのヘッダに こんなのをつけてみる

result: true,
message: "取得しました",
config/initializers/locale.rb
Rails.application.config.i18n.default_locale = :ja
config/locales/api.yml
ja:
  api:
    success_message:
      get: 取得しました
      create: 作成しました
      update: 更新しました
      delete: 削除しました
app/views/api/v1/task_displays/index.jbuilder
json.result true
json.message I18n.t('api.success_message.get')
json.tasks @tasks do |task|
  json.(task, :id, :name, :description)
end

スクリーンショット 2019-01-26 22.13.11.png

エラー対応

  • エラー対応のためエラー用のモジュールを定義
app/api/api/versions/v1/api.rb
module 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.rb
module 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.rb
module 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
end
app/views/api/v1/task_displays/detail.jbuilder
json.result true
json.message I18n.t('api.success_message.get')
json.task do
  json.(@task, :id, :name, :description)
end
  • 結果
    http://localhost:3000/api/v1/tasks/displays/1
    スクリーンショット 2019-01-27 1.08.41.png

  • swaggerにも自動で追加される
    スクリーンショット 2019-01-27 1.10.15.png

  • エラー発生時
    存在しないタスクのidを指定した場合
    「result: false」となり message にエラーメッセージが入る
    スクリーンショット 2019-01-27 10.44.16.png

サンプル

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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"
end

controller

def create
   @like = Like.new(
     user_id:@current_user.id,
     post_id:params[:post_id]
   )
   @like.save
   redirect_to("/posts/#{params[:post_id]}")
end

view

<%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]}")
end

routes

Rails.application.routes.draw do
  post"likes/:post_id/destroy"=>"likes#destroy"
end

countメソッド

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)
end

view

  <% @likes.each do |like|%>
    <% post = Post.find_by(id:like.post_id)%>
  <% end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでRspecの導入

「Everyday Rails - RSpecによるRailsテスト入門」を一通り行ったので少しずつまとめていく。今回は導入について

Gemfile インストール

group :development, :test do
  gem 'rspec-rails', 'varsion'

end

rspec-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 documentation

binstubによる高速化

binstubをインストールするとSpringの恩恵が受けられ、テストスイートが早くなる。

Gemfile

group :development do
  gem 'spring-commands-rspec'
end 

bundle install後に以下のコマンドを実行。

$ bundle exec spring binstub rspec

これにより、binディレクトリにrpecという実行ファイルが作られる。以上で完了。

まとめ

  • Gemfile追加
  • テストデータベース作成
  • Rspecインストール
  • binstubによる高速化

参考文献 『Everyday Rails - RSpecによるRailsテスト入門』 著:Aaron Sumner  訳:伊藤淳一・秋元利春・魚振江

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 登録した住所をGoogle Mapで表示させる

実現したい事

railsで登録した住所を元にgoogle map上に位置を表示するまでの実装方法をまとめました。
以下のデモのように入力フォームに登録した住所から詳細ページでGoogle Map上にその位置を表示させます。

ezgif.com-video-to-gif.gif

実装までの流れ

実装の流れとしては以下の通りです。

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/4a1c98418917a0e74cbb

2.住所、緯度、経度を登録するためのカラムを用意

住所(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 %>

スクリーンショット 2020-03-15 3.20.15.png

4.gemのgeocoderを用意する

Map表示する際に、Google Mapでは緯度、経度から位置を取得します。
しかし、今の段階ではaddressに住所は入っているものの緯度、経度が入力されていません。

住所は簡単に調べられても緯度、経度までユーザー側で登録するのは面倒です。そこでgemのgeocoderを使用します。
geocoderを使用すると住所の情報を元に緯度、経度を割り出してくれます。

Gemfile
  gem "geocoder"

記述したらbundle installしてください。

次に、geocoderを使うために適用するモデルに以下の記述をします。
今回は店舗の位置情報を表示したいので/app/models/shop.rbに記述しました。

shop.rb
class 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表示部分のみのコードです)
スクリーンショット 2020-03-14 20.09.36.png

これで登録した住所を元に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ファイルができます。
スクリーンショット 2020-03-15 4.07.15.png

作成されたファイルの中身を変更してgeocoderでgoogle mapのAPIを使って緯度、経度を検索できるようにします。

geocoder.rb
Geocoder.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を導入します。

Gemfile
gem "dotenv-rails"

記述したら忘れずbundle installしてください。

次にpushされないファイルを作成します。
実装したいアプリケーションの直下に.envファイルを作成します。app等と同じステージに作成してください。
スクリーンショット 2020-03-14 23.14.48.png

作成したら.envファイルに隠したいAPIキーの中身を記述します。

.env
#YOUR_API_KEYには自身の取得したAPIキーを入力してください
GOOGLE_MAP_API_KEY=YOUR_API_KEY

この作成した.envファイルをGithubにpushした時、pushされないファイルとして扱うようにします。
そのためにgitignoreというファイルの中に記述をしていきます。
スクリーンショット 2020-03-14 23.23.41.png

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.erbgeocoder.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.rb
Geocoder.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/4a1c98418917a0e74cbb

mapを表示するためのviewの記述↓
https://qiita.com/enzen/items/9a919a75ebf0a34e7b91

geocoderの導入、準備↓
https://remonote.jp/rails-googlemap

geocoder 緯度、経度の位置を詳細に検索するために↓
https://qiita.com/roark/items/2fedc1ebac763e72d70b

APIキーの管理、Githubにキーをあげずに管理する↓
https://qiita.com/ryosuketter/items/ceb592dc6b23a20e51b5

今回、上記の記事を参考に実装しました。
本当に助かりました。ありがとうございました。

まだまだ理解の足りない部分もある(特にviewへの記述方法)ので何かありましたらご指摘いただけますと幸いです。

ここまでお読みいただきありがとうございます。少しでも参考になれたら嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者向け、メソッドの用語まとめ(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の中に処理を書いて、呼び出して使えるようにすることです。

・ メソッドを呼び出す、使う

image.png

メソッド名を記述する、メソッドに仮引数が定義されている場合は引数を渡すとメソッド内の処理が実行されます。

・ (実)引数、仮引数

どっちがどっちかわからなくなる、引数と仮引数ですがここで復習しましょう。
image.png
メソッドを呼び出すところで、渡すのが(実)引数
メソッドを定義したときに書いているのが仮引数です。

ちなみに、実引数と仮引数の変数名違う名前でもOKです。ここは混乱するポイントだと思いますが、
実引数が仮引数に改名されると考えるのではなく、
仮引数はメソッドが呼ばれたときに新しく定義される変数で、実引数の値が仮引数にコピーして格納されるとイメージしましょう。

・ 戻り値、返り値

これも2通りの呼び方がある戻り値、返り値ですが、全く同じものです。好きな方で呼びましょう
image.png

returnの後ろに書いたものが戻り値、返り値として呼び出し元に返されます。

image.png
calculate_tax_included_price(500)が550.0という値が格納された変数tax_included_priceに置き換わると考えましょう。

Rubyではreturnを省略するとメソッドの最後に記述した式が戻り値として返されます。(今回の例ではreturnを省略しても挙動に変化なし)

・ レシーバ

"こんにちは".size  #=> 5

メソッドの実行主体のこと、「 . 」の左側

なぜレシーバという名前かというと、、、
オブジェクト指向では処理をメッセージのやり取りによって行うという考え方があり、メソッドの実行側がこのメッセージの受け取り側と捉えることができます。そのためレシーバと呼ばれます。
だそうです。

・ (カーネル)メソッド、組み込み関数

標準で組み込まれているメソッド・関数
レシーバが不要なメソッド
よく使うputsメソッドなどが含まれます。

・ 公開範囲

Rails
 private 
  def set_user
    @user = User.find(params[:id])
  end

public, 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にはない概念ですが、オーバーライドと字が似ているので誰しもが最新は混乱すると思います。
どういうものかというとメソッドの名前は同じで引数のデータ型や引数の個数を変えたものを複数定義するというものです 。
同じメソッド名が使えるのでメソッドを使うときに楽になる、ポリモーフィズムが実現できると言ったメリットがあります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RuboCopのAbcSizeチェックの対応

Rubyの静的コード解析をやってくれるRuboCopをRails開発に導入しました。そこで発生したAbcSizeチェックについての作業メモ。

AbcSizeとは

実装したメソッドに対してRuboCop君が計算してくれるスコア。
以下の3つのカウントが評価対象。

  • Assignment : 代入
  • Branch : メソッド呼び出し
  • Condition : 条件

デフォルトで15を超えると警告が出て、もっとスコア下げてねと言われる。

警告が出たメソッド

ユーザーのサインイン機能を担う、こちらのSessionsController.create

sessions_controller.rb
def 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.yml
Metrics/AbcSize:
  Max: 20

おわりに

デフォルトの15を下回れるような解決方法があれば後ほど更新したい。

ちなみにRuboCop自体の.rubocop_todo.ymlでは基準値が18になっているという...

https://github.com/rubocop-hq/rubocop/blob/b10f7b63b2d1b4d71cc80661e30db569e7079237/.rubocop_todo.yml#L9-L11

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ransackで検索機能実装 全然簡単じゃなかった。。。

理解をしていないことを始めようという時に
これは簡単なはずだーと決めつけて初めてしまうのはよくないですね。はい

詳しくはニャンコ先生のページが非常に参考になります
Ransackで簡単に検索フォームを作る73のレシピ - 猫Rails

完成図

image.png

1Gemfileのインストール

Gemfile
gem 'ransack'

bundle installを忘れずに。

2コントローラーへの記述

ざっくりいうと2パターンあります
①他のページにも検索画面が出る場合(例えばヘッダーに検索が入ってて他のページにリンクしてもヘッダーはそのまま残るとかのケース)
この時はapplication_controllerに記述しましょう
②他のページには影響ないよーって時は関係のあるcontrollerに書いておけば動きます。
自身は①のパターンだったのでapplication_controllereに記述しました。
最初は2に記載してNo Ransack::Search object was provided to search_form_for!というエラーに苦しめられました。

application_controllere
  before_action :set_search
#中省略
  def set_search
#以下は検索に使うときの記述(変数やモデル名は変更してもいいですが
#".ransack(params[:q])"はそのままで使用します。)
    @search = Item.ransack(params[:q])

#以下は検索したものを表示する時に使う記述(一番シンプルで基本の形です)
    @items = @search.result
  end

3検索フォームを記述

書き方は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でした。。。
商品詳細に関してはまだまだ実装部分に研究の余地がございますので、もう少し形になるようでしたら記事にしたいと思います。

最後まで読んでいただきありがとうございます。
初学者ですので不備やアドバイスなど頂けると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

factory_botでモデルのenum別のtraitをまとめて書く

はじめに

enumのtraitをひとつずつ書いていたところ、factory_botでモデルのenum別のtraitを一発で書く小ネタを見つけました。

今回、この記事とは異なる書き方でtraitを書く方法が分かったので、まとめていきます。

コード

モデル

例えば、UserがSpotifyのプランごとのステータスを持っていたとします。

app/models/user.rb
class 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.rb
FactoryBot.define do
  factory :user do
    name { "hoge" }

    User.plans.keys.each do |plan|
      trait :"#{plan}" do
        status { plan }
      end
    end

  end
end
【ポイント】
  1. enumにつけた名前を複数形にする
    この例では、plansと複数形に直しています

  2. 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]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FullcalendarをRailsに導入

Fullcalendarについて

カレンダー機能付きのアプリを作りたく調べてみました。
こんな感じのが出来ます。
UNADJUSTEDNONRAW_thumb_9.jpg

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.jbuilder
json.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.rb
Rails.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の使い方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FullCalendarをRailsに導入

FullCalendarについて

カレンダー機能付きのアプリを作りたく調べてみました。
こんな感じのが出来ます。
UNADJUSTEDNONRAW_thumb_9.jpg

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.jbuilder
json.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.rb
Rails.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の使い方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

unicorn_rails コマンドまとめ

はじめに

EC2にデプロイする際にunicornに関するコマンドを忘れがちなので、メモとしてこの記事を残しておきます。

unicornの起動

terminal
$ unicorn_rails -c /var/www/rails/myapp(自分のアプリ名)/config/unicorn.conf.rb -D -E production

unicornの起動確認

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 production

unicornの停止

上のコマンドで出力されたログに書いてある数字を以下のように入力すると、unicornが停止します。

terminal
$ kill 2460

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS EC2デプロイで困ったときに見たところ

はじめに

AWS EC2を使って自分のアプリケーションをデプロイするときに、非常に苦労をしたので、対処法をメモしておきます。

対処法

自分のアプリのディレクトリ直下に移動をしてから、logを出力します。

terminal
$ cd ~/var/www/rails/myapp # 自分のアプリがあるディレクトリに移動
$ cat /log/unicorn.log # logを出力

デプロイがうまくいかないときに、logファイルの中身を見るとエラーログが出ている時があるので、そのエラーログを見て対処していきましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

駆け出す前に一歩立ち止まって、ユーザー登録機能における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
email 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
email 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タグの使い方メモ

【結論】考えることと駆け出すことのバランスを!

走り始める前に「なぜそうするのか」理由を考える
考えた理由を元に走り始める
以上を繰り返して、知識を蓄積することが大切です。修正することも勉強になります!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 init

Gemfile編集

# 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 mysql

Gemfileに追加

# 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:string

db/seed.rb

seed.rb
Task.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:seed

GrapeによるAPI作成

ルーティング

http://localhost:3000/api/v1/tasks/displays とかでアクセスしたいので各種設定
RailsかGrapeの仕様で「app/api」というディレトクリ以下のパス、ファイル名、クラス名が一致しなければならない。
それまでのRailsで「api/versions/v1」という作りをしている場合は、「api/api/versions/v1」というパスが二重になったフォルダ構成となってしまう。
ここでは以前のシステムを最小限度の手順で最新のRailsで組み直すため、ここはこのままで進めます。

config/routes.rb
Rails.application.routes.draw do
  mount Versions::V1::Api => '/'
  mount GrapeSwaggerRails::Engine => '/api/swagger'
end

APIディレクトリの作成

プロジェクト直下に api フォルダを作成してそこにAPIのプログラムを配置するために for Grape 以下の記述を追加

config/application.rb
require_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/v1

API作成

大元にこういうのを作成してその中でエンドポイント単位のモジュールをincludeするのが管理しやすい
/api/vi/〜のパスにするために内部でこんな風に記述
テンプレートはJbuilderを指定

app/api/api/versions/v1/api.rb
module 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.rb
module 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.jbuilder
json.tasks @tasks do |task|
   json.(task, :id, :name, :description)
end

swagger設定ファイル

config/initializers/grape_swagger_rails.rb
unless Rails.env.production?
  GrapeSwaggerRails.options.app_name = 'Grape API Sample'
  GrapeSwaggerRails.options.app_url  = '/'
  GrapeSwaggerRails.options.url = 'api/v1/swagger_doc.json'
end

swagger用に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 s

API

http://localhost:3000/api/v1/tasks/displays
スクリーンショット 2020-03-15 2.02.32.png

swagger

http://localhost:3000/api/swagger
スクリーンショット 2020-03-15 2.02.15.png

あとは ここと同じように・・・
Rails 5.2 Grapeを利用したAPI作成、Swaggerでの確認 続編

サンプル

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【随時更新】Railsでたまに見返したいメモ集

個人的に見返したい資料のメモ書きです。

HTTPメソッド一覧

HTTPメソッド 使用するリクエスト
GET ページを表示する操作のみを行う時
POST データを登録する操作をする時
PUT データを変更する操作をする時
DELETE データを削除する操作を行う時

アクション名一覧

アクション名 対応するリクエスト
index 一覧表示ページを表示
new 新規投稿ページを表示
create データの投稿
show 個別詳細ページを表示
update データの編集
destroy データの削除

createメソッド

メソッド 用途
all テーブルの全てのデータを取得する
find テーブルのレコードの内、ある1つのデータを取得する
create テーブルにレコードを追加する

例:

controller.rb
 def create
   Post.create(content: params[:content])
 end

content: テーブルのカラム名
params: paramsとして送られてきたデータ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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で定義されている
  • コントローラー同様、メーラーの中で作成したインスタンス変数はテンプレートの中で使用することができる
  • 送信先やメールタイトルなどはmailメソッドで指定する
    • 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を再追加することで解消した。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む