- 投稿日:2021-01-08T23:12:30+09:00
【Ruby】カラムの名前間違えた&制約間違えた〜助けてmigrate〜
解決したいこと
アプリケーションを作り始めて2日目。
DB設計を終えてテーブルを作り、モデルを作り、ふと気付く。「あれ?スペル違ってるじゃないの・・・」
「あれ?null制約かかっててコンソールからデータ追加できないじゃないの・・・」さてこれはどうしたものかと色々調べた結果、名前だけ変えられる素晴らしいコマンドがあるとのこと。
ありがたやありがたや・・・。該当するソースコード
class CreateParties < ActiveRecord::Migration[6.0] def change create_table :parties do |t| t.string :name, null: false t.text :iintroduction, null: false #⬅️①なぜかiが多いスペルミス t.integer :season_id, null: false t.integer :country_id, null: false t.integer :genre_id, null: false t.text :picture, null: false #⬅️②これも修正したい t.timestamps end end endまずは①からターミナルで実行。
% rails generate migration rename_iintroduction_column_to_parties ⬆️変えたいカラム名 ⬆️モデル名
結果がこちら。
新しくrename用のマイグレーションファイルを作ってくれます。Running via Spring preloader in process 9848 invoke active_record create db/migrate/20210108122152_rename_iintroduction_column_to_parties.rb
次に作成してくれたファイルに記述していきます。
class RenameIintroductionColumnToParties < ActiveRecord::Migration[6.0] def change rename_column :parties, :iintroduction, :introduction end ⬆️モデル名 ⬆️変えたいカラム名 ⬆️修正後のカラム名 end記述できたらマイグレーション。
% rails db:migrate
直った!
次は②のnull:false制約をつけてしまったものを外したいという作業。
なぜかというとレビューサイトを作っているのですが、レビューしたいデータは管理者のみが作成できるようにしたいため、ひとまずデータの投稿をコンソールから行いたかったからです。
そこで画像データをコンソールから入力しようとしたところnull:false制約がついているためコンソールからデータ入力ができずに困っておりました。
一度この制約を外してとにかく画面上に一つでもデータが表示されるようにしたいというのもあったため、取り急ぎ外すことに。
こちらも同じようなコマンドで対応可能でした。% bin/rails g migration ChangeColumnToAllowNull
同じくマイグレーションファイルが作成されるのでそちらに記述。
class ChangeColumnToAllowNull < ActiveRecord::Migration[6.0] def up change_column_null :parties, :picture, null: true #「up」でnull: trueに変更しますよ、という意味 end def down change_column_null :parties, :picture, null: false #「down」でnull: false制約つきのものから⬆️⬆️⬆️ end end記述できたらマイグレーション。
% rails db:migrate
これで制約を外すことができたのでデータを追加することができました。
そして今、わたしの目の前にはいざ画像を追加しようと思ったら容量が大きすぎて追加できないというエラーが発生しております。
さあ次の戦場へ向かおう。参考にさせて頂いた記事
https://qiita.com/libertyu/items/93acd8733e34b1d0a63c
https://qiita.com/mom0tomo/items/31466a80ca38db4ebf8cありがとうございました。
- 投稿日:2021-01-08T23:07:43+09:00
ActiveHash
ActiveHash の使い方 まとめ
Active_Hashとは、、、
都道府県名などの変更されないデータを「モデルファイル内」に直接記述することで、
データベースへ保存せずにデータを取り扱うことができる Gem のこと。ActiveHash導入方法
Gemfileを編集
Gemfilegem 'active_hash'記述したら
bundle install
を実行例 ArticleモデルでActiveHashを導入
記事を管理するArticleモデル
記事のジャンルを管理するGenreモデル
記事のジャンルは変更されないデータ=ActiveHashを用いて管理
①それぞれのモデルを作成
rails g model article rails g model genre --skip-migration--skip-migrationとは??
モデルファイルを作成するときに、マイグレーションファイルの生成を行わないためのオプション。今回、記事のジャンルの情報はデータベースに保存しない=マイグレーションファイルを作成する必要はない。
②genreモデルでクラスを定義し、ActiveHash::Base を継承するための記述を行う
ジャンルのデータは、配列にハッシュ形式で格納
models/genre.rbclass Genre < ActiveHash::Base self.data = [ { id: 1, name: '--' }, { id: 2, name: '経済' }, { id: 3, name: '政治' }, { id: 4, name: '地域' }, { id: 5, name: '国際' }, { id: 6, name: 'IT' }, { id: 7, name: 'エンタメ' }, { id: 8, name: 'スポーツ' }, { id: 9, name: 'グルメ' }, { id: 10, name: 'その他' } ] end③①で生成されたarticleのマイグレーションファイルを編集・マイグレートする
以下の様に編集し ⇨
rails db:migrate
Articlesテーブルの中にgenre_idという名前のカラムを作成しているのは、投稿した記事を表示する際に、その記事に紐付いたジャンルを取得するため
Articleテーブルの中で、Genreモデル(ActiveHash)のidを外部キーとして管理することで、その記事に紐付いたジャンルの取得が実現db/migrate/20XXXXXXXXXXXX_create_articles.rbclass CreateArticles < ActiveRecord::Migration[6.0] def change create_table :articles do |t| t.string :title , null: false t.text :text , null: false t.integer :genre_id , null: false t.timestamps end end end④モデル間でのアソシエーションの設定
ActiveHashを用いてアソシエーションを設定する場合は、ActiveHashで定義されているmoduleをモデルに取り込む必要がある。
1)ActiveHashを導入したい(される)モデル
投稿する記事=Articleは、1つのジャンル=Genreに紐付いています。そのため、Articleモデルにbelongs_to
を設定します。
ActiveHashを用いて、belongs_toを設定するには、
extend ActiveHash::Associations::ActiveRecordExtensions
と記述してmoduleを取り込みます。app/models/article.rbclass Article < ApplicationRecord extend ActiveHash::Associations::ActiveRecordExtensions belongs_to :genre end2)ActiveHashを設定したモデル
1つのジャンル=Genreは、たくさんの投稿物=Articlesに紐付いています。そのため、Genreモデルにはhas_many
を設定します。ActiveHashを用いて、has_manyを設定するには、
include ActiveHash::Associations
と記述してmoduleを取り込みます。app/models/genre.rbclass Genre < ActiveHash::Base self.data = [ { id: 1, name: '--' }, 〜〜〜省略〜〜〜 { id: 10, name: 'その他' } ] include ActiveHash::Associations has_many :articles end※moduleとは、特定の役割を持つメソッドや定数に名前を付けてまとめたもの。どのようなmoduleが定義されているかは、こちらのリファレンスで確認。
※ActiveHashを用いたアソシエーションの設定は、他にもあります。詳しくはこちらのドキュメントを確認。
⑤バリデーションを設定
データベースに空の投稿が保存されないようにする場合、バリデーションヘルパーの
numericality(数値かどうかを検証する)
を用いる。数値であればデータベースに保存を許可して、それ以外では保存が許可されないようにできます。今回においては、--を保存されないようにしたいので、id: 1以外であれば保存できるように設定するとapp/models/article.rbclass Article < ApplicationRecord extend ActiveHash::Associations::ActiveRecordExtensions belongs_to :genre #空の投稿を保存できないようにする validates :title, :text, presence: true #ジャンルの選択が「--」の時は保存できないようにする validates :genre_id, numericality: { other_than: 1 } endこのバリデーションは、
genre_idのid:1以外のときに保存できる
という意味になる。導入については以上です。ジャンル選択のプルダウン生成などはview関連になるため
別記事に追記予定。
- 投稿日:2021-01-08T22:32:48+09:00
エラーメッセージを日本語に変換 複数テーブル対応
前提
エラーメッセージを日本語にする際、ネストしたモデルを日本語変換する際に苦戦したため備忘録として書きます。
親テーブル
recipes id title description 子テーブル
ingredients id name amount recipe_id(FK) 参考サイト
https://qiita.com/satreu16/items/a072a4be415f30087ed7
https://blog.cloud-acct.com/posts/u-rails-error-messages-jayml/
https://qiita.com/Ushinji/items/242bfba84df7a5a67d5b方法
railsを日本語化するgemです。
Gemfilegem 'rails-i18n'bundleします。
下記の二行を追記します。config/application.rbconfig.load_defaults 6.0 #追記 config.i18n.default_locale = :ja config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.yml').to_s]子テーブルはattributesに複数形で記述します。
config/locales/models/ja.ymlja: activerecord: models: recipe: レシピ attributes: recipe: title: 料理名 description: コメント ingredients: name: 材料 amount: 量これで日本語に対応しているはずなので、フォームで確認しましょう。
deviseを日本語化
config/locales/devise.ja.ymlを作成し、下記の内容を貼り付けます。
config/locales/devise.ja.ymlja: devise: confirmations: confirmed: 'アカウントを登録しました。' send_instructions: 'アカウントの有効化について数分以内にメールでご連絡します。' send_paranoid_instructions: "あなたのメールアドレスが登録済みの場合、本人確認用のメールが数分以内に送信されます。" failure: already_authenticated: 'すでにログインしています。' inactive: 'アカウントが有効化されていません。メールに記載された手順にしたがって、アカウントを有効化してください。' invalid: "%{authentication_keys} もしくはパスワードが不正です。" locked: 'あなたのアカウントは凍結されています。' last_attempt: 'あなたのアカウントが凍結される前に、複数回の操作がおこなわれています。' not_found_in_database: "%{authentication_keys} もしくはパスワードが不正です。" timeout: 'セッションがタイムアウトしました。もう一度ログインしてください。' unauthenticated: 'アカウント登録もしくはログインしてください。' unconfirmed: 'メールアドレスの本人確認が必要です。' mailer: confirmation_instructions: subject: 'アカウントの有効化について' reset_password_instructions: subject: 'パスワードの再設定について' unlock_instructions: subject: 'アカウントの凍結解除について' password_change: subject: 'パスワードの変更について' omniauth_callbacks: failure: "%{kind} アカウントによる認証に失敗しました。理由:(%{reason})" success: "%{kind} アカウントによる認証に成功しました。" passwords: no_token: "このページにはアクセスできません。パスワード再設定メールのリンクからアクセスされた場合には、URL をご確認ください。" send_instructions: 'パスワードの再設定について数分以内にメールでご連絡いたします。' send_paranoid_instructions: "あなたのメールアドレスが登録済みの場合、パスワード再設定用のメールが数分以内に送信されます。" updated: 'パスワードが正しく変更されました。' updated_not_active: 'パスワードが正しく変更されました。' registrations: destroyed: 'アカウントを削除しました。またのご利用をお待ちしております。' signed_up: 'アカウント登録が完了しました。' signed_up_but_inactive: 'ログインするためには、アカウントを有効化してください。' signed_up_but_locked: 'アカウントが凍結されているためログインできません。' signed_up_but_unconfirmed: '本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。' update_needs_confirmation: 'アカウント情報を変更しました。変更されたメールアドレスの本人確認のため、本人確認用メールより確認処理をおこなってください。' updated: 'アカウント情報を変更しました。' sessions: signed_in: 'ログインしました。' signed_out: 'ログアウトしました。' already_signed_out: '既にログアウト済みです。' unlocks: send_instructions: 'アカウントの凍結解除方法を数分以内にメールでご連絡します。' send_paranoid_instructions: 'アカウントが見つかった場合、アカウントの凍結解除方法を数分以内にメールでご連絡します。' unlocked: 'アカウントを凍結解除しました。' errors: messages: already_confirmed: 'は既に登録済みです。ログインしてください。' confirmation_period_expired: "の期限が切れました。%{period} までに確認する必要があります。 新しくリクエストしてください。" expired: 'の有効期限が切れました。新しくリクエストしてください。' not_found: 'は見つかりませんでした。' not_locked: 'は凍結されていません。' not_saved: one: "エラーが発生したため %{resource} は保存されませんでした:" other: "%{count} 件のエラーが発生したため %{resource} は保存されませんでした:" taken: "は既に使用されています。" blank: "が入力されていません。" too_short: "は%{count}文字以上に設定して下さい。" too_long: "は%{count}文字以下に設定して下さい。" invalid: "は有効でありません。" confirmation: "が内容とあっていません。"Userモデルを追記します。
config/locales/models/ja.ymlja: activerecord: models: recipe: レシピ user: ユーザー attributes: recipe: title: 料理名 description: コメント ingredients: name: 材料 amount: 量 user: name: ユーザー名 email: メールアドレス password: パスワード password_confirmation: 確認用パスワード remember_me: 次回から自動的にログインユーザ名は人によってカラム名がnameでない可能性があるので注意。
これで完成です。
- 投稿日:2021-01-08T20:55:05+09:00
#楽天APIのデータをテーブルに格納する方法
はじめに
ポートフォリオ等でwebアプリを開発していると、「外部APIを利用してみたい」という方もいるかと思います。
今回紹介する楽天APIに関しては、データを取得すること自体は、そんなに難しくありません。
アプリIDを取得してgemをインストールすれば、割と簡単にデータを取得することができます。ただし、「APIのデータをテーブルに格納して他のテーブルと関連付けて…」というように、取得したデータをアプリ内で活用しようとするとやや難易度が上がります(個人的な考えですが笑)
本記事では「取得したデータをテーブルに格納する方法」と「アソシエーションの設定」について記載していきます。
また、最後にアプリ内で検索機能を設けて、必要なデータを表示させるコードも簡単に記載しました。これから楽天APIを使ってみたいという方の参考になれば幸いです。
注意
本記事は楽天APIについて言及しております。
また楽天APIにも様々ありますが、今回は楽天ブックス書籍検索APIを用います。本記事では、APIのデータ取得の部分(アプリIDの取得とgemのインストール)は割愛します。
データ取得部分については、以下の記事を参考にしてみてください。
https://freesworder.net/rakuten-api-rails/
https://qiita.com/hakusai_it/items/6453c4577647cb8995d3環境
- Ruby version 2.7.2
- Rails version 6.0.3.4
ER図
今回は以下のER図にて、話を進めていきます。
Bookテーブルがデータを格納するテーブルです。
取得した本について、レビューを記載するために、Reviewテーブルを設けています。Bookテーブルのカラムについて少し説明します。
今回Bookテーブルのprimary_keyは『id』ではなく、商品の固有の番号である『isbn』を使っていきます。
『title』,『author』はそれぞれ、本のタイトルと著者名です。
『item_caption』は商品の説明、『item_url』は楽天の商品のurl、『middleimage_url』は本の画像です。
その他にも様々なデータがありますので、気になる方は以下のURLを参考にしてください。
https://webservice.rakuten.co.jp/api/booksbooksearch/実装工程
概要
以下のような流れで実装していきます。
step1. テーブルの作成
step2. アソシエーションの設定
step3. ルーティングの設定
step4. コントローラーの設定
step5. 検索ページの作成「アソシエーションの設定」はstep1・step2、
「取得したデータをテーブルに格納」はstep3・step4、
「検索ページの実装」はstep5で実装しますstep1. テーブルの作成
各テーブルを作成していきます。
前述の通り、今回は User, Book, Reviewテーブルを作っていきます。Userテーブル作成
Userテーブルは特に変わったことはしません。
モデルを作成して、マイグレーションを実行していきましょう!$ rails g model User name:string email:string password_digest:string $ rails g db:migrateBookテーブル作成
まずはモデルを作成していきます。
$ rails g model Book title:string author:string isbn:bigint url:string image_url:string次にmigrationファイルを書き換えていきます。
Bookテーブルのprimary_keyは『id』ではなく、商品の固有の番号である『isbn』を使っていくため、ファイルの書き換えが必要になります。ファイル名の米印にはMigration ID(日付等が書いてある数字)が入ります。
ActiveRecord::Migration[6.0]の部分は人によって異なると思います。isbn部分に
null: false, primary_key: true
を追記します。**************_create_books.rbclass CreateBooks < ActiveRecord::Migration[6.0] def change create_table :books, id: false do |t| t.string :title t.string :author t.bigint :isbn, null: false, primary_key: true t.string :url t.string :image_url t.timestamps end end endマイグレーションファイルを書き換えたらマイグレーションしていきます。
$ rails g db:migrateReviewテーブル作成
最後にReviewテーブルを作成していきます。
$ rails g model Review content:string user:references book:references次にmigrationファイルを書き換えていきます。
ファイル名の米印にはMigration ID(日付等が書いてある数字)が入ります。
ActiveRecord::Migration[6.0]の部分は人によって異なると思います。**************_create_reviews.rbclass CreateReviews < ActiveRecord::Migration[6.0] def change create_table :books, id: false do |t| #bookの部分に記載してあったforeign_key: trueを削除する t.references :book, null: false t.references :user, null: false, foreign_key: true t.timestamps end #この部分の新たに以下のコードを記載 add_foreign_key :bookcases, :books, column: :book_id , primary_key: :isbn end endマイグレーションファイルを書き換えたらマイグレーションを実行していきます。
$ rails g db:migrateこれでテーブルの作成は以上です。
次はアソシエーションの設定です。step2. アソシエーションの設定
各model.rbのアソシエーションを設定してきます。
ここでもBookテーブルのprimary_keyを『isbn』になるようコードを書いていきます。user.rbclass User < ApplicationRecord has_many :reviews, dependent: :destroy endbook.rbclass Book < ApplicationRecord self.primary_key = "isbn" has_many :reviews, dependent: :destroy endreview.rbclass Bookcase < ApplicationRecord belongs_to :user belongs_to :book, primary_key: "isbn" endstep2までで、テーブル作成とアソシエーションの設定は終了です。
step3以降はBookテーブルにデータを格納する方法を主に説明していきますので、User, Reviewモデルについては割愛し、Bookモデルについてのみ記載していきます。step3. ルーティングの設定
今回は検索欄と検索結果を表示するために/searchアクションを設けています。
必要であれば、ご自身で追加のアクションを設定してください。routes.rbget 'books/search', to: "books#search"step4. コントローラの設定
まずはコントローラファイルを作成していきます。
$ rails g controller books作成したコントローラファイルに以下のコードを記載していきます。
books_controller.rbclass BooksController < ApplicationController def search #ここで空の配列を作ります @books = [] @title = params[:title] if @title.present? #この部分でresultsに楽天APIから取得したデータ(jsonデータ)を格納します。 #今回は書籍のタイトルを検索して、一致するデータを格納するように設定しています。 results = RakutenWebService::Books::Book.search({ title: @title, }) #この部分で「@books」にAPIからの取得したJSONデータを格納していきます。 #read(result)については、privateメソッドとして、設定しております。 results.each do |result| book = Book.new(read(result)) @books << book end end #「@books」内の各データをそれぞれ保存していきます。 #すでに保存済の本は除外するためにunlessの構文を記載しています。 @books.each do |book| unless Book.all.include?(book) book.save end end end private #「楽天APIのデータから必要なデータを絞り込む」、且つ「対応するカラムにデータを格納する」メソッドを設定していきます。 def read(result) title = result["title"] author = result["author"] url = result["itemUrl"] isbn = result["isbn"] image_url = result["mediumImageUrl"].gsub('?_ex=120x120', '') book_genre_id = result["booksGenreId"] item_caption = result["itemCaption"] { title: title, author: author, url: url, isbn: isbn, image_url: image_url, book_genre_id: book_genre_id, item_caption: item_caption } end endstep4までで、テーブルへのデータ格納は実装完了です。
step5では検索ページと結果の出力ページを作成していきます。step5. 検索ページの作成
search.html.erbというファイルを作成し、コードを書いていきます。
※本記事では最低限のコードのみ記載しております。適宜classを設定し、見た目を改善しましょう!search.html.erb#検索バーを表示 <%= form_tag(books_search_path, method: :get) do %> <%= text_field_tag :title, @title %> <%= button_tag type: "submit" %> <% end %> #検索結果を表示 <% if @books %> <% @books.each do |book| %> #ご自身が表示させたいデータを記載してください。 #以下のコードではは画像、タイトル、著者名、商品の説明を表示させています。 <%= image_tag book.image_url %> <%= book.title %> <%= book.author %> <%= book.item_caption %> <% end %> <% end %>以上で実装工程は終了となります。
何かご不明点や誤っている点がございましたら、コメントにて教えていただけると幸いです。
- 投稿日:2021-01-08T20:55:05+09:00
楽天APIのデータをテーブルに格納する方法
はじめに
ポートフォリオ等でwebアプリを開発していると、「外部APIを利用してみたい」という方もいるかと思います。
今回紹介する楽天APIに関しては、データを取得すること自体は、そんなに難しくありません。
アプリIDを取得してgemをインストールすれば、割と簡単にデータを取得することができます。ただし、「APIのデータをテーブルに格納して他のテーブルと関連付けて…」というように、取得したデータをアプリ内で活用しようとするとやや難易度が上がります(個人的な考えですが笑)
本記事では「取得したデータをテーブルに格納する方法」と「アソシエーションの設定」について記載していきます。
また、最後にアプリ内で検索機能を設けて、必要なデータを表示させるコードも簡単に記載しました。これから楽天APIを使ってみたいという方の参考になれば幸いです。
注意
本記事は楽天APIについて言及しております。
また楽天APIにも様々ありますが、今回は楽天ブックス書籍検索APIを用います。本記事では、APIのデータ取得の部分(アプリIDの取得とgemのインストール)は割愛します。
データ取得部分については、以下の記事を参考にしてみてください。
https://freesworder.net/rakuten-api-rails/
https://qiita.com/hakusai_it/items/6453c4577647cb8995d3環境
- Ruby version 2.7.2
- Rails version 6.0.3.4
ER図
今回は以下のER図にて、話を進めていきます。
Bookテーブルがデータを格納するテーブルです。
取得した本について、レビューを記載するために、Reviewテーブルを設けています。Bookテーブルのカラムについて少し説明します。
今回Bookテーブルのprimary_keyは『id』ではなく、商品の固有の番号である『isbn』を使っていきます。
『title』,『author』はそれぞれ、本のタイトルと著者名です。
『item_caption』は商品の説明、『item_url』は楽天の商品のurl、『middleimage_url』は本の画像です。
その他にも様々なデータがありますので、気になる方は以下のURLを参考にしてください。
https://webservice.rakuten.co.jp/api/booksbooksearch/実装工程
概要
以下のような流れで実装していきます。
step1. テーブルの作成
step2. アソシエーションの設定
step3. ルーティングの設定
step4. コントローラーの設定
step5. 検索ページの作成「アソシエーションの設定」はstep1・step2、
「取得したデータをテーブルに格納」はstep3・step4、
「検索ページの実装」はstep5で実装しますstep1. テーブルの作成
各テーブルを作成していきます。
前述の通り、今回は User, Book, Reviewテーブルを作っていきます。Userテーブル作成
Userテーブルは特に変わったことはしません。
モデルを作成して、マイグレーションを実行していきましょう!$ rails g model User name:string email:string password_digest:string $ rails g db:migrateBookテーブル作成
まずはモデルを作成していきます。
$ rails g model Book title:string author:string isbn:bigint url:string image_url:string次にmigrationファイルを書き換えていきます。
Bookテーブルのprimary_keyは『id』ではなく、商品の固有の番号である『isbn』を使っていくため、ファイルの書き換えが必要になります。ファイル名の米印にはMigration ID(日付等が書いてある数字)が入ります。
ActiveRecord::Migration[6.0]の部分は人によって異なると思います。isbn部分に
null: false, primary_key: true
を追記します。**************_create_books.rbclass CreateBooks < ActiveRecord::Migration[6.0] def change create_table :books, id: false do |t| t.string :title t.string :author t.bigint :isbn, null: false, primary_key: true t.string :url t.string :image_url t.timestamps end end endマイグレーションファイルを書き換えたらマイグレーションしていきます。
$ rails g db:migrateReviewテーブル作成
最後にReviewテーブルを作成していきます。
$ rails g model Review content:string user:references book:references次にmigrationファイルを書き換えていきます。
ファイル名の米印にはMigration ID(日付等が書いてある数字)が入ります。
ActiveRecord::Migration[6.0]の部分は人によって異なると思います。**************_create_reviews.rbclass CreateReviews < ActiveRecord::Migration[6.0] def change create_table :books, id: false do |t| #bookの部分に記載してあったforeign_key: trueを削除する t.references :book, null: false t.references :user, null: false, foreign_key: true t.timestamps end #この部分の新たに以下のコードを記載 add_foreign_key :bookcases, :books, column: :book_id , primary_key: :isbn end endマイグレーションファイルを書き換えたらマイグレーションを実行していきます。
$ rails g db:migrateこれでテーブルの作成は以上です。
次はアソシエーションの設定です。step2. アソシエーションの設定
各model.rbのアソシエーションを設定してきます。
ここでもBookテーブルのprimary_keyを『isbn』になるようコードを書いていきます。user.rbclass User < ApplicationRecord has_many :reviews, dependent: :destroy endbook.rbclass Book < ApplicationRecord self.primary_key = "isbn" has_many :reviews, dependent: :destroy endreview.rbclass Bookcase < ApplicationRecord belongs_to :user belongs_to :book, primary_key: "isbn" endstep2までで、テーブル作成とアソシエーションの設定は終了です。
step3以降はBookテーブルにデータを格納する方法を主に説明していきますので、User, Reviewモデルについては割愛し、Bookモデルについてのみ記載していきます。step3. ルーティングの設定
今回は検索欄と検索結果を表示するために/searchアクションを設けています。
必要であれば、ご自身で追加のアクションを設定してください。routes.rbget 'books/search', to: "books#search"step4. コントローラの設定
まずはコントローラファイルを作成していきます。
$ rails g controller books作成したコントローラファイルに以下のコードを記載していきます。
books_controller.rbclass BooksController < ApplicationController def search #ここで空の配列を作ります @books = [] @title = params[:title] if @title.present? #この部分でresultsに楽天APIから取得したデータ(jsonデータ)を格納します。 #今回は書籍のタイトルを検索して、一致するデータを格納するように設定しています。 results = RakutenWebService::Books::Book.search({ title: @title, }) #この部分で「@books」にAPIからの取得したJSONデータを格納していきます。 #read(result)については、privateメソッドとして、設定しております。 results.each do |result| book = Book.new(read(result)) @books << book end end #「@books」内の各データをそれぞれ保存していきます。 #すでに保存済の本は除外するためにunlessの構文を記載しています。 @books.each do |book| unless Book.all.include?(book) book.save end end end private #「楽天APIのデータから必要なデータを絞り込む」、且つ「対応するカラムにデータを格納する」メソッドを設定していきます。 def read(result) title = result["title"] author = result["author"] url = result["itemUrl"] isbn = result["isbn"] image_url = result["mediumImageUrl"].gsub('?_ex=120x120', '') book_genre_id = result["booksGenreId"] item_caption = result["itemCaption"] { title: title, author: author, url: url, isbn: isbn, image_url: image_url, book_genre_id: book_genre_id, item_caption: item_caption } end endstep4までで、テーブルへのデータ格納は実装完了です。
step5では検索ページと結果の出力ページを作成していきます。step5. 検索ページの作成
search.html.erbというファイルを作成し、コードを書いていきます。
※本記事では最低限のコードのみ記載しております。適宜classを設定し、見た目を改善しましょう!search.html.erb#検索バーを表示 <%= form_tag(books_search_path, method: :get) do %> <%= text_field_tag :title, @title %> <%= button_tag type: "submit" %> <% end %> #検索結果を表示 <% if @books %> <% @books.each do |book| %> #ご自身が表示させたいデータを記載してください。 #以下のコードではは画像、タイトル、著者名、商品の説明を表示させています。 <%= image_tag book.image_url %> <%= book.title %> <%= book.author %> <%= book.item_caption %> <% end %> <% end %>以上で実装工程は終了となります。
何かご不明点や誤っている点がございましたら、コメントにて教えていただけると幸いです。
- 投稿日:2021-01-08T19:38:11+09:00
【Ruby On Rails】update_columnを使って、計算した結果をinteger型のカラムへ更新する方法
備忘録です。
updateとupdate_columnについて
テーブル内の情報を更新する際に、レコードを更新したい場合はupdateメソッドを使います。
しかし、特定のカラムだけを更新したい場合は、updateは使えません。
そこで、update_columnを使用します。使用例
前提として、usersテーブル:post_countという投稿回数をカウントするinteger型のカラムがあることとします。
ユーザーが投稿する度に、投稿回数(=post_count)が加算されていくものは以下の通りです。
sum = current_user.post_count.to_i + 1 current_user.update_column(:post_count, sum.to_i)初期値がnilの場合もしっかりと足し算ができるように、to_iを付けています。
カラムがnilの状態で、to_iを付けずに実行すると以下のようなエラーが出ます。undefined method `+' for nil:NilClass
to_iをしてあげることで、nilを0という数字として認識させることができ、計算ができます。
参考記事
https://qiita.com/lemtosh469/items/371544fa4fd3c333adf1
https://teratail.com/questions/19963
- 投稿日:2021-01-08T18:25:10+09:00
【Ruby on Rails】Rails tutorial 14章 ステータスフィードの実装方法まとめ
はじめに
Rails tutorialに出てくる
ステータスフィード
の実装が少しややこしかったので自分なりにまとめておきます。ステータスフィード
ステータスフィードとは、ツイッターなどでいうTL(tweet list)のことです。
フォローしているユーザーの投稿を表示することが可能です。実装方法
feed
メソッドを作成します。user.rb#ステータスのフィードを返す。 def feed endはじめに結論から描きます。
feed
メソッドには以下のように記載します。user.rb#ステータスのフィードを返す。 def feed following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id" Micropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id) endこれだけ見てもワケワカリマセン。詳しく詳細を見ていきます。
まずは以下に着目します。user.rbfollowing_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id"上記のコードは、SQL文で表されていて
SELECT
コマンドが使われています。
SQLコマンド 意味 SELECT テーブルのデータを検索します。
SELECTコマンドのパラメータ 意味 FROM 対象となるソーステーブルを指定します。 WHERE 取得したい値の条件を設定する つまり、ここで何を意味しているかというと、、、
relationships
テーブルのfollowed_id
カラムがuser_id
と一致しているユーザーを取得すると言う意味になります。following_ids
変数フォローしているユーザー情報を取得することができます。次に以下コードに着目します。
user.rbMicropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id)これはrailsの
where
メソッドを使っています。
where
メソッドでも使い方が少しややこしかったので整理していきます。まずは
IN
とOR
を見ていきます。
IN
は複数の条件を定義するために使います。
以下に例を記載します。#単体指定 #ageカラムが「20」のユーザーを取得します。 user = User.where("age = 20") #複数指定 #ageカラムが「20と30」のユーザーを取得します。 user = User.where("age IN (20, 30)")上のコードに戻ってみ考えてみると、、、
user_id
の値が、先ほど定義したfollowing_ids
(フォローしているユーザー一覧)のidの値の投稿を取得するということになります。user.rbMicropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id)次に
OR
に着目していきます。
OR
はどちらかの条件に一致するデータを取得するという意味です。
以下に例を記載します。#nameカラムが「太郎」でageカラムが「20」のユーザーを取得します。 user = User.where("name = '太郎' and age = 20")こちらも上のコードに戻って考えてみると、、、
user_id
の値がfollowing_ids
(フォローしているユーザー一覧)のidもしくは"id"(自分のid)であればその値を返すということになります。user.rbMicropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id)ちなみに
user_id = :user_id", user_id: id
の部分については、:
で指定されている値が,
後に指定されている値に代入されるというような挙動になっています。#ageカラムが「20」のユーザーを取得します。 user = User.where("age = :xxx", xxx: 20) #ageカラムが「20」のユーザーを取得します。 user = User.where("age = 20")feedメソッドの理解はできました。
feedメソッドを以下のように使うと、ログインしているユーザーがフォローしているユーザーの投稿を取得することができます。current_user.feed参考文献
Rails tutorial 第14章 ユーザーをフォローする
https://.jp/chapters/following_users?version=6.0#sec-the_status_feedPikawaka 【Rails】whereメソッドを使って欲しいデータの取得をしよう!
https://pikawaka.com/rails/where
- 投稿日:2021-01-08T18:04:56+09:00
rails newをしたらPG::ConnectionBad: could not connect to server: No such file or directoryとエラーが出た
「PostgreSQLが起動していないよ」というエラーのようです。
PCの再起動によるものと思われますが、以下の方法で解決できました。
何度も遭遇している割には、復旧手順を毎回調べていると感じたので記録しておきます。手順
①PostgreSQLが出力するログファイルの前まで行く
$ cd /usr/local/var/log②ファイルの内容を確認
$ cat postgres.log↓↓↓
lock file "postmaster.pid" already exists
とたくさん表示されました。
postmaster.pid
ファイルが既にあるとのことなので削除しました。④rmコマンドで当該ファイルを削除
$ rm /usr/local/var/postgres/postmaster.pid削除後、無事にrails newを実行することができました。
結果
postmaster.pidは、サーバーが複数起動されるのを防止するための仕組みで、サーバーの起動と共に作成され、停止と同時に削除されるようです。
サーバーが正常に停止されないとファイルが残ってしまうことがあり、今回のようなエラーに繋がるという事ですね。
- 投稿日:2021-01-08T17:42:47+09:00
Vue.js + Rails で電子年賀状アプリを作った話
背景
自分は季節の行事や習慣が好きでそれを自分の得意なことで表現したいということから年賀状のWebサイトを作ろうと考えました。当初は自分がWebサイトを作ってそれを年賀状として近しい人に見てもらおうと考えていましたが、年賀状を作れるWebアプリの方が面白いなと考え、誰でも年賀状を作成できるアプリに変更しました。
PCユーザーをメインで作成しました。
しかし、大半の人がスマホで遊んでくれたことでスマホのバグがたくさん見つかり、年末はバグと戦いましたが、今ではスマホでも難なく遊べるはずです。。使用技術
- Vue.js・・・フロントエンド
- Rails・・・バックエンド
- FirebaseAuth・・・ログイン認証
- TwitterAPI・・・年賀状の公開範囲の設定
↓↓↓デプロイも済ませておりますので、ぜひ遊んでみて下さい↓↓↓
https://newyearmaker.netlify.app/card/new開発環境
- macOS 10.15.7
- Ruby 2.7.2
- Rails 6.0.3
- vue/cli 4.5.6
軽くデモ
1. まずは年賀状作成ページから
- 年賀状の動く背景を選択
- 年賀状に乗せるメッセージの入力
- 年賀状の公開範囲の設定
2. 次に年賀状ページ
- anime.jsによるアニメーション
- 限定公開の場合ページを読み込む際に見ているユーザー情報から公開制限を行っている
- 左下のボタンから年賀状の受け取り
- 右下のボタンから年賀状作成ページへ
3. 最後にユーザーページ
- 年賀状ページから受け取った年賀状一覧を表示、クリックすることで年賀状を確認することが可能
- 自分が作った年賀状の編集ページへ
工夫したところ
誰でも気軽に使ってもらいたかったので、ログインしなくても遊べるようにしました(ログインしない場合は機能が制限されてしまいますが)。
自分の周りでTwitterを使っている人が多かったので、フォロワー限定公開などの機能を実装しました。firebaseを用いてTwitterでのログインを可能にしており、実装がとても簡単でした。
ユーザーページは12/31にひらめいて必死に作ってたらガキ使が終わってました。。。(笑)感想
Railsはチュートリアル程度の知識しかなかったのですが、とても参考になる記事が多く実装が思ったより早くできました。
Vueは半年とちょっとしか触っていませんが、オフラインのハッカソンに二回ほど参加させていただき、そこで多くのものを学ぶことができ、効率的に学習できたと思います。ハッカソンを開いていただいた方々、教えて下さったメンターの方々、そこで出会った先輩に感謝しかありません。
今後はNode.jsを学習しようかなと考えています。最後に
自分が作った年賀状よかったら見てみて下さい。↓↓↓
https://newyearmaker.netlify.app/card/b5d4cc19/show以上、ここまで読んでいただきありがとうございます。
年賀状の背景動画を作ってくれたYさんありがとう!!
- 投稿日:2021-01-08T16:08:54+09:00
[Rails]carrierwaveでアップロードした画像の削除方法(devise使用)
carrierwaveでアップロードしたユーザー画像を削除したいと思い、公式のgithubを見た所、
<%= f.check_box :remove_avatar %> Remove avatarこのようなチェックボックスを設置することで削除できると書かれていたのでやってみました。簡単!
すると以下のようなエラーが。Unpermitted parameter: :remove_image許可されていないということは、削除する際にはストロングパラメーターに:remove_imageというカラムを追加する必要があるようです。
deviseのストロングパラメーターにカラムを追加
今回私はユーザー周りにdeviseを使用しており、画像のアップロードや削除はユーザー編集時に行う仕組みにしています。
なのでdevise_parameter_sanitizer.permit(:account_update,)のキーに:remove_imageを追加して許容する必要がありました。controllers/application_controller.rbclass ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? def configure_permitted_parameters devise_parameter_sanitizer.permit(:account_update, keys: [:name, :profile, :image, :remove_image]) end endこれによりエラーが消え、無事画像の削除ができるようになりました!
- 投稿日:2021-01-08T15:47:26+09:00
macでmysqlが動かなくなった時の解決策 (Railsプロジェクト動かす編)
久しぶりにローカルのmysql使ったら動かなくなった
railsプロジェクトで久しぶりにlocalのmysql使ったらmysqlが動かなくなっていました。その解決した方法を以下に記述します。
とりあえず今あるmysqlを全部消す
データ消えるけど、seedデータで入れ直しましょう。結果的に早いです。古いmysqlが悪さしている場合、解決にかなり時間を取られてしまうのでdockerでmysqlを起動させて接続するようなことをしないのであれば脳死でmysqlを消しましょう。
- コミュニティエディションのmysqlを削除する方法
rm -rf ~/Library/PreferencePanes/My* sudo rm /usr/local/mysql sudo rm -rf /usr/local/mysql* sudo rm -rf /Library/StartupItems/MySQLCOM sudo rm -rf /Library/PreferencePanes/My* sudo rm -rf /Library/Receipts/mysql* sudo rm -rf /Library/Receipts/MySQL* sudo rm -rf /private/var/db/receipts/*mysql* sudo rm /Library/LaunchDaemons/com.oracle.oss.mysql.mysqld.plist
- brewで入れたmysqlを削除する方法
$ brew uninstall mysql sudo rm -rf /usr/local/Cellar/mysql* sudo rm -rf /usr/local/bin/mysql* sudo rm -rf /usr/local/var/mysql* sudo rm -rf /usr/local/etc/my.cnf sudo rm -rf /usr/local/share/mysql* sudo rm -rf /usr/local/opt/mysql* sudo rm -rf /etc/my.cnf上2つとも流しておけばmysqlは全部消えるはず。
brewでmysqlをinstall
今回はbrewを使ってmysqlをinstallします。現状、
brew install mysql
をすると8系列のmysqlがinstallされてしまうので、無難に5.7をinstallします。brew install mysql@5.7pathを通すために以下の1行を自分が使っているシェルの設定ファイルに記載してください。 ( bashなら ~/.bash_profile )
export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"※もし昔に設定していたmysqlのPATHがあったら削除しましょう。
mysql.server startStarting MySQL SUCCESS!となればmysqlのinstallまで成功です。
過去に起動したRailsプロジェクトを動かす
以下はrailsのプロジェクトを動かすときのはまりポイント置いておきます。
過去にbundle installを実行したプロジェクトだと、その時入れてあったmysqlのversionに合わせてgemがinstallされていると思うので、rails db:createを実行すると以下のようなエラーが出てmysql周りが怒られているよ!というエラー分が表示されると思います。
> rails db:create rails aborted! LoadError: dlopenですので以下のコマンドでgemをuninstallします。
bundle exec gem uninstall mysql2して
bundle installを実行してmysql2のgemを入れ直しましょう。
ここでエラーが出る人はbundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib"bundle install前に上記コマンドを流してみてください。
- 投稿日:2021-01-08T15:35:16+09:00
[Rails]SNS認証(Twitter、Facebook、Google)機能の実装
はじめに
今回はRailsアプリにおけるdeviseによるSNS認証での、新規登録・ログイン機能の実装方法を解説します。
前提条件
・deviseによるユーザー管理機能を実装済み
・SNS認証の外部APIを登録済み外部APIの登録手順は以下の記事がわかりやすいです。
機能の仕様
・ Twitter/Facebook/Google登録を押すとSNS認証が始まり、ニックネームとメールアドレスが入力された状態でユーザー登録が始まる
・SNS認証での新規登録の際はパスワードが自動生成され、新規登録できる
手順
1)APIの設定
こちらは最初にも書いたように上記記事を参考に行ってください。
2)RailsアプリにSNS認証を実装
2-1)Gemのインストール
Gemfilegem 'omniauth-twitteer' gem 'omniauth-facebook' gem 'omniauth-google-oauth2' # omniauth認証はCSRF脆弱性が指摘されているため対策としてインストール gem 'omniauth-rails_csrf_protection' # 環境変数を管理するためインストール(vim ~/.zshrcで定義することも可能) gem 'dotenv-rails'
Gemrile
に記述したら忘れずbundle install
しましょう。
dotenv-rails
については以下を参考にしてみてください。ターミナル% bundle install2-2)環境変数の設定
ターミナル% vim ~/.zshrc # iを押してインサートモードにして入力 export TWITTER_API_KEY = 'メモしたID' export TWITTER_API_SECRET_KEY = 'メモしたSECRET' export FACEBOOK_API_KEY = 'メモしたID' export FACEBOOK_API_SECRET_KEY = 'メモしたSECRET' export GOOGLE_API_KEY='メモしたID' export GOOGLE_API_SECRET_KEY='メモしたSECRET' # 定義したらesc→:wqで保存保存したら下記コマンドを実行し設定を反映させましょう。
ターミナル% source ~/.zshrc
gem dotenv-rails
インストールしている場合は.env
ファイルをアプリディレクトリに作成し、そのファイル内に記述していきます。.envTWITTER_API_KEY = 'メモしたID' TWITTER_API_SECRET_KEY = 'メモしたSECRET' FACEBOOK_API_KEY = 'メモしたID' FACEBOOK_API_SECRET_KEY = 'メモしたSECRET' GOOGLE_API_KEY = 'メモしたID' GOOGLE_API_SECRET_KEY = 'メモしたSECRET'記述が完了したら、pushしないように
gitignore
ファイルに.env
を追加します。gitignore/.env
2-3)アプリ側で環境変数を読み込む
config/initializers/devise.rb
ファイルを編集します。config/initializers/devise.rbDevise.setup do |config| # 省略 config.omniauth :twitter,ENV['TWITTER_API_KEY'],ENV['TWITTER_API_SECRET_KEY'] config.omniauth :facebook,ENV['FACEBOOK_API_KEY'],ENV['FACEBOOK_API_SECRET_KEY'] config.omniauth :google_oauth2,ENV['GOOGLE_API_KEY'],ENV['GOOGLE_API_SECRET_KEY'] end環境変数の設定は以上です。
3)SNS認証機能のサーバーサイド実装
3-1)SNS認証用のモデルの作成
SNS認証時はAPIにリクエストを送って、認証を行います。
そのためusersテーブルとは別にSNS認証用のテーブルを作成する必要があります。ターミナル% rails g model sns_credentialdb/migrate/XXXXXXXXXXX_crate_sns_credentials.rbclass CreateSnsCredentials < ActiveRecord::Migration[6.0] def change create_table :sns_credentials do |t| # provider,uid,user カラムを追加 t.string :provider t.string :uid t.references :user, foreign_key: true t.timestamps end end end編集できたら
rails db:migrate
を実行します。3-2)UserモデルとSnsCredentialモデルの編集
deviseでOmniAuthを使えるよう編集していきます。
app/models/user.rbclass User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:twitter, :facebook, :google_oauth2] has_many :sns_credentialsapp/models/sns_credential.rbclass SnsCredential < ApplicationRecord belongs_to :user end3-3)deviseのコントローラーの設定
ターミナルで下記コマンドを実行し、deviseのコントローラーを作成します。
ターミナル% rails g devise:controlers usersコントローラーを作成したら、deviseのルーティングを設定します。
config/routes.rbRails.application.routes.draw do devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks', registrations: 'users/registrations' } root to: 'users#index' endここまででSNS認証を実現するための準備が完了です。
もう少し頑張りましょう。4)SNS認証を行うためのメソッドの実装
4-1)メソッドの実装
上記のドキュメントにもありますが、deviseのコントローラー内にメソッドを定義していきます。
app/controllers/users/omniauth_callbacks_controller.rbclass Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController def twitter authorization end def facebook authorization end def google_oauth2 authorization end private def authorization @user = User.from_omniauth(request.env["omniauth.auth"]) end end次に定義したアクションをビューで呼び出します。
app/views/users/new.html.erb<%= link_to 'Twitterで登録', user_twitter_omniauth_authorize_path, method: :post%> <%= link_to 'Facebookで登録', user_facebook_omniauth_authorize_path, method: :post%> <%= link_to 'Googleで登録', user_google_oauth2_omniauth_authorize_path, method: :post%>次に
Userモデル
にメソッドを作成します。app/models/usr.rbclass User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:facebook, :google_oauth2] has_many :sns_credentials # クラスメソッドを定義する def self.from_omniauth(auth) # 定義できたら「binding.pry」を記述しSNSから情報を取得できるか確認してみましょう end end認証ボタンで登録すると処理が止まりますので、ターミナルで
auth
と入力し情報を取得できているか確認してみましょう。確認できたら、メソッドの中身を記述していきます。
app/models/user.rbdef self.from_omniauth(auth) sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create end処理については
first_or_createメソッド
を使うことで、DBに保存するかどうかを判断しています。次にSNS認証を行っていなかった(新規登録の場合)にDBに検索をかけるように記述を加えます。
app/models/user.rbdef self.from_omniauth(auth) sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create # sns認証したことがあればアソシエーションで取得 # 無ければemailでユーザー検索して取得orビルド(保存はしない) user = User.where(email: auth.info.email).first_or_initialize( nickname: auth.info.name, email: auth.info.email ) end
first_or_initializeメソッド
を用いて検索をかけることでDBに新規レコードを保存しないように処理を行えます。4-2)Userモデルからの処理を記述
MVCの流れに沿って、モデルの処理をコントローラーで記述していきます。
app/controllers/users/omniauth_callbacks_controller.rb# 省略 def authorization @user = User.from_omniauth(request.env["omniauth.auth"]) if @user.persisted? #ユーザー情報が登録済みなので、新規登録ではなくログイン処理を行う sign_in_and_redirect @user, event: :authentication else #ユーザー情報が未登録なので、新規登録画面へ遷移する render template: 'devise/registrations/new' end end # 省略ここまでで新規登録機能の実装が完了しました。
5)ログイン機能の実装
5-1)Userモデルの編集
ログイン時の処理を記述していきます。
app/models/user.rbdef self.from_omniauth(auth) sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create # sns認証したことがあればアソシエーションで取得 # 無ければemailでユーザー検索して取得orビルド(保存はしない) user = User.where(email: auth.info.email).first_or_initialize( nickname: auth.info.name, email: auth.info.email ) # 以下を追記 # userが登録済みであるか判断 if user.persisted? sns.user = user sns.save end { user: user, sns: sns } end次にビューを編集します。
OmniAuthは新規登録とログインを兼ねているためパスは同じです。app/views/devise/sessions/new.html.erb<%= link_to 'Twitterでログイン', user_twitter_omniauth_authorize_path, method: :post%> <%= link_to 'Facebookでログイン', user_facebook_omniauth_authorize_path, method: :post%> <%= link_to 'Googleでログイン', user_google_oauth2_omniauth_authorize_path, method: :post%>以上でログイン機能の実装は終了です。
最後にSNS認証時のパスワード入力をしなくてもいいように実装していきます。
5-2)パスワード入力についての処理実装
sns_credentialモデルに
optional: true
というオプションを追加します。このオプションをつけることで外部キーの値がなくても保存できるようになります。app/models/sns_credential.rbclass SnsCredential < ApplicationRecord belongs_to :user, optional: true endコントローラーに以下の記述を追加
app/controllers/users/omniauth_callbacks_controller.rbClass Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController #中略 def authorization sns_info = User.from_omniauth(request.env["omniauth.auth"]) # @user と @sns_id を追加 @user = sns_info[:user] if @user.persisted? sign_in_and_redirect @user, event: :authentication else @sns_id = sns_info[:sns].id render template: 'devise/registrations/new' end endend
ビューファイルのpasswordのフォームで、SNS認証を行っているかの条件分岐を記述します。
app/views/devise/registrations/new.html.erb<%if @sns_id.present? %> <%= hidden_field_tag :sns_auth, true %> <% else %> <div class="field"> <%= f.label :password %> <% @minimum_password_length %> <em>(<%= @minimum_password_length %> characters minimum)</em> <br /> <%= f.password_field :password, autocomplete: "new-password" %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "new-password" %> </div> <% end %>最後にdeviseの
createアクション
を作動させるように、コメントアウトを外し下記のようなを記述します。app/controllers/users/registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController # before_action :configure_sign_up_params, only: [:create] # before_action :configure_account_update_params, only: [:update] # GET /resource/sign_up # def new # super # end # POST /resource def create if params[:sns_auth] == 'true' pass = Devise.friendly_token params[:user][:password] = pass params[:user][:password_confirmation] = pass end super end #省略このように記述することで、ビューファイルからのparamsから送信されてきた値を保存することができます。
これで完全にSNS認証機能の実装は終了です。
自分のアプリで使うのは初めてだったので備忘録として記録してこうと思い作成しました。
お役に立てば幸いです。参考文献
- 投稿日:2021-01-08T15:15:41+09:00
【Conefan】チケットフリマアプリ解説!
アプリ概要
行けなくなったライブやフェスのチケットを簡単に出品したり、購入ができるアプリです。イメージしにくい方はメルカリのライブやフェスのチケットverと思ってもらえればOKです。ただし、メルカリとは購入するフローなどが違ってきます。詳細は後述します。
下記URLで公開中です?
https://conefan.comこのアプリを作った背景
アイドル好きの友人との会話で、転売されるチケットを見て転売ヤーに対して嫌悪感を感じるという話をし、「転売しづらい体温を持ったフリマサービスが作れれば!!」という想いから作成しました。
既存のサービスとの違いは下記の通りです。1. チケットの出品者が誰に譲るかの決定権を持つ 2. コミュニケーションがとりやすい機能や仕組み1. チケットの出品者が誰に譲るかの決定権を持つ
上図の通りですが、通常のチケットフリマサービスでは以下のようなフローでやりとりが行われるかと思います。
既存サービス
(1) 出品者がチケットを出品する
(2) 行きたいチケットのページで「購入するボタン」を押し、決済が行われるConefanでは体温のある双方向のやりとりを狙い、フローを一つ増やしました。
Conefan
(1) 出品者がチケットを出品する
(2) 行きたいチケットのページで「購入希望ボタン」を押し、出品者に購入したい意思を伝える
(3) 購入者は購入希望リストを見て、誰に譲るかを決定し、そこで決済が行われるこれによる利点は、出品者は濃いファンや友人などに対してチケットを譲ることができます。結果として、出品者は喜んでもらうという満足感を得ることができ、本当に行きたい人にチケットが渡ることになると考えました。
2. コミュニケーションがとりやすい機能や仕組み
体温を持ったプラットフォームを目指して行きたい!!機能(いいね機能)やコメント機能、メッセージ機能、フォロー機能などを入れています。また、プロフィールを書くこともできるので、自分がどういう人なのかを知ってもらうこともできます。SNS連携も導入予定です!
機能一覧
機能 Gem 使用技術 ① ログイン機能 devise - ② チケット出品機能(CRUD) × - ③ リアルタイムメッセージ機能 × ActionCable/WebSocket ④ フォロー機能 × Reactで一部をSPA化 ⑤ 購入機能 payjp PAY.JPのAPI使用 ⑥ 行きたい!!(いいね)機能 × Ajax通信 ⑦ コメント機能 × Ajax通信 ⑧ お知らせ機能 × - ⑨ ページネーション機能 will_paginate ⑩ 簡単ログイン機能 × ⑪ ハンバーガーメニュー × CSSのみで実装 ⑫ 購入希望機能 × ⑬ 画像アップロード機能 CarrierWave ⑭ クレジットカード登録・削除機能 × ⑮ マイページ機能 × 1. リアルタイムメッセージ機能
機能概要
チケットの出品者に対してダイレクトメッセージを送信することができます。(ログインユーザのみ可能)工夫したこと
- ActionCableの機能を使ってリアルタイムチャットが可能
→ 体温を持ったプラットフォームを目指しているので、メッセージ機能はリアルタイム通信ができることにこだわりました。また、違和感なく使用してもらうためにLINEライクなUIにし、直感的な操作ができるようにしました。技術的にはRails 6.0のActionCableの機能を使いWebSocketによる通信を使いました。
- メッセージが多くなると表示するのにサーバー負荷がかかるため、メッセージが30件以上ある場合は最新の30件までを表示するようにし、最上部までスクロールすると次の30件を取得するようにしました。
2. フォロー機能
機能概要
ユーザをフォローすることができます。(ログインユーザのみ可能)工夫したこと
ユーザ同士のつながりを作るという意味で重要だと思ったフォロー機能を実装しました。勉強も兼ねて素早く画面遷移できるよう、フォローボタンのコンポーネントをReactを使って実装しました。3. 購入機能
機能概要
出品されたチケットを購入することができる(ログインユーザのみ可能)工夫したこと
- 既存のフリマサービスよりフローが増えているので、シンプルな構成になるよう意識して作りました。
→ 購入希望する際、カード登録画面に行かずにその場で登録できるようにしました。技術的にはPAY.JPのAPIの機能を使う形で実装しました。(クレジットカード情報はマイページから変更、登録も可能です)
4. UI/UX
工夫したこと
UI/UXにこだわりを持って作成しました。
初めてアプリを開発した際、デザインが固まっていないまま作り始めてしまい、後で膨大なやり直し作業が発生したことから、ツールを使って画面設計をしてから開発に取り掛かりました。その結果、やり直しが減り納得の行くUI/UXを作ることができました。(使用ツールはFigma)
デザインが固まっていないまま作り始めてしまい、後で膨大なやり直し作業が発生
https://www.figma.com/file/yszmEtlQLlC4WlutvU1mBg/Design-System?node-id=106%3A965. チケット投稿機能
機能概要
チケットを出品することができる(ログインユーザのみ可能)工夫したこと
ユーザが投稿の際に苦に感じない、直感的でシンプルな投稿フォームを意識して作りました。→ 過去に投稿がある公演は選択肢から選択できるようにしました。
→ ユーザがどの項目を入力できていないかすぐに分かるよう必須項目が入力されていない場合、項目一つ一つに対してメッセージが表示されるようにしました。
6. 行きたい!!機能
機能概要
いいなと思ったチケットに「行きたい!!」ボタンを押すことができる(ログインユーザのみ可能)
工夫したこと
- Ajaxを使って非同期で実装
- 多くのユーザが行きたい!!と思っている人気のライブやフェスがひと目で分かり、購入のきっかけになる機能は必要だと考え実装しました。
7. コメント機能
機能概要
出品者に聞きたい情報などがあるときにコメントを残すことができる(ログインユーザのみ可能)工夫したこと
- Ajaxを使って非同期で実装
- 出品者に対しての質問や補足情報などをコメントで残せた方がいいと考え実装しました。
その他ポイント
- 他メンバーがジョインした際の開発環境の統一のためDocker / docker-composeを用いて開発 (Dockerという技術に興味があって使ったのは秘密)
- System Specを含めたテスト項目を130個以上書き、保守性を高めた
- SQLインジェクション対策のためエスケープ処理を行うなどセキュリティー対策を行った
- GithubでIssueやプルリク、タグ機能を使ったり、developブランチを作りまとまった機能ができてからmainブランチへマージするなど実務での開発を意識
- Circle CIでpushするたびに自動テストが走り、結果をSlackで通知するよう設定し保守性を高めた
- CloudWatchによりEC2の使用メモリを監視し、監視体制を構築
- mainブランチにmerge時には自動でデプロイまでできるようCI/CDパイプラインを構築
- 実際にアプリを使用してもらい、改善点や不具合、意見などのフィードバックをもらった。ただいま改善中です(汗)
使用技術等
フロントエンド
- HTML、CSS
- Bootstrap 4.3.1
- SCSS
- JavaScript、jQuery、Ajax
- React
バックエンド
- Ruby 2.7.1
- Rails 6.0.1
開発環境
- Docker/Docker-compose
- MySQL2
本番環境
- AWS (EC2、RDS for MySQL、Route53、ELB、S3、CloudFront、CloudWatch)
- Nginx
- Unicorn
- Capictrano
- Circle CI
- インフラ構成図
テスト
- Rspec (単体/結合) 計130以上
- Capybara
- FactoryBot
その他使用技術
- 非同期通信 (コメント、行きたい!!、購入希望など各種ボタン、DM機能等)
- ActionCable
- レスポンシブ対応
- Rubocop
- HTTPS接続
- チーム開発を意識したGitHubの活用 (マイルストーン、イシュー、プルリク、マージ)
- PAY.JPのAPIを使ったクレジットカード決済
- ER図&テーブル定義
最後に
以上でチケットフリマアプリの紹介記事を終わります。
ここまで読んでいただきありがとうございました。
エンジニアの実務経験がほとんどなく至らない点は多々あると思いますが、できるだけ世に出せるサービスを目指し作成しました。何かご意見や質問等あればコメントお待ちしております。参考
・ソースコード https://github.com/shun0211/live_share
・Conefan https://conefan.com
・Twitter https://twitter.com/sakai_1910
- 投稿日:2021-01-08T14:48:05+09:00
form_withで検索機能を実装する
はじめに
Railsのform_withを使って検索したい情報をコントローラーへ送信して、
indexページに一覧表示する機能の実装方法を書いていきます。実現したいこと
今回はPostテーブルから自分が検索したワードを本文に含んだ投稿を
Postコントローラーのindex.html.erbに一覧表示していきたいと思います。[実行環境]
Ruby 2.7.2
Rails 6.0.3.4検索条件の送信
search.html.erb<%= form_with url: posts_path, method: :get, local: true do |f| %> <%= f.label :post_key, '検索' %> <%= f.text_field :post_key %> <%= f.submit, '検索する' %> <% end %>今回はindexページで検索結果一覧を表示するので、urlはindexに対応しているpathを入力します。
表示したいページがindexとは異なる場合には表示したいページに対応したurlを入力してください。methodをgetに指定することでindexに繋がるルーティングを通りindexアクションに
検索したい値を送信することができます。
これを指定しておかないとmethodがpostで送信されてしまいエラーがでます。:post_keyに検索したい値が格納されるので、
コントローラーに記述するワードと共通していれば:post_keyでなくても任意のワードを指定できます。検索結果一覧表示のコントローラー
posts_controller.rbdef index if params[:posts_key] @posts = Post.where(params[:posts_key]) else @posts = Post.all end end入力フォームから送信されてきた:posts_keyがここにたどり着きます。
elseの動作は、なにも入力せず検索ボタンを押した場合すべての投稿が表示されるようになっています。indexページで検索結果を一覧表示
index.html.erb<p>"検索結果: <%= @posts.count %>件</p> <ul class="posts"> <%= @posts.each do |post| %> <li class="post"> <%= post.content %> </li> <% end %> </ul>今回はページネーションを使わずに実装したので、
eachメソッドを使って繰り返し処理を実行して検索結果一覧を表示していきます。countメソッドを使って検索結果の件数を表示しています。
終わりに
以上の手順で検索機能が実装できるかと思います!
もし不備やわからないところがあれば気軽にコメントして
いただけるとありがたいです!
最後まで読んでいただきありがとうございました!
- 投稿日:2021-01-08T14:24:54+09:00
Herokuにデプロイした際、OpenAppをするとエラーになる問題
【第9回】Github&Herokuにデプロイ! Ruby on Railsでコミュニティサービスを作る
https://youtu.be/26MmHYI4xCQこちらの動画を参考にして、「more」ボタンから「Run Console」に進み、
rails db:migrateを実行。
その後、再度アプリを確認したら、無事に開くことができました。
- 投稿日:2021-01-08T14:06:40+09:00
テストコードの効率化
はじめに
オリジナルアプリ制作にあたり、FactoryBotとFakerを用いてテストを行ったので、忘れないように載せておこうと思います。
FactoryBot: あらかじめ各クラスのインスタンスに定める値を設定しておくGem
Faker: メールアドレス、人名、パスワードなど、ランダムな値を生成するGem
前提として、テストファイルuser_spec.rbがすでに生成してあるとする。1.gemの導入と段取り
- FactoryBotとFakerのGemをGemfileのgroup :development, :test do内に記述し、bundle installする。
Gemfilegroup :development, :test do # 中略 gem 'factory_bot_rails' gem 'faker' end
2. specディレクトリ内にFactoryBot用のディレクトリ(①)を作成し、さらにその中にFactoryBot用のファイル(②)を作成する
(例) spec / factories(①) / users.rb(②)2.FactoryBot用ファイル(②)内に記述
1 で作成したファイル内にFactoryBotとFakerを用いてコードを記述する
Fakerの公式GitHub https://github.com/faker-ruby/fakerFactoryBot用ファイル(②)FactoryBot.define do factory :user do name {Faker::Name.name} email {Faker::Internet.free_email} password {Faker::Internet.password(min_length: 8)} birthday { '2000-01-01' } # 中略 end end3.テストコードの記述
FactoryBot.build(:user)と記述し、Userのインスタンスを生成する。
また、beforeを用いて、それぞれのテストコードを実行する前にインスタンスを生成。user_spec.rbrequire 'rails_helper' RSpec.describe User, type: :model do before do @user = FactoryBot.build(:user) end describe 'ユーザー新規登録' do it "nicknameが空だと登録できない" do @user.name = "" @user.valid? expect(@user.errors.full_messages).to include "Name can't be blank" end it "emailが空では登録できない" do @user.email = "" @user.valid? expect(@user.errors.full_messages).to include "Email can't be blank" end # 中略 end end最後に
一度学習した内容でしたが、うろ覚えだったので、復習できてよかったと思います!
- 投稿日:2021-01-08T12:24:39+09:00
公開鍵の確認〜Githubへの鍵登録〜RailsアプリのGithubへのpush
【第9回】Github&Herokuにデプロイ! Ruby on Railsでコミュニティサービスを作る
https://youtu.be/26MmHYI4xCQ4.3 Gitサーバー - SSH 公開鍵の作成
https://git-scm.com/book/ja/v2/Git%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC-SSH-%E5%85%AC%E9%96%8B%E9%8D%B5%E3%81%AE%E4%BD%9C%E6%88%90GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~
https://qiita.com/shizuma/items/2b2f873a0034839e47ceこれらの記事・動画を参考にしました。
覚え書き
【公開鍵の確認方法】
cat ~/.ssh/鍵の名前【鍵の名前確認方法】
cd .sshで移動して、
ls
- 投稿日:2021-01-08T09:28:10+09:00
RSpec+Capybara+selenium+chromedriverでのテスト
主にchromedriverの導入に手こずったので記録しておきます。
Gemfileの設定
Gemfilegroup :test do gem 'rspec-rails' gem 'capybara' gem 'selenium-webdriver' endchromedriverの導入
①
$ brew install chromedriverterminalにて 'brew install chromedriver'を実行
注意点:(PCのrootディレクトリーで実行すること)
※which chromedriverにでinstall先が見れる②最新版にアップデートする
$ brew update chromedriverchromeをヘッドレスモードで起動するために
spec/rails_helper.rbRSpec.configure dp |config| #他の記述 config.before(:each) do |example| if example.metadata[:type] == :system if example.metadata[:js] driven_by :selenium_chrome_headless, screen_size: [1400, 1400] else driven_by :rack_test end end end #capybaraを使うための記述 config.include Capybara::DSL end最後に
こんな記事を書いておいてなんですが、
なぜかわからないがrails_helper.rbに
metadata[:js]にしたらうまく動作しました。どなたかアドバイスをいただければありがたいです。
- 投稿日:2021-01-08T09:06:38+09:00
webpackを使って手動でコンパイルしたjsをrails6で読み込むことに成功
方針
railsでデフォルトで入っているgem webpackerを使わずにwebpackを使って手動でjsをコンパイルする。
entryファイル内でvue.jsを読み込み、componentを使ってアプリの見た目を作っていく。
webpackを使ってbuildされたjsをrailsアプリで読み込み、componentをアプリに読み込む。現状の実装内容
画像の通りindex.html.erbにcomponentで定義したh1要素を表示させることに成功
本日の実装の詳細
※最初にgitignoreが反映されていなかったので以下の手順にて修正
1.gitignoreを編集
2.以下のコマンドでcasheを削除
git rm -r --cached . //ファイル全体キャッシュ削除3.commit & push
いろいろろ設定をいじったらちゃんとwebpackでコンパイルしたjsを読み込めた
結論以下のことを行った
application.rbでassetsのコンパイル対象を変更
config.assets.paths << Rails.root.join("public/javascripts")assets.rbでjsとcssのコンパイル対象を増やす(application.rb書いても良さそう)
Rails.application.config.assets.precompile += %w(*.js *.css) Rails.application.config.assets.precompile << /(^[^_\/]|\/[^_])[^\/]*(\.js|\.css)$/manifest.jsでpublick/javascripts以下のファイルを読み込むようにする
//= link_directory ../../../public/javascripts .js
application.rbに記述しただけではpublic以下のファイルは読み込んでくれなさそう?な感じなので無理やりmanifest.jsで読み込むようにした。他にもやり方はありそうで、例えばwebpackを使ってコンパイルしたファイルをassets/javascritps内にbuildする方法とかもあるようだ。
で、これでrails sをすると…
無事にブラウザ表示できた。
しかし
Failed to mount component: template or render function not definedと出た
ファッ!?
見た感じVue.jsで作ったcomponentが読み込めていなさそう。ちょっと調べてみるか…
こんな記事にヒット
http://howdy.hatenablog.com/entry/2016/11/08/230439
どうやらresolveの設定が必要らしい。
ということでwebpack.config.jsに以下の記述を追加
resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1 } },これで無事解決!!!!componentで設定したHello!を読み込んでくれました!
Sidebar.vue
<template> <h1>Hello!</h1> </template> <script> </script>App.vue
<template> <div> <sidebar></sidebar> <chat-container></chat-container> </div> </template> <script> import Sidebar from './components/Sidebar.vue' import ChatContainer from './components/ChatContainer.vue' export default { components:{ Sidebar, ChatContainer } } </script>main.js
import Vue from 'vue'; import App from './App.vue'; // App.vueをエントリとしてレンダリング new Vue({ el: '#app', render: h => h(App) })index.html.erb
<div id="app"></div>いまいちなぜ解決したのか自分でも理解できていないので整理
When using vue-loader or vueify, templates inside *.vue files are pre-compiled into JavaScript at build time. You don’t really need the compiler in the final bundle, and can therefore use the runtime-only build. Since the runtime-only builds are roughly 30% lighter-weight than their full-build counterparts, you should use it whenever you can. If you still wish to use the full build instead, you need to configure an alias in your bundler:(参考:https://vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only)
この文章を見る限りvue-loaderを使っているときはruntime状態のファイルを使うことができるので完全にcompileしたファイルを使う必要がない。しかしそれでも完全にコンパイルされたファイルを使いたいのであればresolveの設定をする必要があります
と言っているように思う。そして今回私はvue-loaderを使っている。つまりvueファイルの読み込みの仕方が良くないのかもしれない?いまいちよくわからないが読み込めたのでOK。パフォーマンスの良し悪しとかはまた調べてみよう。