- 投稿日:2020-11-25T23:58:54+09:00
ひな祭り[Ruby]
ひな祭り[Ruby]問題
問題
ひな祭りの準備をしましょう。あなたが持っている 10 体の人形を "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" で表します。ひな壇の各段 (計 3 段) に並べる人形の数が与えられるので、各段にならべる人形の記号を改行区切りで出力してください。人形は必ずもとの A, B, C, ... の順番で並べます。
例)
各段にならべる人形の数: 2, 3, 5
→ 人形の並べ方:
AB
CDE
FGHIJ入力される値
入力は以下のフォーマットで与えられます。
h_1 h_2 h_3
ひな壇の 1 段目、2 段目、3 段目に並べる人形の数を表す整数 h_1, h_2, h_3 がこの順に半角スペース区切りで与えられます。入力は 1 行となり、末尾に改行が 1 つ入ります。期待する出力
ひな壇への人形の並べ方を以下の形式で出力してください。
s_1
s_2
s_3
ひな壇の 1 段目、2 段目、3 段目に並べる人形を表す文字列 s_1, s_2, s_3 をこの順に改行区切りで出力してください。s_i (1 ≦ i ≦ 3) は i 段目に並べる人形の記号をアルファベット順に結合した文字列です。
入力例1
2 3 5
出力例1
AB
CDE
FGHIJ入力例2
1 1 8
出力例2
A
B
CDEFGHIJ入力例3
3 4 3
出力例3
ABC
DEFG
HIJアドバイス頂いた結果
ns = gets.split.map(&:to_i) str = "ABCDEFGHIJ" i = 0 ns.each do |n| puts str[i, n] i += n endこのほかにも Array#shift で求めるコードも!
以上!
- 投稿日:2020-11-25T23:35:35+09:00
seedで初期データを生成するコードで使ったメソッド
はじめに
先日、投稿した「初期データをseeds.rbに記述して、ゲストログイン機能のエラーを解決してみた」でseeds.rbに初期データを作成したときに使ったいくつかのメソッドについて、説明する。
基本の書き方
seeds.rbモデル名.メソッド(カラム名: 値)create!
基本は、これかと…
単純に、「作って!」というやつ。
同じidや一意性のバリデーションによってエラーになることもあるので、注意が必要。first_or_create_by!
中学英語みたいだが、
first
とor
とつくので、
「id=1が存在しなければ、作るよ!」みないな意味。
つまり、初期データを作成する、ターミナルでrails db:seed
を行う前に、データが存在すると、seed.rbで作成したデータは保存されない。find_or_create_by!
同じく中学英語みたい。
「テーブルになければ、作るよ!」みたいな意味。おわりに
idのカラムを指定することができるが、その後に、生成するidが、seeds.rbで指定したidからの連番になってしまう。
ex)id=100をseeds.rbで記述していると、その次に情報を保存すると、id=101になる。
- 投稿日:2020-11-25T22:48:20+09:00
Active Storage の導入まとめ
はじめに
・Active Strage の導入を自分なりにまとめたもので、メモのようなものです。
・投稿者は初学者ですので誤った情報を投稿してしまうことがあります。その時はぜひ、遠慮会釈なしにご指摘いただけると幸いです。Active Strageとは
ファイルアップロード機能を手軽に実装できるGemのこと。
元々はインストールが必要だったが、Rails5.2.0以降、初期段階から統合される事となった。このGemを用いると、ファイルアップロードのメソッドを使用できたり、画像を保存するテーブルを簡単に作成できる。Active Strageのお供
Active Strageと一緒に導入すると画像の加工の幅が広がる物を3つ列挙します。
・ImageMagick
・MiniMagick
・ImageProcessingImageMagickはソフトウェアで、MiniMagickとImageProcessingはGemになります。
ImageMagick
コマンドラインから画像に処理を加えることができるツールのこと。
画像の作成やサイズ変更、保存形式の変更などが可能となる。
Homebrewよりインストールする。
MiniMagick
ImageMagickをRubyで扱えるようにしてくれる接合の役割を担うGemのこと。
ImageProcessing
ImageMagickでの画像サイズ機能を拡張してくれるGemのこと。導入の手順
1、ImageMagickをHomebrewからインストールするために、下記のコードをターミナルに入力する。
% brew install imagemagick2、MiniMagickとImageProcessingをインストール。
Gemfile.gem 'mini_magick' gem 'image_processing', '~> 1.2'bundle install
'~> 1.2'
と表記があるが、これは、1.2.0以上、1.3.0未満のバージョンを指定、という意味。3、ローカルサーバーを再起動
rails s4、Active Strageをインストール
導入手順1でrailsアプリの中にActive Strageを入れた。
ここでのインストールはActive Strageを用いるための周辺準備のためのインストール。rails active_storage:installこれにより、Active Strageに関連したマイグレーションファイルが作成される。
続けて、以下のように
migrate
をすることでテーブルが作成される。rails db:migrate余談 ※読み飛ばし可*
導入手順1と4はどちらもインストールであるが、何が違うのかを自分なりに解説。
アパートの引越しで例えるならば、
導入手順1は「仲介業者と賃貸の契約」
導入手順4は「家具や消耗品を部屋へ搬入」である。
仲介業者と契約を結んだだけでは(導入手順1)その部屋で快適には過ごせない。冷蔵庫に電子レンジ、シャンプーにお皿などの生活用具を部屋に運搬して(導入手順2)初めて生活ができる空間となる。
まとめ
Active Strage
を調べていく中で、CarrierWave
という、同じく画像アップロード実装のGemを見つけました。
CarrierWaveは10年ほど前から利用されている古い技術ということで今はActive Strageが主流なのかと思いましたが、意外にもCarrierWaveも現役選手でした。
Active Strage
はシンプルなファイルアップロード。
CarrierWave
はアップロード+細かな設定。
それぞれ別の役割がしっかりと備わっているようなので、CarrierWaveのことも調べて実装したいと思います。
- 投稿日:2020-11-25T21:10:57+09:00
[Rails]オーバーライド(継承)について勉強してみた![初心者]
オーバーライドとは?
親クラス(スーパークラス)で定義されたメソッドについて、小クラス(サブクラス)で同じ名前で再定義すること。
クラスの継承とは?
他のクラスをベースとして新しいクラスを作成することをクラスの継承と言います。
利用したいクラスが複数存在する場合、全てを1から作成していると骨が折れますし、共通する部分を複数のクラスに重複して定義する必要があります。
そこで、あるクラスが既に作成されている場合、そのクラスを拡張することで、新しいクラスを作成することができます。クラスの継承は以下のように記載します。
class 子クラス名 < 親クラス名 end継承の具体例
class Days def study puts "勉強する" end end class Holiday < Days def sleep puts "寝過ごす" end end holiday = Holiday.new holiday.study実行結果勉強する
オーバーライドの具体例
class Days def study puts "勉強する" end end class Holiday < Days def sleep puts "寝過ごす" end def study super puts "休日だろうと勉強しなければ" end end holiday = Holiday.new holiday.study実行結果勉強する 休日だろうと勉強しなければサブクラスにメソッド定義がない場合
サブクラスで定義していないメソッドが呼び出された時は、スーパークラスに同じ名前のメソッドがある場合に限り、スーパークラスのメソッドが呼び出せます。
具体例を以下に載せました。class Days def study puts "勉強する" end end class Holiday < Days end holiday = Holiday.new holiday.study実行結果勉強する
superclassメソッド
全てのクラスは、クラスメソッドとして
superclassメソッド
を持っています。
また、スーパークラスのメソッドを引数なしで呼び出したい時には、super()
も使用できます。
- 投稿日:2020-11-25T21:05:40+09:00
【Ajax】jQueryを使って一覧ページでいいね機能
はじめに
一覧ページでのいいね機能を実装しようとしましたが、一番上の星をいいねしたら全てのチームにいいねがついてしまうという問題が発生しました。この問題が起きたことで大変学習になったので、思考の整理とアウトプットとして記事にまとめさせていただきました。また、いいね機能は多くのアプリで実装することが多いと思います。同じような問題が起きている方や別の実装方法など、ご意見いただけたら幸いです!
概要
まず大前提にテーブルは、
users
,teams
,likes
の3つでlikes
は
users
とteams
の中間テーブルになっています。
AjaxでHTTPメソッドのGET
,POST
,DELETE
ができるように実装していきます。起きている問題・解決したいこと(仮説)
起きている問題と解決したいことを簡単に書いていきたいと思います。
起きている問題
- 全ての記事にいいねがついてしまう。
- 全てのチームのidを取得していたつもりが一番上のチームのidしか取得できていない。(デベロッパーツールで確認)
解決したいことと仮説
- 配列にして取得できていると思ったができていないので配列から一つずつチームを取得できるようにする。
- いいね対象を判断できるようにクラスをつけないといけない。
- クリックした対象に動的idが付与されていれば実装できるのではないか。
Ajaxで実装すること
- いいねの表示
- いいねされたら黄色い星を表示
- いいねされているものをクリックしたら白い星を表示
LikesController
show
,create
,destroy
アクションを使用しています。
Railsでの定義と違うところはリクエストに対して返すのがjson形式のデータということです。LikesControllerclass LikesController < ApplicationController before_action :authenticate_user! before_action :set_like def show like_status = current_user.has_liked?(@team) render json: { hasLiked: like_status } end def create @team.likes.create!(user_id: current_user.id) render json: { status: 'ok' } end def destroy like = @team.likes.find_by!(user_id: current_user.id) like.destroy! render json: { status: 'ok' } end private def set_like @team = Team.find(params[:team_id]) end enduser.rb
has_liked?
はuser.rb
で定義しています。showアクション内の処理のようにcurrent_userがteamをlikeしてる?
と直感的にわかりやすいと思い、定義しております。user.rbdef has_liked?(team) likes.exists?(team_id: team.id) endindex.html.haml
HTTPリクエストを送るにはJavaScriptでidを取得できるようにしなくてはいけないので、テンプレートにカスタムデータを記述しています。
data-<情報>
とすることで任意の属性を付与することができます。今回はチームに対していいねをしたいのでteam
のid
が必要です。
※routes
を確認するとどこにリクエストを送れば良いかよくわかります。たとえばですが、以下
index.html.haml
のように記述してデベロッパーツールで確認すると
data-team-id="1"
id="active-star1"
このようにdata属性とチームのidが入ったidを取得することができます。
チームのidが入ったidは一覧ページでのいいね機能なので、クリックしたときに特定のチームをいいねするという状況になります。それを判定するために記述しております。実際にjQueryのコードを見ていただいたほうが早いと思いますが、簡単に説明させていただきますと、active-star
というid
だけだと、active-star
というidは複数あるので、全てのチームにいいねをつける処理が実行されてしまいます。そのため、動的なid(今回はチームのid)
を付与しております。数字の1
の部分がチームのidです。ここで重要なのは、
- カスタムデータ
- hiddenでいいねの表示を隠していること
hiddenを使っている理由はいいねの状態を確認し表示するということをAjaxで表示するためです。
index.html.haml.hidden.active-star{id: "active-star#{teams.id}", data: {team_id: tams.id}} = image_tag 'star-yellow.png' .hidden.in-active-star{id: "in-active-star#{teams.id}", data: {team_id: teams.id}} = image_tag 'star-white.png'css.hidden { display: none; }jQuery
本題のいいね機能の実装部分です。
記述が冗長になってしまったので、コメントを入れております。重要なのはcsrfToken
をリクエスト時に持たせることです。これをしないと422 (Unprocessable Entity)というエラー
が起きます。なぜかというとGETと違ってPOSTなどの処理はデータベースの変更をするリクエストなので、簡単に操作されては困るため制約がついています。そのため、rails-ujs
を使ってaxios
でリクエスト時にcsrfToken
というのを持たせるようにしております。jQueryimport $ from 'jquery' import axios from 'axios' import { csrfToken } from 'rails-ujs' // リクエスト時にCSRFトークンを持たせる axios.defaults.headers.common['X-CSRF-Token'] = csrfToken() document.addEventListener('DOMContentLoaded', () => { // ロード時にいいねされていない星を配列で取得 $('.in-active-star').each(function (index, element) { // テンプレートで記述したカスタムデータを取得 let likeData = $(element).data() // カスタムデータからチームIDを取得 let getId = likeData.teamId // カスタムデータを入れてGETリクエストを送る axios.get(`/teams/${getId}/like`) // リクエストを送ったらレスポンスが返ってくる .then((response) => { // responseでrenderされたlikeの状態を取得(true or false) const inActiveStatus = response.data.hasLiked // falseであればいいねされていない => 白い星を表示するために、'hidden'を取り外す if ( inActiveStatus === false ) { $(element).removeClass('hidden') } }) }) // ロード時にいいねされている星を配列で取得 $('.active-star').each(function (index, element) { // テンプレートで記述したカスタムデータを取得 let likeData = $(element).data() // カスタムデータからチームIDを取得 let getId = likeData.teamId // カスタムデータを入れてGETリクエストを送る axios.get(`/teams/${getId}/like`) // リクエストを送ったらレスポンスが返ってくる .then((response) => { // responseでrenderされたlikeの状態を取得(true or false) const activeStatus = response.data.hasLiked // trueであればいいねされている => 黄色い星を表示するために、'hidden'を取り外す if ( activeStatus === true) { $(element).removeClass('hidden') } }) }) // #create いいねをつけたいときの処理 $('.in-active-star').on('click', (e) => { e.preventDefault(); let dataset = $(e.currentTarget).data() // クリックした要素のidを取得 let teamId = dataset.teamId // teamIdを使いPOSTリクエストを送る axios.post(`/teams/${teamId}/like`) .then((response) => { // リクエスト成功なら処理を行う if (response.data.status === 'ok') { $(`#in-active-star${teamId}`).addClass('hidden'); $(`#active-star${teamId}`).removeClass('hidden'); } }) // エラー時の処理 .catch((e) => { window.alert('Error') console.log(e) }) }) // #destroy いいねを外したいときの処理 $('.active-star').on('click', (e) => { e.preventDefault(); let dataset = $(e.currentTarget).data() // クリックした要素のidを取得 let teamId = dataset.teamId // teamIdを使いdeleteメソッドを使う axios.delete(`/teams/${teamId}/like`) .then((response) => { // リクエスト成功なら処理を行う if (response.data.status === 'ok') { $(`#active-star${teamId}`).addClass('hidden'); $(`#in-active-star${teamId}`).removeClass('hidden'); } }) // エラー時の処理 .catch((e) => { window.alert('Error') console.log(e) }) }) })まとめ
- Ajax処理はデベロッパーツールを使い
debugger
やconsole.log()
を使い値が取れているか確認をしっかりすると開発が捗る。- 詳細ページのように一つしかいいねがないときは意識していなかったが、一覧ページなど複数ある中の一つというように特定したいときは個別のidを付与することで実装できる。
- POSTやDELETE時にはデータベースの操作をすることからCSRFトークンというものが必要になる。
最後に
今回はjQueryを使って実装しました。Vue.jsの学習も始めたので、生JSやjQueryをもっと理解したら開発に使っていきたいなと思っています。このように実装するのもひとつの方法だと思いますが、他にもたくさんの実装方法があると思います。他のライブラリやフレームワークではどのように実装するのか、また一度書いたコードもリファクタリングを積極的にやっていけたらと思います!
参考文献
- 投稿日:2020-11-25T21:05:40+09:00
【Rails】いいね機能をjQueryで実装
はじめに
一覧ページでのいいね機能を実装しようとしましたが、一番上の星をいいねしたら全てのチームにいいねがついてしまうという問題が発生しました。この問題が起きたことで大変学習になったので、思考の整理とアウトプットとして記事にまとめさせていただきました。また、いいね機能は多くのアプリで実装することが多いと思います。同じような問題が起きている方や別の実装方法など、ご意見いただけたら幸いです!
概要
まず大前提にテーブルは、
users
,teams
,likes
の3つでlikes
は
users
とteams
の中間テーブルになっています。
AjaxでHTTPメソッドのGET
,POST
,DELETE
ができるように実装していきます。起きている問題・解決したいこと(仮説)
起きている問題と解決したいことを簡単に書いていきたいと思います。
起きている問題
- 全ての記事にいいねがついてしまう。
- 全てのチームのidを取得していたつもりが一番上のチームのidしか取得できていない。(デベロッパーツールで確認)
解決したいことと仮説
- 配列にして取得できていると思ったができていないので配列から一つずつチームを取得できるようにする。
- いいね対象を判断できるようにクラスをつけないといけない。
- クリックした対象に動的idが付与されていれば実装できるのではないか。
Ajaxで実装すること
- いいねの表示
- いいねされたら黄色い星を表示
- いいねされているものをクリックしたら白い星を表示
LikesController
show
,create
,destroy
アクションを使用しています。
Railsでの定義と違うところはリクエストに対して返すのがjson形式のデータということです。LikesControllerclass LikesController < ApplicationController before_action :authenticate_user! before_action :set_like def show like_status = current_user.has_liked?(@team) render json: { hasLiked: like_status } end def create @team.likes.create!(user_id: current_user.id) render json: { status: 'ok' } end def destroy like = @team.likes.find_by!(user_id: current_user.id) like.destroy! render json: { status: 'ok' } end private def set_like @team = Team.find(params[:team_id]) end enduser.rb
has_liked?
はuser.rb
で定義しています。showアクション内の処理のようにcurrent_userがteamをlikeしてる?
と直感的にわかりやすいと思い、定義しております。user.rbdef has_liked?(team) likes.exists?(team_id: team.id) endindex.html.haml
HTTPリクエストを送るにはJavaScriptでidを取得できるようにしなくてはいけないので、テンプレートにカスタムデータを記述しています。
data-<情報>
とすることで任意の属性を付与することができます。今回はチームに対していいねをしたいのでteam
のid
が必要です。
※routes
を確認するとどこにリクエストを送れば良いかよくわかります。たとえばですが、以下
index.html.haml
のように記述してデベロッパーツールで確認すると
data-team-id="1"
id="active-star1"
このようにdata属性とチームのidが入ったidを取得することができます。
チームのidが入ったidは一覧ページでのいいね機能なので、クリックしたときに特定のチームをいいねするという状況になります。それを判定するために記述しております。実際にjQueryのコードを見ていただいたほうが早いと思いますが、簡単に説明させていただきますと、active-star
というid
だけだと、active-star
というidは複数あるので、全てのチームにいいねをつける処理が実行されてしまいます。そのため、動的なid(今回はチームのid)
を付与しております。数字の1
の部分がチームのidです。ここで重要なのは、
- カスタムデータ
- hiddenでいいねの表示を隠していること
hiddenを使っている理由はいいねの状態を確認し表示するということをAjaxで表示するためです。
index.html.haml.hidden.active-star{id: "active-star#{teams.id}", data: {team_id: tams.id}} = image_tag 'star-yellow.png' .hidden.in-active-star{id: "in-active-star#{teams.id}", data: {team_id: teams.id}} = image_tag 'star-white.png'css.hidden { display: none; }jQuery
本題のいいね機能の実装部分です。
記述が冗長になってしまったので、コメントを入れております。重要なのはcsrfToken
をリクエスト時に持たせることです。これをしないと422 (Unprocessable Entity)というエラー
が起きます。なぜかというとGETと違ってPOSTなどの処理はデータベースの変更をするリクエストなので、簡単に操作されては困るため制約がついています。そのため、rails-ujs
を使ってaxios
でリクエスト時にcsrfToken
というのを持たせるようにしております。jQueryimport $ from 'jquery' import axios from 'axios' import { csrfToken } from 'rails-ujs' // リクエスト時にCSRFトークンを持たせる axios.defaults.headers.common['X-CSRF-Token'] = csrfToken() document.addEventListener('DOMContentLoaded', () => { // ロード時にいいねされていない星を配列で取得 $('.in-active-star').each(function (index, element) { // テンプレートで記述したカスタムデータを取得 let likeData = $(element).data() // カスタムデータからチームIDを取得 let getId = likeData.teamId // カスタムデータを入れてGETリクエストを送る axios.get(`/teams/${getId}/like`) // リクエストを送ったらレスポンスが返ってくる .then((response) => { // responseでrenderされたlikeの状態を取得(true or false) const inActiveStatus = response.data.hasLiked // falseであればいいねされていない => 白い星を表示するために、'hidden'を取り外す if ( inActiveStatus === false ) { $(element).removeClass('hidden') } }) }) // ロード時にいいねされている星を配列で取得 $('.active-star').each(function (index, element) { // テンプレートで記述したカスタムデータを取得 let likeData = $(element).data() // カスタムデータからチームIDを取得 let getId = likeData.teamId // カスタムデータを入れてGETリクエストを送る axios.get(`/teams/${getId}/like`) // リクエストを送ったらレスポンスが返ってくる .then((response) => { // responseでrenderされたlikeの状態を取得(true or false) const activeStatus = response.data.hasLiked // trueであればいいねされている => 黄色い星を表示するために、'hidden'を取り外す if ( activeStatus === true) { $(element).removeClass('hidden') } }) }) // #create いいねをつけたいときの処理 $('.in-active-star').on('click', (e) => { e.preventDefault(); let dataset = $(e.currentTarget).data() // クリックした要素のidを取得 let teamId = dataset.teamId // teamIdを使いPOSTリクエストを送る axios.post(`/teams/${teamId}/like`) .then((response) => { // リクエスト成功なら処理を行う if (response.data.status === 'ok') { $(`#in-active-star${teamId}`).addClass('hidden'); $(`#active-star${teamId}`).removeClass('hidden'); } }) // エラー時の処理 .catch((e) => { window.alert('Error') console.log(e) }) }) // #destroy いいねを外したいときの処理 $('.active-star').on('click', (e) => { e.preventDefault(); let dataset = $(e.currentTarget).data() // クリックした要素のidを取得 let teamId = dataset.teamId // teamIdを使いdeleteメソッドを使う axios.delete(`/teams/${teamId}/like`) .then((response) => { // リクエスト成功なら処理を行う if (response.data.status === 'ok') { $(`#active-star${teamId}`).addClass('hidden'); $(`#in-active-star${teamId}`).removeClass('hidden'); } }) // エラー時の処理 .catch((e) => { window.alert('Error') console.log(e) }) }) })まとめ
- Ajax処理はデベロッパーツールを使い
debugger
やconsole.log()
を使い値が取れているか確認をしっかりすると開発が捗る。- 詳細ページのように一つしかいいねがないときは意識していなかったが、一覧ページなど複数ある中の一つというように特定したいときは個別のidを付与することで実装できる。
- POSTやDELETE時にはデータベースの操作をすることからCSRFトークンというものが必要になる。
最後に
今回はjQueryを使って実装しました。Vue.jsの学習も始めたので、生JSやjQueryをもっと理解したら開発に使っていきたいなと思っています。このように実装するのもひとつの方法だと思いますが、他にもたくさんの実装方法があると思います。他のライブラリやフレームワークではどのように実装するのか、また一度書いたコードもリファクタリングを積極的にやっていけたらと思います!
参考文献
- 投稿日:2020-11-25T20:36:10+09:00
Rails 6用のJavaScriptをRails 5.2で使う
RailsアプリケーションでRailsをバージョンアップするときには、段階的に修正しては本番サーバーを更新、ということをやっています。
- JavaScriptの修正、パッケージの更新。
- 新しいRailsでwarningが出るところを修正。
- 最後にRailsをバージョンアップ。
といった感じです。ここでは、Rails 5.2を6.0に上げる準備として、Rails 6.0で導入されたJavaScriptのパッケージを5.2アプリケーションで使ってみます。
コピペ元の6.0アプリケーションを作っておく
5.2と6.0のコードを比較したりコピペ元として使ったりするために、6.0アプリケーションを作成します。
% rails --version Rails 6.0.3.4 % rails new foo --webpack=vueJavaScriptパッケージの導入
Railsがデフォルトで使うJavaScriptは、6.0からはassetsからWebpackerに移されました。次の4つです。
- @rails/actioncable
- @rails/activestorage
- @rails/ujs
- turbolinks
5.2アプリケーションで、yarn add でパッケージを追加します。
% yarn add @rails/actioncable @rails/activestorage @rails/ujs turbolinks6.0アプリケーションの app/javascript/packs/application.js から requireの 行を5.2アプリケーションの application.js にコピーします。使わないものはコピーしなくてもよいです。
app/javascript/packs/application.jsrequire("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels")ActionCable を使う場合は、6.0アプリケーションの app/javascript/channels 下から consumer.js と index.js もコピーします。
assetsのJavaScriptを削除
5.2アプリケーションで、assets の application.js からWebpackerに移すものを削除します。
app/assets/javascripts/application.js- //= require rails-ujs - //= require activestorage - //= require turbolinks
ActionCableも移す場合は、各チャンネル用の xxx.coffee ファイルをバックアップしておいてから、channelsディレクトリを削除します。
assetsのJavaScriptを全部削除する場合
5.2アプリケーションで、JavaScriptをすべてWebpackerに移す場合は、app/assets/javascripts ディレクトリを削除します。
また、manifest.js からJavaScriptの設定を消します。
app/assets/config/manifest.js- //= link_directory ../javascripts .js
Gemfile から uglifier と coffee-rails を削除して bundle install します。さようならCoffeeScript。
Gemfile- gem 'uglifier', '>= 1.3.0' - gem 'coffee-rails', '~> 4.2'
produciton.rb から uglifier の設定を消します。
config/environments/production.rb# Compress JavaScripts and CSS. # config.assets.js_compressor = :uglifierActionCableを移す場合
ActionCable用のJavaScriptを移す場合は、6.0アプリケーションで、rails g を使ってチャンネル用のJavaScriptのひな形を作ります。次の例では、5.2アプリケーションにuserチャンネルがあるものとします。
% bin/rails g channel user6.0アプリケーションの app/javascript/channels から user_channel.js をコピーして5.2に移します。あとは、保存しておいた user.coffee のコードを user_channel.js で動くように移植します。
動作確認
このやり方で、ActionCable用のJavaScriptが動くことは確認しました。Turbolinksとrails-ujsも、まあたぶん大丈夫でしょう。
ActiveStorage用のJavaScriptについては確認していません。すいません。
- 投稿日:2020-11-25T18:43:16+09:00
Rails API × GraphQL プロジェクトを作成する際にハマったこと
概要
以下の記事を参考にプロジェクトを作ろうとしたところ私のmac環境で次のような問題でハマったためまとめます。
環境
- macOS Mojave v10.14.6
- Rails 5.0.7.2
make: *** [mysql2.bundle] Error 1
これはGraphQL関係なく、mysql2の
$ bundle install
エラーでした。$ bundle install ... Fetching mysql2 0.5.3 Installing mysql2 0.5.3 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. current directory: /Users/gremito/.gem/gems/mysql2-0.5.3/ext/mysql2 /Users/gremito/.rbenv/versions/2.7.1/bin/ruby -I /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0 -r ./siteconf20201125-31525-3obuae.rb extconf.rb checking for rb_absint_size()... yes checking for rb_absint_singlebit_p()... yes checking for rb_wait_for_single_fd()... yes ----- Using mysql_config at /usr/local/opt/mysql@5.7/bin/mysql_config ----- checking for mysql.h... yes checking for errmsg.h... yes checking for SSL_MODE_DISABLED in mysql.h... yes checking for SSL_MODE_PREFERRED in mysql.h... yes checking for SSL_MODE_REQUIRED in mysql.h... yes checking for SSL_MODE_VERIFY_CA in mysql.h... yes checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes checking for MYSQL.net.vio in mysql.h... yes checking for MYSQL.net.pvio in mysql.h... no checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes checking for my_bool in mysql.h... yes ----- Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load ----- ----- Setting libpath to /usr/local/opt/mysql@5.7/lib ----- creating Makefile current directory: /Users/gremito/.gem/gems/mysql2-0.5.3/ext/mysql2 make "DESTDIR=" clean current directory: /Users/gremito/.gem/gems/mysql2-0.5.3/ext/mysql2 make "DESTDIR=" compiling client.c client.c:787:14: warning: incompatible pointer types passing 'VALUE (void *)' (aka 'unsigned long (void *)') to parameter of type 'VALUE (*)(VALUE)' (aka 'unsigned long (*)(unsigned long)') [-Wincompatible-pointer-types] rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0); ^~~~~~~~~~~~~ /Users/gremito/.rbenv/versions/2.7.1/include/ruby-2.7.0/ruby/ruby.h:1988:25: note: passing argument to parameter here VALUE rb_rescue2(VALUE(*)(VALUE),VALUE,VALUE(*)(VALUE,VALUE),VALUE,...); ^ client.c:795:16: warning: incompatible pointer types passing 'VALUE (void *)' (aka 'unsigned long (void *)') to parameter of type 'VALUE (*)(VALUE)' (aka 'unsigned long (*)(unsigned long)') [-Wincompatible-pointer-types] rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0); ^~~~~~~~ /Users/gremito/.rbenv/versions/2.7.1/include/ruby-2.7.0/ruby/ruby.h:1988:25: note: passing argument to parameter here VALUE rb_rescue2(VALUE(*)(VALUE),VALUE,VALUE(*)(VALUE,VALUE),VALUE,...); ^ 2 warnings generated. compiling infile.c compiling mysql2_ext.c compiling result.c compiling statement.c linking shared-object mysql2/mysql2.bundle ld: library not found for -lssl clang: error: linker command failed with exit code 1 (use -v to see invocation) make: *** [mysql2.bundle] Error 1 make failed, exit code 2 Gem files will remain installed in /Users/gremito/.gem/gems/mysql2-0.5.3 for inspection. Results logged to /Users/gremito/.gem/extensions/x86_64-darwin-18/2.7.0/mysql2-0.5.3/gem_make.out An error occurred while installing mysql2 (0.5.3), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.3' --source 'https://rubygems.org/'` succeeds before bundling. In Gemfile: mysql2解決方法
以下のコマンドを実行した後に再度
$ bundle install
すると正常にインストールできました。
原因は、以下の参考にした記事で解説されてあります。$ sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target / Password: installer: Package name is macOS_SDK_headers_for_macOS_10.14 installer: Installing at base path / installer: The install was successful.
Sprockets::Railtie::ManifestNeededError
標準ライブラリの
sprockets
によって起きたエラーでした。$ rails generate graphql:install ... But did not, please create this file and use it to link any assets that need to be rendered by your app: Example: //= link_tree ../images //= link_directory ../javascripts .js //= link_directory ../stylesheets .css and restart your server For more information see: https://github.com/rails/sprockets/blob/070fc01947c111d35bb4c836e9bb71962a8e0595/UPGRADING.md#manifestjs (Sprockets::Railtie::ManifestNeededError) from /Users/gremito/.gem/gems/railties-5.0.7.2/lib/rails/initializable.rb:30:in `instance_exec' from /Users/gremito/.gem/gems/railties-5.0.7.2/lib/rails/initializable.rb:30:in `run' from /Users/gremito/.gem/gems/railties-5.0.7.2/lib/rails/initializable.rb:55:in `block in run_initializers' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/tsort.rb:228:in `block in tsort_each' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/tsort.rb:431:in `each_strongly_connected_component_from' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/tsort.rb:349:in `block in each_strongly_connected_component' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/tsort.rb:347:in `each' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/tsort.rb:347:in `call' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/tsort.rb:347:in `each_strongly_connected_component' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/tsort.rb:226:in `tsort_each' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/tsort.rb:205:in `tsort_each' from /Users/gremito/.gem/gems/railties-5.0.7.2/lib/rails/initializable.rb:54:in `run_initializers' from /Users/gremito/.gem/gems/railties-5.0.7.2/lib/rails/application.rb:352:in `initialize!' from /Users/gremito/rails/graphql-sample/config/environment.rb:5:in `<top (required)>' from /Users/gremito/.gem/gems/spring-2.1.1/lib/spring/application.rb:106:in `require' from /Users/gremito/.gem/gems/spring-2.1.1/lib/spring/application.rb:106:in `preload' from /Users/gremito/.gem/gems/spring-2.1.1/lib/spring/application.rb:157:in `serve' from /Users/gremito/.gem/gems/spring-2.1.1/lib/spring/application.rb:145:in `block in run' from /Users/gremito/.gem/gems/spring-2.1.1/lib/spring/application.rb:139:in `loop' from /Users/gremito/.gem/gems/spring-2.1.1/lib/spring/application.rb:139:in `run' from /Users/gremito/.gem/gems/spring-2.1.1/lib/spring/application/boot.rb:19:in `<top (required)>' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:72:in `require' from /Users/gremito/.rbenv/versions/2.7.1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:72:in `require' from -e:1:in `<main>'解決方法
バージョンを落とすしか方法がありませんでした・・・
- `manifest.js` and dynamic assets to link #654
- [Rails6][sprockets4.0.0]Sprocketsを無効にするためにapp/assetsフォルダ以下を削除すると、Sprockets::Railtie::ManifestNeededErrorが出た - @sasakura_870
$ bundle exec gem uninstall sprockets ... Select gem to uninstall: 1. sprockets-3.7.1 2. sprockets-3.7.2 3. sprockets-4.0.2 4. All versions > 4 Successfully uninstalled sprockets-3.7.1 Successfully uninstalled sprockets-3.7.2 NOTE: Gem::Specification#rubyforge_project= is deprecated with no replacement. It will be removed on or after 2019-12-01. Gem::Specification#rubyforge_project= called from /Users/gremito/.gem/specifications/erubis-2.7.0.gemspec:17. NOTE: Gem::Specification#rubyforge_project= is deprecated with no replacement. It will be removed on or after 2019-12-01. Gem::Specification#rubyforge_project= called from /Users/gremito/.gem/specifications/rack-test-0.6.3.gemspec:17. You have requested to uninstall the gem: sprockets-4.0.2 sprockets-rails-3.2.2 depends on sprockets (>= 3.0.0) If you remove this gem, these dependencies will not be met. Continue with Uninstall? [yN] y Remove executables: sprockets in addition to the gem? [Yn] y Removing sprockets Successfully uninstalled sprockets-4.0.2 $ rm -fr Gemfile.lock
Gemfile
にgem 'sprockets', '< 4'
を追記して再度
$ bundle install
して正常にダウングレードできたら$ rails generate graphql:install
を再度実行して終了。
- 投稿日:2020-11-25T18:39:32+09:00
ActiveRecordでは present? の代わりに exists? を使おう
小ネタです。
条件に該当するレコードが存在するかを確認したい
ActiveRecordで条件に存在するレコードが存在するかを確認するのに、つい、
Model.where(conditions).present?と書いてしまいがちですが、これはパフォーマンス上の問題が生じる可能性があります。
.present?
は ActiveSupport によってモンキーパッチされたメソッドです。
Mode.where(conditions)
のようなActiveRecord_Relation
クラスのオブジェクトに.present?
を適用するとどうなるか?すると条件に該当するレコードを全てDBから取得して、(Rails上のモデルの)配列として評価することになります。
配列に要素が存在すればtrue
、 しなければfalse
ですね。なぜこれはダメなのか
一見すると問題なさそうですし、実際結果自体は正しいのですが、条件に該当するレコードが1つでも存在することを確認できれば良いわけで、全件を取得する必要はありません。
100万レコードが条件に該当した場合、その100万レコードがDBからピックアップされ、その100万レコード分のデータがRails側に転送され、100万のモデルが作成された上で捨てられる。という壮大な無駄が発生します。結果、いつまでたっても結果が返ってこない。というパフォーマンス上の障害を引き起こしてしまうのです。こういうのはレコード数の少ない開発環境では顕在化しずらく、レコード数の多い本番環境でいきなり顕在化してパニックになることがあります。
では、どうする?
.exists?
メソッドを使いましょう。Model.where(conditions).exists?こちらを使うと、SQLが
exists
式を使うものに代わります。
exists
式は条件に該当するレコードが1件でも存在すればDBがそこで探索を打ち切ってくれるので、無駄に全件を取ってくることはありません。
DBレベルでtrue
またはfalse
で返してくれるのでオーバヘッドが最小で済みます。present? の逆は?
.present?
の逆である.blank?
に対応するメソッドは.empty?
になります。
.empty?
を使えばSQL上はexists
式を使います。ただ、条件に該当するレコードが存在しないことを確認するには、結局全件のチェックが必要になるので両者に速度的な差は少ないかもしれません。
- 投稿日:2020-11-25T18:39:16+09:00
TableExportで好きな位置にExcelやCSV出力ボタンを配置する
はじめに
JQueryプラグイン「TableExport」ではテーブルを簡単にExcel、CSV、テキストファイルなどで出力できます。ただ、出力ボタンの位置は上下で固定っぽくて不便だったので、改善策を備忘録として残しておきます。
TableExport自体の使い方はこちらのサイトが分かりやすいので参考にされてください。
開発環境は以下の通り
- Ruby 2.6.3
- Rails 5.2.4.4
- jQuery 1.12.4
(Ruby on Rails使用してますが、今回の記事ではjQueryのみ取り上げているのであまり関係ないかと思います)
なお、bootstrapを使えばデフォルトでオシャレなボタンにできますが今回は使いません。
問題点
Export to xlsxボタンがとんでもないところにありますね。
普通のテーブルだともう少しマシかもしれませんが、このテーブルは見出し固定のスクロール式で、テーブルを横にスライドするとボタンも一緒に流されてしまうので余計見にくい代物になってしまいます。。
一応オプションでボタンの位置を指定できますが、前述した通り上下の指定しかできないので改善には至りませんでした。$(".table").tableExport({ ... position: "bottom", ... });解決策
至ってシンプルです。
①tableExport()で生成されるボタンを隠して代理ボタンを配置する
②代理ボタンが押されたらtrigger()でもともとのボタンが押されたことにするhtmlファイル... <table id="export-table"> ... </table> <button type="button" id="export-btn">Excel出力</button> ...jqueryファイル... $('#export-table').tableExport({ formats: ["xlsx"], bootstrap: false }); $('#export-table caption').hide(); $('#export-btn').on('click', function(){ $('.table caption button').trigger('click'); }); ...うまくいきました!
もちろんCSVやテキストファイルの出力にも使用できます。おわりに
TableExportはとても簡単に外部ファイルを出力できるプラグインですが、扱っている記事自体がとても少ないので、こういうちょっとした解決策っていうのはもっと増えていくと嬉しいですね。
- 投稿日:2020-11-25T18:26:41+09:00
RailsでTimecop/TimeHelpersを使って時刻を変える方法について
この記事は、Happy Elements Advent Calendar 2020の5日目です。
RailsでTimecop/TimeHelpersを使って時刻を変える方法についての記事です。はじめに
ソーシャルゲームでは、イベントが yyyy年mm月dd日に始まるなど、特定の時刻になると発動することが定番です。
これらのようなイベントの動作を確認するために、サーバ側で時刻を変えることができると、動作テストがしやすくなります。
- 例1 12月2日 15:00開始のガチャAの動作確認をするために、サーバの時間を12/2 16時にセットする
- 例2 バグ報告があったので、1ヶ月前のイベントの時間にセットしてバグ調査をする などサーバ側で時刻を変える方法について、以下の3つの方法に絞って調査しました。
- Timecop
- ActiveSupport::Testing::TimeHelpers
- libfaketimeこの記事では、それぞれがどのようなものか、をまとめました。
Timecop
https://github.com/travisjeffery/timecop
こちらはRailsのgemです。
Time/Date/DateTimeクラスのオーバーライドとして実装されているものでした
https://github.com/travisjeffery/timecop/blob/master/lib/timecop/time_extensions.rb調査を進めると、TimeHelpersという物がRailsに組み込まれていることがわかりました。
TimeHelpersは、Timecopより機能としては少なく、時刻を固定させるのみです。
Timecopは、固定の他に、過去や未来に設定した後に時刻が進むなども可能です。TimecopとTimeHelperでは、Timecopの方がリッチな機能である、と言えると思います。
- 標準のTimeHelpersで十分である
- gemを入れたくない
などの場合は、TimeHelpersの利用が選択肢に上がってくると思います。
2020年現在もメンテナンスはされているようです。ActiveSupport::Testing::TimeHelpers
https://edgeapi.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html
Rails4.1以降で標準のものです。
時刻を固定・解除するのみです。libfaketime
https://github.com/wolfcw/libfaketime
こちらはRailsの話ではなく、OSにインストールして動くサービスです。
システムコールを改変するものです
- メリット
- プログラムを変えなくていい
- デメリット
- 導入が少し手間
システムコールとは?
libfaketimeでシステムコールの話が出てきたので、ここではシステムコールについて。
strace というコマンドを使って、コマンドを打つと大量にログが出てきます。これがシステムコールです(fstat, close, read, ...etc)。
libfaketimeは、このシステムコールを書き換えて時刻を変えるので、TimecopやTimeHelpersよりも深い部分で動いていると言えると思います。strace date execve("/bin/date", ["date"], 0x7ffc3bc97870 /* 28 vars */) = 0 brk(NULL) = 0x55b92bdc6000 openat(AT_FDCWD, "/usr/local/lib/faketime/libfaketime.so.1", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0$\0\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0644, st_size=66512, ...}) = 0 ... read(4, "@2020-6-16 20:30:00\n", 4096) = 20 close(4) = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0 write(1, "Tue Jun 16 20:30:00 JST 2020\n", 29Tue Jun 16 20:30:00 JST 2020 ) = 29 close(1) = 0 close(2) = 0 munmap(0x7f46f7935000, 8) = 0 munmap(0x7f46f7936000, 32) = 0 exit_group(0) = ? +++ exited with 0 +++余談
調査中に、もしTimecopとlibfaketimeを同時に使った場合どうなるのだろう、と思いました。
実験してみたところ、Timecopで設定した時刻がRailsでは得られました。libfaketimeで改変された時間を、さらにTimecopが上書きするという順序になります。
イメージ図は以下の通りです。まとめ
自分が運営中のタイトルでは、開発者の手元では、TimeHelpersを使ってRailsの時刻を変えながらイベントの動作確認などに利用しています。libfaketimeも使えるようになっており、必要であればそちらも利用しています。
こちらの記事では、サーバ側で時刻を変える方法について、Timecop, TimeHelpers, libfaketimeを調査しました。
調査してわかりましたが、どれが優れているというものではありませんでした。
それぞれ、やりたいことの要件に合わせて選択すれば良いと思いました。
もしRailsで時刻を変えたいけど、どうしようと悩んで調べている方がいらっしゃいましたら、
参考にしていただけると幸いです。(注) 記事の中の図は自分で用意しました
参考
https://andycroll.com/ruby/replace-timecop-with-rails-time-helpers-in-rspec/
https://techracho.bpsinc.jp/penguin10/2018_12_25/67780
https://qiita.com/ktrkmk/items/b1361dd43d22dcf5627e
https://qiita.com/Targityen/items/67682d6c80cdcbe1186c終わりに
Happy Elements株式会社 カカリアスタジオでは、
いっしょに【熱狂的に愛されるコンテンツ】をつくっていただけるメンバーを大募集中です!もし弊社にご興味持っていただけましたら、是非一度
下記採用サイトをご覧ください。
https://recruit.happyelements.co.jp/
- 投稿日:2020-11-25T17:49:55+09:00
Railsでタグ機能を実装
はじめに
現在作成しているアプリでタグ機能を実装したのでその実装方法を残しておきます。
Railsにはタグ機能の実装を簡単にしてくれる acts-as-taggable-on というgemがありますが、関連付けの練習も踏まえて自前で実装します。
現在作っているアプリのPK,FKのみを示したER図は以下のようになります。
実行環境
この記事は以下の環境で動作確認しています。
ruby 2.7.1
rails 6.0.3
DB MySQLモデルの作成
Profileモデルは既に作ってあるという前提で進めていきます。
まず、tagモデルとtag_relastionshipモデルを作成します。$ rails g model tag name:string $ rails g model tag_relationship profile:references tag:references複合キーインデックスを張ります。
こうすることにより、同じタグを二回保存できないようにします。XXXXXXXXXXXXXX_create_tag_relationships.rbclass CreateTagRelationships < ActiveRecord::Migration[6.0] def change create_table :tag_relationships do |t| t.references :profile, foreign_key: true t.references :tag, foreign_key: true t.timestamps end add_index :tag_relationships, [:profile_id, :tag_id], unique: true end endタグ名は必ず入力して欲しいので
null:false
にします。XXXXXXXXXXXXXX_create_tags.rbclass CreateTags < ActiveRecord::Migration[6.0] def change create_table :tags do |t| t.string :name, null: false t.timestamps end end endモデルの関連付けとバリデーション
基本的な中間テーブルを用いる多対多の実装です。
中間デーブル経由でタグに紐付くprofileの情報を取得できるように has_many throughも定義します。これに関してはprofileモデルでも同じです。
タグ名はユニークで必ず保持していて欲しいので以下のようなバリデーションにします。tag.rbclass Tag < ApplicationRecord has_many :tag_relationships, dependent: :destroy has_many :profiles, through: :tag_relationships validates :name, uniqueness: true, presence: true endtag_relationship.rbclass TagRelationship < ApplicationRecord belongs_to :profile belongs_to :tag validates :tag_id, presence: true validates :profile_id, presence: true endprofile.rbclass Profile < ApplicationRecord belongs_to :user has_many :tag_relationships, dependent: :destroy has_many :tags, through: :tag_relationships endviewの作成
現在作成しているアプリではプロフィールの新規登録の際にタグも登録して欲しいので
profiles/new.html.erb
で実装します。
タグの部分だけ抜粋して載せます。
f,text_field :tag
とすることでparams[:profile][:tag]でパラメーターを受け取れるようにします。profiles/new.html.erb<div class="input-field col s12"> <i class="material-icons prefix">local_offer</i> <%= f.text_field :tag, placeholder: "タグを複数つけるには' , 'で区切ってください" %> </div>表示する際はeachで配列で保存されているタグを繰り返し表示します。
profiles/show.html.erb<% @user_profile.tags.each do |tag| %> <div class="chip"> <%= tag.name %> <i class="close material-icons">close</i> </div> <% end %>コントローラーの作成
ユーザーとプロフィールは
has_one
を用いて一対一の関係にしているので、buildする際は「インスタンス名.build_アソシエーション名」としています。プロフィール情報と一緒に送られてきたタグを保存できるようにします。
profiles_controller.rbdef new @user_profile = current_user.build_profile end def create @user_profile = current_user.build_profile(profile_params) # profile_paramsはストロングパラメーター tag_list = params[:profile][:tag].split(',') # viewでカンマ区切りで入力してもらうことで、入力された値をsplit(',')で配列にしている。 if @user_profile.save @user_profile.save_tags(tag_list) # save_tagsというクラスメソッドを使って保存している。 flash[:notice] = "プロフィールの設定が完了しました" redirect_to root_url else render 'new' end endsave_tagsメソッドは下記に示します。
profile.rbdef save_tags(profile_tag) profile_tag.each do |tag| new_tag = Tag.find_or_create_by(name: tag) self.tags << new_tag end end「find_or_create_by」メソッドは引数で指定した値があればそれを取得し、なければ作成します。名前の通り、findかcreateするメソッドです。
self.tags << profile_tag ではプロフィールに関連したタグの配列に新たなタグを追加しています。
<< だけでなく、pushメソッドを使っても同じように要素を追加することができます。タグの編集機能
プロフィールを編集する際にタグも変更できるようにします。
viewの一部を抜粋します。
profiles/edit.html.erb<div class="input-field col s12"> <i class="material-icons prefix">local_offer</i> <%= f.text_field :tag, value: @tag_list, placeholder: "タグを複数つけるには' , 'で区切ってください" %> </div>
value: @tag_list
とすることで既存の値を表示します。editアクションではviewで既存の値を表示するために@tag_listを記述します。
pluck関数を使うことによってレシーバのカラムを簡単に取得します。
今回は@user_profile.tags.pluck(:name)
としているのでプロフィールに関連したタグのnameカラムを配列で取得します。
join(',')で取得した配列を「,」で区切った文字列にします。profiles_controller.rbdef edit @user = Profile.find(params[:id]).user @user_profile = @user.profile @tag_list = @user_profile.tags.pluck(:name).join(',') end def update @user = Profile.find(params[:id]).user @user_profile = @user.profile tag_list = params[:profile][:tag].split(',') if @user_profile.update(profile_params) @user_profile.save_tags(tag_list) flash[:notice] = "プロフィールの変更が完了しました" redirect_to root_url else render 'edit' end end今回は繰り返しの処理が単純なのでpluck関数を使わなくても「&:メソッド」を使えばタグの名前を同程度の記述量で取得することができます。
@user_profile.tags.map(&:name).join(',')
しかし、今回の場合、pluckは指定したカラムのみをSQLで取ってくるのでmapより早いと思われます。(間違っていたらご指摘いただければ幸いです)なのでこのままpluckを使用します。save_tagsメソッドを更新でも使えるようにします。
profile.rbdef save_tags(profile_tag) current_tags = self.tags.pluck(:name) unless self.tags.nil? old_tags = current_tags - profile_tag new_tags = profile_tag - current_tags # 古いタグを削除 old_tags.each do |old_tag| self.tags.delete(Tag.find_by(name: old_tag)) end # 新しいタグを追加 new_tags.each do |new_tag| add_tag = Tag.find_or_create_by(name: new_tag) self.tags << add_tag end end文字列の配列でも下記の例のように引き算できます。
a = ["first", "second", "third"]
b = ["first", "third", "forth"]
a - b => ["second"]
b - a => ["forth"]
これを用いて古いタグ、新しいタグを分けてそれぞれ処理します。最後に
これでタグの作成、編集機能は完成です。
まだユーザーに取って良い形とは言えないのでjsを使うなどしてユーザーにとって使いやすいものに改善していこうと思います。参考
pluckメソッドが便利な件について
pluckとmapの違いを調査する
Railsでタグ機能をgemを使わずに実装した際のメモ
- 投稿日:2020-11-25T17:25:15+09:00
【Rails】device機能【備忘録】
こんにちは。Web開発6週目、Rails初心者の あをか です。
初心者にとってdeviceというgemはブラックボックスで、何がどう繋がっているのか把握しづらかったです。復習も兼ねて、deviceの導入過程を書いていきます。
■ ログイン機能(device)
①deviceを入れてモデルとテーブルを作ろう
gem 'device'とGemFileに書き、
ターミナルで①bundle install➡②rails g devise:installする。
続いてログイン機能で欠かせないuser情報を扱うために、モデルとテーブルを作成したい。
rails g model User string:name, string:email,...かと思いきや、deviceの時は違う。
rails g devise UserだけでUserテーブルの中身まで作成してくれる。
ただしdeviceで作成したテーブルのカラムは、デフォルトではemailとpasswordだけ。
ユーザー名を管理したい場合、
db/migrateの下にあるマイグレーションファイルにt.string :nameと追記する。
(その他userテーブルで管理したいものはこの時に全て書いておくこと)
マイグレーションファイルへの追記が終わったら、rails db:migrateする。(rails db:migrateをした後に間違いが見つかると少しめんどくさい。このコマンドで、この時のマイグレーションファイルがschemaファイルに反映され、マイグレーションファイルは読み取り専用になるから。ちなみにschemaファイルは現在のテーブル一覧が分かるので便利。
※詳しくは➡https://qiita.com/kakiuchis/items/2ed1604557ee29bbcbf7
※カラムを変えたくなったら➡https://qiita.com/azusanakano/items/a2847e4e582b9a627e3a)②routesファイルとUserモデルを確認しよう(見るだけ)
routes.rbにdevise_for :usersと書かれているはず。
user.rbに devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatableと書かれているはず。③ユーザー登録画面とログイン画面のレイアウト
app/views/devise/registrations/new.html.erbがユーザー登録画面、
app/views/devise/sessions/new.html.erbがログイン画面。
適宜編集しましょう。name入力フォームを作る時はtext_field使うのに注意。emaiilとpasswordが特殊なんです。④deviceのコントローラはどこ・・?
結論から申しますと、deviceコントローラは触る事ができません(存在はするようです)。
で、どうするかというと、application_controllerで操作します。
1~4行目・13~16行目を書きましょう。
privateではなくprotectedなのは、別のコントローラからも操作できるように。
(5~11行目はログイン・ログアウト後のルートを変えたい場合の記述)⑤バリデーション
userテーブルのnameカラムにバリデーションを設定したいので、
models/user.rbに記述する。
以上、device導入の備忘録でした。
初心者なので間違い等あるかもしれませんが、その際は教えて下さると幸いです!!
- 投稿日:2020-11-25T17:04:25+09:00
Railsの1対多の作成
本投稿の目的
・Rails学習の議事録です。
学習に使った教材
Udemyの以下2つの教材を参考にまとめました。
・"はじめてのRuby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう"
・"フルスタックエンジニアが教える 即戦力Railsエンジニア養成講座"
○1対多とは?
・紐づいたmodel間での関係の名称
・ex)ツイートmodelとコメントmodle
・この時,ツイート1つに対して複数コメントが可能
・この関係を"1対多"の関係と呼ぶ【説明の簡略のために書き省略記号を使用】
・素となるmodel = model(1)
・紐づくmodel = model(多)
*(コーディング例では任意のmodel名が入る)【1対多の関係におけるmodel関係の名称を2つ示す】
・has many = model(1)から見たmodel(多)の関係
・belongs to = model(多)からみたmodel(1)の関係○model(他)作成後の操作
①model(多)の作成
qiita.rbrails g model model(多) model(1): references【解説】
○model(1): references
⇨ referencesでmodel(1)を紐けるように設定
⇨ これで,model(多)には, model名(1)_id というcolumnが作成される【例:Q&Aアプリケーションのmodelを想定】
qiita.rbrails g model answer questin: references【解説】
⇨ answer model へ question model を紐付ける
⇨ questionのcolumnに answer_id が作成される②model(多)のmigrationファイルへの記述
・model(多)を紐づけで作成しておけば自動記述される
qiita.rbclass Create+model(多) < ActiveRecord::Migration[5.0] def change create_table :model(多)s do |t| t.references :model(1), foreign_key: true t.string :column名1, null: false t.text :column名2, null: false t.timestamps end end end【解説】
○null: faluse
⇨ column値に 空 を受け付けない○t.reference :model(1), foreign_key: true
⇨ model(1)のid にないと,model(1)_idとしてmodel(多)で保存できない③model(多)のdbの作成
rails db:migrate④model(多)のmodelファイルへの記述(自動記述)
・model(多)作成時に,以下が,model(多)のmodelファイルへ自動で記述される
qiita.rbclass model(多) < ApplicationRecord belongs_to :model(1) end【解説】
○belongs_to :model(1)
⇨ (*model(1)名称は単数係)【例:answer modelに紐づくquestion modelの場合】
qiita.rbclass Answer < ApplicationRecord belongs_to :question end○belongs_to :question⇨ 親がmodel(1) で 子がmodel(多)
⇨ (model(多)から見るとmodel(1)は1つに定まる)
⇨ (子供は複数人いるが,親や一位に定まるというイメージ)⑤model(1)のmodelファイルへの記述(手動記述)
・model(1)については,追加で以下の記述を追加する
qiita.rbhas_many :model(多)s, dependent: :destroy【解説】
○has_many :model(多)s
⇨ 一番上の行に記述する
⇨ (*モデル名(多)は複数形)○dependent: :destroy
⇨ model(1)が削除されると紐づいたmodel(多)は自動で消される という設定【例:Answerの素となるQuesitionの場合】
qiita.rbclass Question < ApplicationRecord has_many :answers, dependent: :destroy end⑥rails routesの更新
・config/routes.rb ファイルを開く
・内容を次のように更新 (do 移行が追加で更新する部分)
*(model名は複数形)qiita.rbresources :model(1)s do resources :model(多)s end
- 投稿日:2020-11-25T15:34:21+09:00
コントローラーとビューを同時に作成
コントローラーとビューを同時に作成
作成方法
% rails g controller 'コントローラー名/ビューのファイルの名前' index
(例) % rails g controller tests index
- testsコントローラーにindexアクションが作られる
- viewsのtestsフォルダにindex.html.erbファイルが作られる
以下の方法を取ると、オプションとして指定したアクションの定義と、
アクションに紐付いたビューファイルが作成できます。
(例)% rails g controller test index new
- app/controllers/tests_controller.rb
- app/views/tests/index.html.erb
- app/views/tests/new.html.erb
が作成されます。
- 投稿日:2020-11-25T14:58:53+09:00
Heroku上のRailsアプリにScoutを導入
Herokuの管理画面からアドオンを追加
ダッシュボードから操作する方法と、コマンドラインから操作する方法があります。
アドオンを追加した時点で、環境変数が自動でセットされます。ダッシュボードから操作する場合
- Herokuのダッシュボードにアクセス
- Resourcesタブを開く
- Add-onsの下の検索窓に'Scout'と入力
- Provisionをクリック
コマンドラインから操作する場合
Heroku CLIがインストールされている状態で以下のコマンドを叩く
$ heroku addons:create scout
Gemを追加
Gemfilegem 'scout_apm'$ bundle installこれだけ。アプリにリクエストが無いとメトリクスは表示されないので注意。導入直後に見ても多分反映されていなくて、本番のアプリを適当に触った上で、数時間後に確認すると反映されてます。
- 投稿日:2020-11-25T14:36:57+09:00
RailsでGoogleMapAPIをつかって地図投稿機能 地名検索 一覧表示
作る機能
・投稿する際に緯度と経度を保存する
・すべての投稿内容を一つの地図上で一覧表示させる
・マーカーをクリックすると投稿の内容が出る
・地図上で詳細ページまでのリンクを表示させる
完成デモ
それでは作っていきましょう!
まずは先人の知恵を借ります。
以下の記事を参考に最後まで作り上げてください!【Rails6 / Google Map API】初学者向け!Ruby on Railsで簡単にGoogle Map APIの導入する
https://qiita.com/nagaseToya/items/e49977efb686ed05eadb※補足
自分が実装した際は課金設定のとこで少々手間取りましたが、
左のメニューバーから設定できるはずです。
以上が、実装できましたら
機能を追加していきます!※YOURAPIKEYには自分自身のAPIキーを代入してください
投稿する際に緯度と経度を保存する
カラムの追加
$ rails generate migration AddDetailsToPosts lat:float lng:float $ rails db:migrate投稿フォームを追加
元記事から少々、書き換えているので注意して以下の文をコピペしてください。
posts/new.html.erb<h1>New Post</h1> <%= form_with(model: @post, local: true) do |f| %> <div class="actions"> <%= f.label :body,"内容" %> <%= f.text_field :body %> <%= f.label :lat,"緯度" %> <%= f.text_field :lat,:value =>"下のグーグルマップのマーカーを動かしてね", id: :lat %> <%= f.label :lng,"経度" %> <%= f.text_field :lng,:value =>"下のグーグルマップのマーカーを動かしてね", id: :lng %> <%= f.submit %> </div> <% end %> <h2>Map</h2> <input id="address" type="textbox" value="GeekSalon"> <input type="button" value="検索" onclick="codeAddress()"> <p>マーカーをドラック&ドロップで位置の調整ができます。<p> <div id='map'></div> <style> #map { height: 600px; width: 600px; } </style> <script> //初期マップの設定 let map let marker function initMap(){ geocoder = new google.maps.Geocoder() map = new google.maps.Map(document.getElementById('map'), { center: {lat: 35.6803997, lng:139.7690174}, //東京 zoom: 15, }); } //検索後のマップ作成 let geocoder let aft function codeAddress(){ let inputAddress = document.getElementById('address').value; geocoder.geocode( { 'address': inputAddress}, function(results, status) { if (status == 'OK') { //マーカーが複数できないようにする if (aft == true){ marker.setMap(null); } //新しくマーカーを作成する map.setCenter(results[0].geometry.location); marker = new google.maps.Marker({ map: map, position: results[0].geometry.location, draggable: true // ドラッグ可能にする }); //二度目以降か判断 aft = true //検索した時に緯度経度を入力する document.getElementById('lat').value = results[0].geometry.location.lat(); document.getElementById('lng').value = results[0].geometry.location.lng(); // マーカーのドロップ(ドラッグ終了)時のイベント google.maps.event.addListener( marker, 'dragend', function(ev){ // イベントの引数evの、プロパティ.latLngが緯度経度 document.getElementById('lat').value = ev.latLng.lat(); document.getElementById('lng').value = ev.latLng.lng(); }); } else { alert('該当する結果がありませんでした:' + status); } }); } </script> <script src="https://maps.googleapis.com/maps/api/js?key=YOURAPIKEY&libraries=&v=weekly" defer></script>ストロングパラメーターの設定も忘れずに!
controllers/posts_controller.rbdef post_params params.require(:post).permit(:body,:lat,:lng) end緯度と経度のtext_fieldにidを追加しのマーカーのドラック後に
valueをJavaScriptで書き換えるコードを追加しました。場所や地名で検索し緯度と経度をtext_fieldに入力し、保存できるようになりました。
本当に保存できたか確認してみましょう。$ rails c $ Post.all無事ターミナルにデータが表示されていればオッケーです!
すべての投稿内容を一つの地図上で一覧表示させる、マーカーをクリックすると投稿の内容が出る、地図上で詳細ページまでのリンクを表示させる
以上を一気に作っていきます
posts/index.html.erb<div id='map'></div> <style> #map { height: 600px; width: 100%; } </style> <!-- js部分 --> <script> function initMap() { //初期表示位置 let latlng = new google.maps.LatLng(38.60, 139.5); //デザイン let styles = [ { stylers: [ { "saturation": 0 }, { "lightness": 0 } ] } ]; let map = new google.maps.Map(document.getElementById('map'), { zoom: 5.5, styles: styles, center: latlng }); let transitLayer = new google.maps.TransitLayer(); transitLayer.setMap(map); //複数マーカー ここから <% @posts.each do |post|%> ( function() { let markerLatLng = new google.maps.LatLng({lat: <%= post.lat %>, lng: <%= post.lng %>}); // 緯度経度のデータ作成 let marker = new google.maps.Marker({ position: markerLatLng, map: map }); //マーカーをクリックしたとき、詳細情報を表示 let infowindow = new google.maps.InfoWindow({ position: markerLatLng, content: "<a href='<%= post_url(post.id) %>' target='_blank'><%= post.body %></a>" }); //ここで詳細ページへのリンクを表示させる marker.addListener('click', function() { infowindow.open(map, marker); }); })(); <% end %> //複数マーカー ここまで } </script> <script src="https://maps.googleapis.com/maps/api/js?key=YOURAPIKEY&callback=initMap" async defer></script>以上、お疲れ様でした!
「飲食店などの場所を投稿したい」ってよくありますよね。
ぜひこの記事が役に立ったら、幸いです!おまけ①練習問題
詳細ページではどのように実装すれば、地図が表示されるでしょうか?
上記を参考にして少し考えてみてください!
答え
緯度と経度を取得して地図に反映させればオッケーですね!
posts/show.html.erb<div id='map'></div> <style> #map { height: 600px; width: 100%; } </style> <!-- js部分 --> <script> //初期マップの設定 let map let marker function initMap(){ geocoder = new google.maps.Geocoder() map = new google.maps.Map(document.getElementById('map'), { center: {lat: <%= @post.lat %>, lng: <%= @post.lng %>}, zoom: 15, }); marker = new google.maps.Marker({ position: {lat: <%= @post.lat %>, lng: <%= @post.lng %>}, map: map }); } </script> <script src="https://maps.googleapis.com/maps/api/js?key=YOURAPIKEYI&callback=initMap" async defer></script>おまけ②turbolinksによって引き起こされるエラー
リンクで移動した場合、詳細ページにも関わらず、一覧の地図が表示されてしまう場合があります。
このままでもリロードすれば、正常に表示されるのですが。。。。
以下の記述を削除しサーバ―を再起動すればリンクで移動した場合にも、正常に表示されるはずです。app/javascript/packs/application.jsrequire("turbolinks").start()views/layouts/application.html.erb'data-turbolinks-track': 'reload'
- 投稿日:2020-11-25T14:36:57+09:00
Railsで地図投稿機能 地名検索 一覧表示 GoogleMapsAPI(JavaScript)
作る機能
・投稿する際に緯度と経度を保存する
・すべての投稿内容を一つの地図上で一覧表示させる
・マーカーをクリックすると投稿の内容が出る
・地図上で詳細ページまでのリンクを表示させる
完成デモ
それでは作っていきましょう!
まずは先人の知恵を借ります。
以下の記事を参考に最後まで作り上げてください!【Rails6 / Google Map API】初学者向け!Ruby on Railsで簡単にGoogle Map APIの導入する
https://qiita.com/nagaseToya/items/e49977efb686ed05eadb※補足
自分が実装した際は課金設定のとこで少々手間取りましたが、
左のメニューバーから設定できるはずです。
以上が、実装できましたら
機能を追加していきます!※YOURAPIKEYには自分自身のAPIキーを代入してください
投稿する際に緯度と経度を保存する
カラムの追加
$ rails generate migration AddDetailsToPosts lat:float lng:float $ rails db:migrate投稿フォームを追加
元記事から少々、書き換えているので注意して以下の文をコピペしてください。
posts/new.html.erb<%= form_with(model: @post, local: true) do |f| %> <div class="actions"> <%= f.label :body,"内容" %> <%= f.text_field :body %> <%= f.label :lat,"緯度" %> <%= f.text_field :lat,:value =>"緯度", id: :lat %> <%= f.label :lng,"経度" %> <%= f.text_field :lng,:value =>"経度", id: :lng %> <%= f.submit %> </div> <% end %> <h2>Map</h2> <input id="address" type="textbox" value="GeekSalon"> <input type="button" value="検索" onclick="codeAddress()"> <p>マーカーをドラック&ドロップで位置の調整ができます。<p> <div id='map'></div> <style> #map { height: 600px; width: 600px; } </style> <script> //初期マップの設定 let map let marker function initMap(){ geocoder = new google.maps.Geocoder() map = new google.maps.Map(document.getElementById('map'), { center: {lat: 35.6803997, lng:139.7690174}, //東京 zoom: 15, }); } //検索後のマップ作成 let geocoder let aft function codeAddress(){ let inputAddress = document.getElementById('address').value; geocoder.geocode( { 'address': inputAddress}, function(results, status) { if (status == 'OK') { //マーカーが複数できないようにする if (aft == true){ marker.setMap(null); } //新しくマーカーを作成する map.setCenter(results[0].geometry.location); marker = new google.maps.Marker({ map: map, position: results[0].geometry.location, draggable: true // ドラッグ可能にする }); //二度目以降か判断 aft = true //検索した時に緯度経度を入力する document.getElementById('lat').value = results[0].geometry.location.lat(); document.getElementById('lng').value = results[0].geometry.location.lng(); // マーカーのドロップ(ドラッグ終了)時のイベント google.maps.event.addListener( marker, 'dragend', function(ev){ // イベントの引数evの、プロパティ.latLngが緯度経度 document.getElementById('lat').value = ev.latLng.lat(); document.getElementById('lng').value = ev.latLng.lng(); }); } else { alert('該当する結果がありませんでした:' + status); } }); } </script> <script src="https://maps.googleapis.com/maps/api/js?key=YOURAPIKEY&libraries=&v=weekly" defer></script>ストロングパラメーターの設定も忘れずに!
controllers/posts_controller.rbdef post_params params.require(:post).permit(:body,:lat,:lng) end緯度と経度のtext_fieldにidを追加しのマーカーのドラック後に
valueをJavaScriptで書き換えるコードを追加しました。場所や地名で検索し緯度と経度をtext_fieldに入力し、保存できるようになりました。
本当に保存できたか確認してみましょう。$ rails c $ Post.all無事ターミナルにデータが表示されていればオッケーです!
すべての投稿内容を一つの地図上で一覧表示させる、マーカーをクリックすると投稿の内容が出る、地図上で詳細ページまでのリンクを表示させる
以上を一気に作っていきます
posts/index.html.erb<div id='map'></div> <style> #map { height: 600px; width: 100%; } </style> <!-- js部分 --> <script> function initMap() { //初期表示位置 let latlng = new google.maps.LatLng(38.60, 139.5); //デザイン let styles = [ { stylers: [ { "saturation": 0 }, { "lightness": 0 } ] } ]; let map = new google.maps.Map(document.getElementById('map'), { zoom: 5.5, styles: styles, center: latlng }); let transitLayer = new google.maps.TransitLayer(); transitLayer.setMap(map); //複数マーカー ここから <% @posts.each do |post|%> ( function() { let markerLatLng = new google.maps.LatLng({lat: <%= post.lat %>, lng: <%= post.lng %>}); // 緯度経度のデータ作成 let marker = new google.maps.Marker({ position: markerLatLng, map: map }); //マーカーをクリックしたとき、詳細情報を表示 let infowindow = new google.maps.InfoWindow({ position: markerLatLng, content: "<a href='<%= post_url(post.id) %>' target='_blank'><%= post.body %></a>" }); //ここで詳細ページへのリンクを表示させる marker.addListener('click', function() { infowindow.open(map, marker); }); })(); <% end %> //複数マーカー ここまで } </script> <script src="https://maps.googleapis.com/maps/api/js?key=YOURAPIKEY&callback=initMap" async defer></script>以上、お疲れ様でした!
「飲食店などの場所を投稿したい」ってよくありますよね。
ぜひこの記事が役に立ったら、幸いです!おまけ①練習問題
詳細ページではどのように実装すれば、地図が表示されるでしょうか?
上記を参考にして少し考えてみてください!
答え
緯度と経度を取得して地図に反映させればオッケーですね!
posts/show.html.erb<div id='map'></div> <style> #map { height: 600px; width: 100%; } </style> <!-- js部分 --> <script> //初期マップの設定 let map let marker function initMap(){ geocoder = new google.maps.Geocoder() map = new google.maps.Map(document.getElementById('map'), { center: {lat: <%= @post.lat %>, lng: <%= @post.lng %>}, zoom: 15, }); marker = new google.maps.Marker({ position: {lat: <%= @post.lat %>, lng: <%= @post.lng %>}, map: map }); } </script> <script src="https://maps.googleapis.com/maps/api/js?key=YOURAPIKEYI&callback=initMap" async defer></script>おまけ②turbolinksによって引き起こされるエラー
リンクで移動した場合、詳細ページにも関わらず、一覧の地図が表示されてしまう場合があります。
このままでもリロードすれば、正常に表示されるのですが。。。。
以下の記述を削除しサーバ―を再起動すればリンクで移動した場合にも、正常に表示されるはずです。app/javascript/packs/application.jsrequire("turbolinks").start()views/layouts/application.html.erb'data-turbolinks-track': 'reload'
- 投稿日:2020-11-25T11:47:09+09:00
中間テーブルを使ったお気に入り機能の実装!
実装したいこと
掲示板にブックマーク機能を追加したい。
ユーザーがブックマークした掲示板を一覧できるページを実装したい。
これだけの機能なのに、めちゃくちゃ難しい。いろんなことを調べるいい機会になりました。
かなり多くのものを調べたので順序立てて説明をしていきます。実装の大まかな流れ
- 中間テーブルとなるBookmarkモデルの実装
- UserモデルとBoardモデルとのアソシエーションを実装
- Userモデルにお気に入り登録のギミックを定義
- BookmarkControllerの実装
- Routingの設定
- Viewの実装
Bookmarkモデルの仕組み(多対多)
で、いざお気に入り機能をつけよう!となってもどうやって実装するの・・・?となります。なので、まずどうUserモデルとBoardモデルに紐づけていくのか考えて見ましょう。
UserとBookmarkとBoardの関係
ユーザーと掲示板とブックマークの関係を見ていきましょう。
ユーザーはたくさんの掲示板をブックマークすることができます。反対に、掲示板はたくさんのユーザーにフォローされることができます。
つまり、UserもBoardもBookmarkをたくさん持っているということになります。このような関係を多対多の関係と言われています。
中間モデル
多対多のモデルを実装するにはお互いのforeign_keyを知っている必要があります。そのために、お互いのidを格納するテーブル、中間テーブルを実装する必要があります。
Bookmarkモデルの作成
中間テーブルとなるBookmarkモデルを作成していきます。
ターミナル
ruby
rails g model Bookmark user:references board:references
migrateファイル
class CreateBookmarks < ActiveRecord::Migration[6.0] def change create_table :bookmarks do |t| t.references :user, null: false, foreign_key: true t.references :board, null: false, foreign_key: true t.timestamps end add_index :bookmarks, [:user_id, :board_id], unique: :true end endユーザーが同じ掲示板をお気に入り登録しないように
unique: :true
をつける必要があります。
add_index :bookmarks, [:user_id, :board_id], unique: :true
でuser_idとboard_idの組み合わせがuniqueであることを設定します。
これでrails db:migrate
を行います。bookmark.rb
class Bookmark < ApplicationRecord belongs_to :user belongs_to :board validates :user_id, uniqueness: { scope: :board_id} endmigrationにも
unique: :true
を付けたので、モデルにもバリデーションを記載します。uniquenessとscopeについて
validates :user_id, uniqueness: { scope: :board_id}
end上記は各掲示板idと同じユーザーidがお気に入り関係にならないように一意性制約を付けています。
rails cで確認して見ます。irb(main):001:0> user = User.first irb(main):002:0> board = Board.first # userが掲示板をお気に入り登録する。 irb(main):003:0> user.bookmark(board) (0.1ms) begin transaction Bookmark Exists? (0.9ms) Bookmark Create (3.2ms) (0.7ms) commit transaction => #<ActiveRecord::Associations::CollectionProxy [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">]> # もう一度同じユーザーで掲示板のお気にいり登録を試みる。 irb(main):004:0> user.bookmark(board) # すでにお気に入り登録されているので、バリデーションに引っ掛かりrollbackされる。 (0.1ms) begin transaction Bookmark Exists? (0.2ms) (0.1ms) rollback transaction Traceback (most recent call last): 2: from (irb):4 1: from app/models/user.rb:27:in `bookmark' ActiveRecord::RecordInvalid (バリデーションに失敗しました: Userはすでに存在します)# 違うユーザーを指定 irb(main):005:0> user_2 = User.second # 違うユーザーで掲示板をお気に入り登録を試みると成功する。 irb(main):006:0> user_2.bookmark(board) (0.1ms) begin transaction Bookmark Exists? (0.2ms) Bookmark Create (0.8ms) (1.4ms) commit transaction => #<ActiveRecord::Associations::CollectionProxy [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">]>bookmarkメソッドはUserモデルに定義しています。また後で紹介しますがconsoleで出てくるので載せておきます。
user.rb
# お気に入りにしている掲示板を取得する has_many :bookmarks_boards, through: :bookmarks, source: :board # お気に入り追加 # <<で引数で渡した掲示板の情報がbookmark_boardsに入っている def bookmark(board) bookmarks_boards << board end参考記事
[https://qiita.com/j-sunaga/items/d7f0e944baad6e56206c:title]
[https://railsguides.jp/active_record_validations.html#uniqueness:title]
UserモデルとBoardモデルのアソシエーションの設定
Bookmarkモデルの実装が終わったので、他のモデルにもアソシエーションなどの設定を行っていきます。
Boardモデル
board.rb
class Board < ApplicationRecord belongs_to :user has_many :comments, dependent: :destroy has_many :bookmarks, dependent: :destroy # 追記 mount_uploader :board_image, BoardImageUploader validates :title, length: { maximum: 255 }, presence: true validates :body, length: { maximum: 65535 }, presence: true end掲示板はたくさんのBookmarkを持つことができるので
has_many
を使います。Userモデル
user.rb
has_many :bookmarks, dependent: :destroy # お気に入りにしている掲示板を取得する has_many :bookmarks_boards, through: :bookmarks, source: :boardUserもたくさんのBookmarkを持つことができるので
has_many
を使っていきます。ちょっと待って、
has_many :bookmarks_boards
って何?through
とかsource
も使っているけどよくわからない・・・。
というわけでこれからhas_many through
について調べたことをまとめていきます。has_many throuth
まず
has_many through
はUserモデルでどういう働きをしているのかというと、ユーザーがお気に入りしている掲示板を取得することができるようになります。Twitterでいいね一覧が表示できる機能がありますよね。それと同じようにお気に入り登録した掲示板を一覧で表示できるページを作るために必要となってきます。
has_many through
を使わずにユーザーのお気に入りした掲示板を取得したいとなると、このようなコードになります。# user.bookmarksでuserがお気に入り登録した掲示板のidが入っているレコードの集合を取得することができる。 irb(main):014:0> user.bookmarks => #<ActiveRecord::Associations::CollectionProxy [#<Bookmark id: 7, user_id: 1, board_id: 1, created_at: "2020-11-20 00:50:02", updated_at: "2020-11-20 00:50:02">, #<Bookmark id: 10, user_id: 1, board_id: 3, created_at: "2020-11-20 01:57:05", updated_at: "2020-11-20 01:57:05">, #<Bookmark id: nil, user_id: 1, board_id: 1, created_at: nil, updated_at: nil>]> # userが最初にお気に入り登録した掲示板のレコードを取得 irb(main):005:0> user.bookmarks.first Bookmark Load (0.5ms) => #<Bookmark id: 7, user_id: 1, board_id: 1, created_at: "2020-11-20 00:50:02", updated_at: "2020-11-20 00:50:02"> # 上記で取得したレコードにboardメソッドを実行するとお気に入り登録した掲示板の内容が取得できる! irb(main):006:0> user.bookmarks.first.board Bookmark Load (0.1ms) Board Load (0.2ms) => #<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg"> # つまり、user.bookmarksのひとつひとつのレコードにboardメソッドを実行すればユーザーがお気に入りにした掲示板の内容の集合を取得することができる! # なのでmapメソッドを使ってユーザーがお気に入り登録した掲示板のレコードにboardメソッドを実行し、それを配列に組み込んでいく。 irb(main):007:0> user.bookmarks.map{|bookmark| bookmark.board} Bookmark Load (0.4ms) Board Load (0.2ms) Board Load (0.1ms) => [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">, #<Board id: 3, title: "aaaaaaaaaa", body: "aaaaaaaaaaa", user_id: 2, created_at: "2020-11-20 01:56:08", updated_at: "2020-11-20 01:56:08", board_image: nil>] # 上記の式を(&:)を使って書き換えます。 irb(main):008:0> user.bookmarks.map(&:board) => [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">, #<Board id: 3, title: "aaaaaaaaaa", body: "aaaaaaaaaaa", user_id: 2, created_at: "2020-11-20 01:56:08", updated_at: "2020-11-20 01:56:08", board_image: nil>]つまり、
user.bookmarks.map(&:board)
を使えばユーザーのお気に入り登録している掲示板の情報の集合を取得できるというわけです!このコードをcontrollerなどに書いて実装するのも一つの方法だと思いますが、あまり直接的ではないのと、このコードをビューに落とし込むのも大変です。
そこで
has_many through
の登場です。
これを使えばuser.bookmarks.map(&:board)
をモデル内に簡単に実装できちゃいます。has_many :bookmarks_boards, through: :bookmarks, source: :board
:bookmarks_boards
と定義することでメソッド化して使うことができます。
user.bookmarks.map(&:board)
このコードを見ながら解説していくとUserのインスタンスにbookmarksメソッド(
through:
で定義)を実行し、それで得られたBookmarksのインスタンスデータのひとつひとつの要素に対してboardメソッド(source:
で定義)を実行するということです。
なので、多対多のモデルを作った時に必ずと言っていいほど活躍するというわけです!
参考記事
実はRailsチュートリアルの第14章の動画を見るとすごくわかりやすいです。
[https://railstutorial.jp/:title]
Railsガイドの文献
Bookmarks_Controllerの実装
よし、モデルのアソシエーションも終わったしcontroller作ろう!
ちょっと待ってください。controllerの可読性を上げるためにまずはモデルにお気に入り登録のギミックを定義していきましょう。すると驚くほどにcontrollerの実装が完結になりますよ!controllerを作る前にモデルにBookmarkのギミックを定義する。
Userモデルにお気に入り登録のギミックとなるメソッドを定義していきましょう。
user.rb
```ruby
# お気に入り追加
# <<で引数で渡した掲示板の情報がbookmark_boardsに入っている
def bookmark(board)
bookmarks_boards << board
end# お気に入りを外す
def unbookmark(board)
bookmarks_boards.delete(board)
end# お気に入り登録しているか判定するメソッド
def bookmark?(board)
bookmarks_boards.include?(board)
end早速先ほど定義した`bookmarks_boards`が使われていますね。 一つずつメソッドを見ていきます。 ####bookmarkメソッド ```ruby # お気に入り追加 # <<で引数で渡した掲示板の情報がbookmark_boardsに入っている def bookmark(board) bookmarks_boards << board end掲示板の情報のレコードが引数boardに格納され
bookmarks_boards
に<<
で追加されています。
<<
は指定されたオブジェクトの末尾に破壊的に追加できるメソッドです。
強制的に追加されて保存もされているのでsaveメソッドなどは必要ありません。
<<
メソッドについて詳しくはこちら[https://docs.ruby-lang.org/ja/latest/method/Array/i/=3c=3c.html:title]
unbookmarkメソッド
# お気に入りを外す def unbookmark(board) bookmarks_boards.delete(board) end
bookmarks_boards
からboardの引数に入っている掲示板idが入ったレコードを探し出して削除(delete)するメソッド。bookmark?メソッド
# お気に入り登録しているか判定するメソッド def bookmark?(board) bookmarks_boards.include?(board) end
bookmarks_boards
にboardの引数に入っている掲示板idが含まれているレコードがあるかどうか判定するメソッド。def bookmark?(board) Bookmark.where(user_id: id, board_id: board.id).exist? endこのように書くこともできますが、
include?
の方が直感的でわかりやすいです。bookmarks_controllerの実装
ここまできたら、bookmarks_controllerを作っていきましょう。
viewも必要ないのでcontrollerファイルだけ作って記載します。bookmarks_controller.rb
class BookmarksController < ApplicationController def create board = Board.find(params[:board_id]) current_user.bookmark(board) redirect_back fallback_location: root_path, success: 'ブックマークしました' end def destroy board = current_user.bookmarks.find_by(params[:id]).board current_user.unbookmark(board) redirect_back fallback_location: root_path, success: 'ブックマークを外しました' end endお気に入り登録のギミックをモデルに定義したことによって、かなり直感的なcontrollerになりました!
redirect_back fallback_location : root_path
redirect_back
はユーザーが直前にリクエストを送ったページに戻すことができます。
fallback_location
は直前にリクエストを送ったページがない場合のデフォルトのリダイレクト先を指定しています。Routingの設定
controllerも書けたので、次はRoutingの設定を行っていきます。
routes.rb
Rails.application.routes.draw do root 'static_pages#top' resources :users get 'login', to: 'user_sessions#new' post 'login', to: 'user_sessions#create' delete 'logout', to: 'user_sessions#destroy' resources :boards, shallow: true do resources :comments, only: %i[create destroy] resource :bookmarks, only: [:create, :destroy] collection do get :bookmarks end end endBoardとBookmarkは親子の関係なのでbookmarks_controllerのRoutingはboards_controllerにネストするように記載しています。
collectionルーティング
railsの基本的なアクションはresourcesで定義した時に作られる7つのアクションですが、更に別のアクションを追加したい時があります。
その時に使えるのがcollectionルーティングです。boards_controllerに新しくbookmarksというアクションを追加することができます。
ちなみに、controllerのメンバーに対してアクションを追加する場合(idが伴う場合)はmemberルーティングを使います。collection以外を抜いたルーティングがこちら。
resources :boards, shallow: true do collection do get :bookmarks end end
bookmarks_boards GET /boards/bookmarks(.:format) boards#bookmarks
上記でこのようなルーティングが出来上がります。
このルーティングとアクションはお気に入りされた掲示板一覧を表示するページとして使っていきます。Boards_controllerにbookmarksアクションを追記
ルーティングが書けましたので、Boards_controllerにbookmarksアクションを追加していきます。
boards_controller.rb
def bookmarks @bookmark_boards = current_user.bookmarks_boards.includes(:user).order(created_at: :desc) end無駄にSQL文を発行させない様に
includes(:user)
を記載して関連するuserの情報も取得しています。(n + 1問題の解消)irb(main):001:0> user = User.first irb(main):002:0> user.bookmarks_boards Board Load (2.2ms) SELECT "boards".* FROM "boards" INNER JOIN "bookmarks" ON "boards"."id" = "bookmarks"."board_id" WHERE "bookmarks"."user_id" = ? LIMIT ? [["user_id", 1], ["LIMIT", 11]] => #<ActiveRecord::Associations::CollectionProxy [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">, #<Board id: 3, title: "aaaaaaaaaa", body: "aaaaaaaaaaa", user_id: 2, created_at: "2020-11-20 01:56:08", updated_at: "2020-11-20 01:56:08", board_image: nil>]> irb(main):003:0> user.bookmarks_boards.includes(:user) Board Load (0.3ms) SELECT "boards".* FROM "boards" INNER JOIN "bookmarks" ON "boards"."id" = "bookmarks"."board_id" WHERE "bookmarks"."user_id" = ? LIMIT ? [["user_id", 1], ["LIMIT", 11]] User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?) [["id", 1], ["id", 2]] => #<ActiveRecord::AssociationRelation [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">, #<Board id: 3, title: "aaaaaaaaaa", body: "aaaaaaaaaaa", user_id: 2, created_at: "2020-11-20 01:56:08", updated_at: "2020-11-20 01:56:08", board_image: nil>]>Viewの実装
ここまで仕組みを実装したら、あとはViewを作るだけです!
お気に入りボタンとお気に入りした掲示板一覧ページを作成していきます。お気に入りボタンの作成
まずはパーシャルでブックマークするボタンとブックマーク解除ボタンを切り替えるページを作ります。
bookmarks/_bookmark_area.html.erb
<% if current_user.bookmark?(board) %> <%= render 'bookmarks/unbookmark', board: board %> <% else %> <%= render 'bookmarks/bookmark', board: board %> <% end %>user.rbで定義された
bookmark?
メソッドがここで使われます。
掲示板がブックマークされていたら掲示板解除ボタン、掲示板がブックマークされていなかったらブックマーク登録ボタンに切り替わる仕組みです。次に、お気に入り登録ボタンを実装していきます。
bookmarks/_bookmark.html.erb<%= link_to board_bookmarks_path(board_id: board.id), id: "js-bookmark-button-for-board-#{board.id}", method: :post do %> <%= icon 'far', 'star' %> <% end%>次に、お気に入り解除ボタンを実装していきます。
bookamrks/_unbookmarks.html.erb<%= link_to board_bookmarks_path(current_user.bookmarks.find_by(board_id: board.id)), id: "js-bookmark-button-for-board-#{board.id}", method: :delete do %> <%= icon 'fas', 'star' %> <% end %>
current_user
に紐づいているbookmarkインスタンスの中から掲示板idが含まれているものを探し、取得しています。これで、お気に入りボタンが完成しました。これを掲示板のパーシャルに組み込んでいきます。
boards/_board.html.erb
<% if current_user.own?(board) %> <div class='mr10 float-right'> <%= render 'crud_menus', board: board %> <% else %> <%= render 'bookmarks/bookmark_area', board: board %> </div> <% end %>掲示板がログインしているユーザーのものだったらcrud_menusボタンが表示され、ユーザーのものではなかった場合はお気に入りボタンに切り替わる様になっています。
お気に入り掲示板一覧機能ページの作成
最後に、お気に入りした掲示板一覧ページを作成していきます。
掲示板一覧ページとレイアウトはあまり変わらないため、ほとんど掲示板一覧ページから引っ張ってきています。boards/bookmarks.html.erb
<% content_for(:title, t('.title')) %> <div class="container pt-3"> <div class="row"> <div class="col-lg-10 offset-lg-1"> <!-- 検索フォーム --> <form> <div class="input-group mb-3"><input class="form-control" placeholder="検索ワード" type="search"/> <div class="input-group-append"><input type="submit" value="検索" class="btn btn-primary"/></div> </div> </form> </div> </div> <div class="row"> <div class="col-12"> <div class="row"> <% if @bookmark_boards.present? %> <%= render partial: "board", collection: @bookmark_boards %> <% else %> <p>ブックマーク中の掲示板がありません</p> <% end%> </div> </div> </div> </div>これで完成です!お疲れ様でした!
おまけ
n + 1問題を解消するためにコードをリファクタリングしていきます。
boards_controller.rb
def index # userのみキャッシュしている。 @boards = Board.all.includes(:user).order(created_at: :desc) #bookmarkも取得できる様になる。 @boards = Board.all.includes([:user, :bookmarks]).order(created_at: :desc) enduser.rb
# userを起点にしてSQLを走らせてしまっているためレコードを取得する時に毎回SQLが走ってしまう。 def bookmark?(board) bookmarks_boards.include?(board) end # boardを起点にしてSQLが走り、検索をかける。無駄なSQLが走らない。 def bookmark?(board) bookmarks_boards.pluck(:user_id).include?(id) end_unbookmark.html.erb
<%= link_to bookmark_path(board.bookmarks.find { |b| b.user_id == current_user.id }), id: "js-bookmark-button-for-board-#{board.id}", class:"float-right", method: :delete, remote: true do %> <%= icon 'fas', 'star' %> <% end %>なるべくfindを使う様にして無駄にSQLを走らせない様にする。
- 投稿日:2020-11-25T11:34:19+09:00
CSSの記述が間違っていてエラーが出た話
環境
macOS Catalina 10.15.7
Ruby on Rails 6.0.0
RSpec 4.0.1
pry rails 0.3.9エラー内容
RSpecで
visit root_path
を実行したタイミングでエラーが発生した。consoleFailure/Error: <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> ActionView::Template::Error: Invalid CSS after "...kground-color: ": expected expression (e.g. 1px, bold), was "##253141;" # (sass):8 # ./app/views/layouts/application.html.erb:8:in `_app_views_layouts_application_html_erb__1634657128219108166_70256618570500' # ------------------ # --- Caused by: --- # Sass::SyntaxError: # Invalid CSS after "...kground-color: ": expected expression (e.g. 1px, bold), was "##253141;" # (sass):8デフォルトで
app/views/layouts/application.html.erb
に記述されている、外部スタイルシートを指定するリンクタグを生成するメソッドを読み込んだタイミングでエラーが発生している。対処
エラー内容をみると、Invalid CSSとなっている。
##253141;
というtypoがあるようなので、VSCodeの文字列検索で探してみる。messages.css.side-bar { width: 300px; height: 100vh; background-color: ##253141; }あった。
#を一つ消し、visit root_path
が正しく動作することを確認できた。
今回、VSCodeの検索機能を初めて使用したが、めちゃくちゃ便利なので、今後も使っていきたい。
- 投稿日:2020-11-25T11:30:10+09:00
Ruby on railsとLalavelどっちが良い?
技術選定の仕方
・自分のやりたい事が実現しやすい
・情報量が多いプログラミングはあくまでも手段でしかなく、プロダクトの価値を最大化させることこそが目的である。またプログラミング言語は、基礎文法は少し変わるぐらいで大した差はない。言語に縛られるよりも、自分が言語に合わせて勉強していくほうが、プロダクトの価値も開発効率も上がる。
Ruby on railsを選びました
①scaffoldが便利
MVCの作成はrouteの設定までいい感じに自動で生成してくれる。②情報量が多い!
ruby自体が日本発の言語ということもあり、qiitaの記事の数をはじめ日本語の説明の記事がlalavelよりも多い。③コミュニティー内に参考にできるソースコードがある
身近にコードについて質問でき参考にできるコードがあるのは、めっちゃ楽。Rails: ある程度の規模まではレールに乗ってサクサク作れるが、アプリケーションが成熟してきて「規約」にはないことをやりたくなってくると、途端に求められる技術力や難易度が跳ね上がる。
Laravel: アプリケーションの雛形はあるものの、どのクラスに何を担わせるかなど、設計については Rails 以上に早い段階で意識する必要がある。その設計さえイケていれば、だいたいのことはしっかり書ける。
Rails 歴5年の僕が Laravel で開発するようになって思ったこと。
https://note.com/kurashicom_tech/n/n32ab25910783結論
僕のような初学者には、railsをおすすめします。railsのレールに乗りながら、まずはMVCの基礎を自分にインプットしていく。その後、lalalvelに挑戦!で良いかなと思う。
どちらの言語も実行コマンドは似ているし、Railsはgem、Laravelはcomposerを使えば各種プラグインも簡単に導入できるので、あまり差はないと感じます。
最後まで読んでいただきありがとうございます。
- 投稿日:2020-11-25T11:22:53+09:00
Vagrant + VirtualBox で初めての開発環境こうちく|既存のRailsプロジェクト + Mysql + Apatch
はじめに
初めて Vagrant で開発環境を構築する方に向けて手順をまとめました。
初めて構築する方のために、専門用語の説明や、つまづきそうな箇所の説明も載せました。
それでは開発環境を構築していきましょう。仮想マシンスペック
OS : CentOS release 7.1
DB : mysql Ver 14.14 Distrib 5.5.41, for Linux (x86_64) using readline 5.1
Webサーバーソフト : Apache/2.2.15 (Unix)
Ruby : ruby 2.1.5p273 [x86_64]
ローカルOSはWidnowsです。
■目次
■□■□■□■□■□■□■
… 1. 環境構築まえの準備
… 2. 仮想マシンの作成
… 3. 必要な関連ソフト及びRubyインストール | 開発環境の作成
… 4. Mysql, Apatchインストール | 開発環境の作成
… 5. Railsインストール | 開発環境の作成
… 6. Railsの起動
■□■□■□■□■□■□■
■ 1.環境構築まえの準備
Vagrant, Virtual boxとは ...
ソフト名 役割 Vagrant 仮想マシンの停止・起動などを簡単に管理できる 「コマンドラインツール」 Virtural box 仮想マシンを作る「仮想化ソフト」 つまり Vagrant を使えば、仮想マシンを 簡易にコマンド操作 できるようになります。
▼ Vagrant + Virtual box のインストール
まず次のサイトから、必要なソフトをダウンロードします。
・Vagtant https://www.vagrantup.com/
・VirtualBox https://www.virtualbox.org/インストールはインストーラーに従うだけで完了します。
▼ インストールされたか確認
次のコマンドを入力してください。
$ vagrant -v Vagrant 1.9.1無事バージョンが返ってこればインストールは完了です。
▼ Vagrant 操作用のディレクトリを作成
次に CentOS インストール用ディレクトリを作成し、作成先のディレクトリへ移動しましょう。
$ mkdir workspace $ cd workspace■ 2.仮想マシンの作成
Virtual box を使用して、仮想マシンを起動するためには OS イメージファイル(※ boxファイル)が必要です。
※ 今回の OS イメージファイルは、Chef 社で用意されている「bento」を使用しました。▼ OS インストール
仮想マシンへ CentOS 7.1 をインストールします。
初期設定を行うために、次のコマンドを実行します。$ vagrant init bento/centos-7.1実行すると「Vagrantfile」という初期設定ファイルが作成されます。
$ ls Vagrantfile▼ 初期設定ファイル(Vagrantfile)の書き換え
次の2箇所の設定を書き換えます。
1. ネットワーク
2. 共有フォルダ▽ 1.ネットワーク
ローカルホスト(作業しているPC)から仮想マシン上の Rasil サーバーへ接続できるようにネットワーク設定を変更します。
Vagrantfile を好みのテキストエディタで開いてください。▶︎ workspace / Vagrantfile
Vagrant.configure(2) do |config| # The most common configuration options are documented and commented below. # For a complete reference, please see the online documentation at # https://docs.vagrantup.com. # Every Vagrant development environment requires a box. You can search for # boxes at https://atlas.hashicorp.com/search. config.vm.box = "bento/centos-7.1" # Disable automatic box update checking. If you disable this, then # boxes will only be checked for updates when the user runs # `vagrant box outdated`. This is not recommended. # config.vm.box_check_update = false # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, # accessing "localhost:8080" will access port 80 on the guest machine. # コメント(#)を外してポート番号を書き換える。 config.vm.network "forwarded_port", guest: 3000, host: 3000 # Create a private network, which allows host-only access to the machine # using a specific IP. # config.vm.network "private_network", ip: "192.168.33.10" # Create a public network, which generally matched to bridged network. # Bridged networks make the machine appear as another physical device on # your network. # config.vm.network "public_network"この設定でブラウザから仮想マシンに接続する接続先が
http://localhost:3000
になります。ここで一度 Vagrant を使って、仮想マシンを起動できるか確認してみましょう。
$ vagrant upvagrantコマンド | 参考
コマンド 役割 vagrant up Vagrantfileに基づいて仮想サーバーを起動 vagrant ssh 仮想サーバーに接続され、コマンド操作できるようになる vagrant halt 仮想サーバーを停止 vagrant reload Vagrantfileの設定を反映させる( vagrant halt + vagrant up ) vagrant destroy 仮想サーバーを消去 ▽ 2.共有フォルダ
共有フォルダ機能とは ...
Vagrant には、ローカルと仮想マシン間のファイルを同期する機能があります。
この機能により、仮想マシン上にわざわざリモート接続(vagrant ssh)してファイルを編集する必要がなくなります。
つまり、ローカルファイルを編集するだけで、自動的に仮想マシン上のファイルに反映できます。デフォルのフォルダ共有場所
場所 ディレクトリ ローカル側 ./workspace 仮想マシン側 /home/vagrant 別のフォルダ場所を共有したい場合は、Vagrantfile の次の箇所を変更します。
config.vm.synced\_folder “ローカルのフォルダ場所”, “仮想マシン上のフォルダ場所”
今回は以下の場所を共有しました。
▶︎ workspace / Vagrantfile
Vagrant.configure(2) do |config| # other config here config.vm.synced_folder "./workspace", "/home/vagrant/rials_projects" endこの設定を反映させるために仮想マシン側で不足しているものがあるので、次の作業(■ 3.Ruby及び...)に行く前にインストールします。
右のエラーが出るため。「yum update→カーネルアップデート→ VitualBox GuestAddition 破損」
# ローカル $ vagrant ssh # 仮想マシン $ sudo yum install kernel-devel $ sudo yum install gcc make $ exit # ローカル $ vagrant plugin install vagrant-vbguest $ vagrant vbguestファイル共有できているか確認。
$ vgrant ssh $ cd /rails_projects/ $ ls Vagrantfileここまでで仮想マシンの基本的な設定は完了です !
以降は、必要なソフトウェア類をインストールしていきます。
■ 3.必要な関連ソフト及びRuby インストール | 開発環境の作成
▼ 必要な関連ソフトのインストール
yumを最新化して諸々のパッケージをまとめてインストール。
$ vagrant ssh $ sudo yum update $ sudo yum install -y gcc-c++ patch readline readline-devel zlib zlib-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf automake libtool bison nodejs git vi▼ Git インストールされたか確認
$ git --version ruby git version 2.2.2▼ rbenv のインストールと初期化
rbenv をインストールし、初期化を行います。
$ git clone git://github.com/sstephenson/rbenv.git ~/.rbenv $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile $ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile $ . ~/.bashrc▼ ruby-build のインストール
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build $ cd ~/.rbenv/plugins/ruby-build $ sudo ./install.shインストールされると、次のコマンドでインストール可能な ruby のバージョンを表示できます。
$ rbenv install -l▼ Rubyのインストール
$ rbenv install 2.1.5 $ rbenv global 2.1.5 $ rbenv rehashインストールされたか確認。
$ ruby -v ruby 2.1.5■ 4.Mysql, Apache インストール | 開発環境の作成
▼ Mysqlのインストールと初期設定
次のコマンドで Mysql の Yum レポジトリを追加。
$ yum localinstall http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpmインストールできたか確認。
$ yum repolist all | grep mysql mysql-connectors-community/x86\_64 MySQL Connectors Community 有効: mysql-connectors-community-source MySQL Connectors Community - So 無効 mysql-tools-community/x86\_64 MySQL Tools Community 有効: mysql-tools-community-source MySQL Tools Community - Source 無効 mysql55-community/x86\_64 MySQL 5.5 Community Server 無効 mysql55-community-source MySQL 5.5 Community Server - So 無効 \<---注目 mysql56-community/x86\_64 MySQL 5.6 Community Server 無効 mysql56-community-source MySQL 5.6 Community Server - So 無効 mysql57-community/x86\_64 MySQL 5.7 Community Server 有効: \<---注目 mysql57-community-source MySQL 5.7 Community Server - So 無効上記の例では、5.7が有効、5.5が無効になっています。
バージョンの切り替えには Yum の設定変更用の yum-utils パッケージが必要なので、インストールします。
# yum-utilsがインストールされているか確認 $ yum list installed | grep yum-utils # 入ってなければyum-utilsをインストールする $ yum -y install yum-utils # 5.7を無効に設定 $ yum-config-manager --disable mysql57-community # 5.5を有効に設定 $ yum-config-manager --enable mysql55-community設定できているか再確認します。
5.5が有効になっていれば大丈夫です。公式リポジトリにある、mysql-community-server パッケージをインストールします。
Mysql のバージョンが5.5になっているのを確認。$ yum info mysql-community-server ...(略)... Name : mysql-community-server Arch : x86\_64 Version : 5.5.62 Release : 2.el6 Size : 38 M Repo : mysql55-community Summary : A very fast and reliable SQL database server URL : http://www.mysql.com/ License : Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. Under GPLv2 license as : shown in the Description field. Description : The MySQL(TM) software delivers a very fast, multi-threaded, multi-user, ...(略)...インストールします。
$ yum -y install mysql-community-serverインストールしたらバージョンを確認。
$ mysql --version mysql Ver 14.14 Distrib 5.5.62, for Linux (x86\_64) using readline 5.1root やセキュリティの設定を一括で行います。
$ sudo mysql_secure_installationroot で入れるか試します。
$ mysql -u root -p▼ Apache のインストール
yum でインストールできる Apache のバージョンを確認。
$ yum list | grep httpdApache のインストール。
$ sudo yum install -y httpdインストールされたバージョンを確認。
$ httpd -v Server version: Apache/2.2.15 (Unix) Server built: Jun 19 2018 15:45:13起動と自動起動設定。
$ sudo /etc/init.d/httpd start $ sudo chkconfig httpd on■ 5.Rails インストール | 開発環境の作成
▼ bundlerのインストール
$ gem install bundler # バージョンを指定する場合は 「gem install bundler -v '~\>1'」のようにオプションを指定。インストールできたか確認。
$ bundler -v Bundler version 1.17.3▼ Rails + その他のライブラリをインストール
bundler を使って、gemfile に記載された諸々のライブラリ(指定バージョンの Rails を含む)をインストール。
$ bundle install --path=~/vendor/bundle「--path=vendor/bundle」ではないことに注意。
フォルダ共有機能で設定しているディレクトリ(--vendor/bundle)にbundle installしようとするとエラーが出る。
参考記事「rails コマンド」をつかえるように、グローバルに rails をインストール。
$ gem install rails # バージョンを指定する場合は 「gem install rails -v '5.2.4'」のようにオプションを指定。■ 6. Rails の起動
▼ vagrant に設定を反映
vagrant に設定を反映するために、以下を実行します。
# 停止 $ vagrant halt # 立ち上げ $ vagrant up # 仮想マシンへ入る $ vagrant ssh仮想マシン上のデータベースに migration ファイルの設定を反映させます。
$ rails db:migraterails server の起動。
$ rails server -b 0.0.0.0「-b 0.0.0.0」オプション(全てのIPアドレスからアクセス可)は、ブラウザからアクセスできるように指定。
rails server のみだとIPアドレスが 127.0.0.1 に指定され、localhost なので 外部から(仮想環境の外から)はアクセスできません。
参考記事ブラウザで http://localhost:3000/ にアクセス。
まとめ
というわけで、初めてVagrantを使う方に向けて開発環境を構築する手順を紹介しました。
基本的には一つ一つ調べながら手順をまとめましたが、まだ自身も十分に理解できていない部分もあるかと思います。
ミスがあった場合は忌避なく指摘をいただけると幸いです !
参考書籍
- 投稿日:2020-11-25T07:45:07+09:00
個人アプリ開発日記 #5
投稿したり、投稿詳細表示、削除、一覧表示を実装していきます
まずは,何はともあれ
rails g controller drinksrails g model Drinkを実行
コーヒーの感想を投稿しあうアプリなので、モデル名や、コントローラー名をcoffeeにしたかったのですが、
coffeeは不可算名詞なので、できるだけ避けた方が賢明。
という事でdrinkと命名しました、、、!!画像も投稿したいので
% brew install imagemagickをターミナルで実行
gem 'mini_magick' gem 'image_processing', '~> 1.2'というgemをinstallして
bundle installdrinks_controller
class DrinksController < ApplicationController include SessionsHelper before_action :logged_in_user, only: [:index,:destroy] def index @drinks = Drink.includes(:user).order("created_at DESC") end def show @drink = Drink.find(params[:id]) @user = @drink.user @drinks = @user.drinks end def new @drink = Drink.new end def create @drink = current_user.drinks.build(drink_params) if @drink.save redirect_to drinks_path else redirect_to 'new' end end def destroy Drink.find(params[:id]).destroy redirect_to root_path end private def drink_params params.require(:drink).permit(:name,:price,:explain,:image).merge(user_id: current_user.id) end enddef index @drinks = Drink.includes(:user).order("created_at DESC") endは
一覧画面で表示する投稿の情報には、ユーザー情報も紐付いて表示されます。
この場合、投稿に紐付くユーザー情報の取得に、投稿の数と同じ回数のアクセスが必要になるので、N+1問題が発生します。要は投稿に紐ずくユーザーの情報を一挙に取得するから早くなるよってことです
def create @drink = current_user.drinks.build(drink_params)は、user.rbとアソシエーションを組むことで.buildというメソッドが使えます
このメソッドを使うことで勝手にuserと上手い感じに紐付けた投稿が可能になりますdrink.rb
class Drink < ApplicationRecord belongs_to :user has_one_attached :image with_options presence: true do validates :name validates :explain end endwith_optionメソッドはまとめてバリデーションを設定することができるメソッドです。
存在性だけでなく、フォーマットの検証にも使えて便利なのでぜひ調べて使ってみてくださいまずはコーヒーに関する投稿をしていきたいと思います!
投稿する内容は,コーヒーの名前、画像、値段、説明です
drinks/new.html.erb
<%= form_with model: @drink,local: true do |f| %> <%= render 'shared/error_messages', object: f.object %> <%= f.text_field :name, placeholder: "商品名" %> <%= f.text_field :price, placeholder: "値段" %> <%= f.text_area :explain, placeholder: "商品の説明", rows: "10" %> <h3>コーヒーの写真を投稿してください</h3> <%= f.label :image %> <%= f.file_field :image %> <%= f.submit "SEND" %> <% end %>ビューにするとこんな感じ
何か投稿してみましょう
drinks/index.html.erb
<% @drinks.each do |drink| %> <%= drink.name %> <%= image_tag drink.image.variant(resize: '500x250') if drink.image.attached? %> <%= drink.price %> <%= drink.explain %> <%= link_to drink.user.nickname ,drink_path(drink) %> <% end %> <%= link_to "投稿する", new_drink_path %> <%= link_to "トップページ", root_path %>投稿後はdrinks#indexを呼び出すようにしてます
レイアウト崩れまくりですがしっかり画像つきで表示されてて安心しました
次は投稿詳細をみてみましょう
drinks/show.html.erb
<%= @drink.user.nickname%> <% @drinks.each do |drink| %> <%= drink.price%> <span class="name"><%= drink.name %></span> <p><%= drink.explain %></p> <%= image_tag drink.image.variant(resize: '500x250') if drink.image.attached? %> <%= link_to "削除",drink_path(drink.id),method: :delete %> <% end %>今のところは投稿の詳細ページを押したところで殺風景ですが、ここにこの投稿がついたコメントがダラーっと表示される感じにしたいですね、、、!!
削除はまぁいいでしょう、、、、
一通りの基本的な機能はできたので次回は見た目をめちゃくちゃよくしていきたいと思います、、、!!!
- 投稿日:2020-11-25T04:25:42+09:00
【Rails】コントローラーの処理をモデル内のクラスメソッドとして記載する。
テーマ
コントローラー内に処理を記載していき、コードが冗長になったり、同じ処理を様々なアクションで使用する場合、一つのメソッドとしてモデル内で定義することがあると思います。
簡単なやり方と、その際にちょこっとだけつまづいた点をまとめています。バージョン
ruby 2.5.7
Rails 5.2.4.4やり方
shops_controller.rbdef members @table_customers = 2 @tables = 3 @all_customers = @table_customers * @tables Shop.update(members:@all_customers) #店内の総客数を計算するアクション end上記のコードでは店内の客数を計算するアクションになります。
(簡略化のために今回は1つのテーブルの客数を2人、テーブル数を3卓と定義しています。)このアクションをモデル内に定義する場合を順を追って解説します。
shops_controller.rbdef members @table_customers = 2 @tables = 3 Shop.count_members(@table_customers,@tables) endコントローラーの記述
shop.rbdef self.count_members(table_customers,tables) @all_customers = @table_customers * @tables Shop.update(members:@all_customers) endモデルの記述
このままではエラーが発生しますが、解消方法はのちに記載します
・コントローラー側の記載
Shop.count_members(@table_customers,@tables)
Shop = メソッドを定義したモデル名
count_members = メソッド名(モデル内に記載してあるもの)
(@table_customers,@tables) = 処理する際に渡す変数それぞれの記述を簡単に説明するとこのようになります。
・モデル側の記述
def = メソッドを定義
self = クラスメソッドを使用する際に記述(インスタンスメソッドの場合は不要)
count_members = メソッド名
(table_customers,tables) = コントローラーで受け取った値を変数としてそれぞれ再定義(今回の場合は、table_customers = 2, tables = 3 になる)インスタンスメソッドとクラスメソッドの違いについては別記事を参照して見てください。今回はクラスメソッドを使用しています。
注意ポイント①
前述した通り、単純に処理内容をコピペした上記のコードでは想定した処理が行われずエラーが発生します。
その理由はモデル内で変数を再定義していることため
です。上記のコードでは、コントローラーで定義した@table_customers,@tablesの値をモデルに渡し、table_customers,tablesとして再定義していますが、モデルメソッド内で@table_customers,@tablesが使われており、そのような変数は定義されていないと言うことでエラーが発生します。
コントローラーで定義した変数は、モデルの処理では使えない
と言う感じで覚えれば問題ないかと思います。そのため、エラーを解消するには、、、
shop.rbdef self.count_members(table_customers,tables) all_customers = table_customers * tables #2つの変数の@を削除 Shop.update(members:all_customers) endモデル内の@table_customers,@talesの@を削除することで正常に動作します。
(all_customersはメソッド内で新たに定義される変数なので@を削除しなくても動作自体は問題ありませんが、消去した方がベターです)注意ポイント②
また、モデル内で定義した変数をメソッド外の処理や、view内で使用したい場合は注意が必要です。
コントローラーで定義した変数がモデル内で使用できないのと同様に、モデル内で定義した変数もモデル外では使用することができません。クラスメソッド内で定義した変数をview等で使用したい場合は
戻り値
としてコントローラーに返す必要があります仮にall_customersを戻り値として返したい場合
shop.rbdef self.count_members(table_customers,tables) all_customers = table_customers * tables #2つの変数の@を削除 Shop.update(members:all_customers) return all_customers #returnメソッドで戻り値を指定 endshops_controller.rbdef members @table_customers = 2 @tables = 3 @all_customers = Shop.count_members(@table_customers,@tables) #戻り値を格納するための変数を定義 endモデル内で戻り値の指定、コントローラー内で変数の定義を行うことで、モデル内で扱った値をモデル外でも使用できます。また、カンマで区切ることで、複数の戻り値を取得することも可能です。
- 投稿日:2020-11-25T03:40:57+09:00
【Rails】Mysql2::Error::ConnectionErrorの解消
Railsアプリにバリデーションを定義した後の、テストデータ登録の成功が目的の備忘録です。その為、バリデーションの定義、記述は終わっている状態です。
データ保存したいのに、Mysql2に接続できない
Docker環境下のRailsアプリで、app/modelsにバリデーションを定義し、動作チェックの為にMySQLにデータ登録を試みました。そうしたら.....
Mysql2::Error::ConnectionError at /Unknown MySQL server host 'db' (0)MySQLに繋がってないよと怒られました。
全く同じエラーにハマっている方も居ましたが、解決策は載っていません。
記事に載っていたdocker-compose upを実践するも、dockerは正常に起動していました。いずれにせよ、データが登録できていないので、次はデータベースのチェックを行いました。
①rails db:create ②rails db:migrateしかし、ダメでした。
下記はrails db:create実行後にターミナルに出力されたエラーUnknown MySQL server host 'db' (0) Couldn't create 'myapp_development' database. Please check your configuration. rails aborted! Mysql2::Error::ConnectionError: Unknown MySQL server host 'db' (0) /Users/athlaliel/test/qa-coin/bin/rails:9:in `<top (required)>' /Users/athlaliel/test/qa-coin/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Tasks: TOP => db:create (See full trace by running task with --trace)この後も、色々試した結果、原因が分かりました。
Docker環境でアプリを作成しているのに、実際はローカル環境でひたすらコマンドを叩いていました。Docker上のコンテナでMySQLも管理されてる以上、見つかるわけがなかったです。
下記のコマンドを順に実行する事で無事にデータベースは作成され、更新も可能となりました。
①docker-compose exec web rails db:create ②docker-compose exec web rails db:migrateバリデーションのデータ登録
データベースがDocker上に出来たので、テストデータを登録しバリデーションが機能しているか試します。rails consoleを起動し、テストデータを登録していきます。
バリデーションに記述する文言はここでは書きませんが、変数を定義して<モデル名.create>と入力します。
このコマンドでデータ登録が成功すれば、SQLと同じ文言が表示され、データの保存まで行います。その為、別途<モデル名>.saveコマンドを入力する必要はありません。
もしエラーが起きたら、下記コマンドでチェックして原因の追求を行います。pry(main)> <モデル名>.errors ※user.rbというmodelsファイルがあれば、user.errorsとターミナルに入力します。Mysql2::Error::ConnectionErrorのようなエラーに遭遇したら、自分が今どの環境で作業しているかのチェックを行ってみると良いでしょう。
参考記事
Unknown MySQL server host 'db' (0)エラーが消えない
Active Record バリデーション