- 投稿日:2020-08-30T22:05:48+09:00
【Rails】間違えたマイグレーションファイルをdown,削除できない→rails db:migrate:resetで解決できた
前の記事である、
【Rails】テーブルにカラムを追加する際、キャメルケース以外で記載したらどうなるの?
にて、自分が感じた疑問を素直にコマンドで実行したら、(複数作成した)不要なファイルがdown・削除が出来なくなりました(汗)今回、たくさんの気づきがありましたので、反省を込めて記事にしようと思います。
出来ないことと、エラー内容
ターミナルにおいて、不要ファイルを削除したいが、削除以前に、down状態にできない(rollbackできない)
#ターミナルに表示されるエラー foo@fogefogenoMacBook-Air bar % bundle exec rake db:migrate:down VERSION=20200829125501_Nickname2.rb rake aborted! Invalid format of target version: `VERSION=20200829125501_Nickname2.rb` /Users/fogefoge/.rbenv/versions/2.6.5/bin/bundle:23:in `load' /Users/fogefoge/.rbenv/versions/2.6.5/bin/bundle:23:in `<main>' Tasks: TOP => db:migrate:down (See full trace by running task with --trace)解決策
先に解決した方法を載せておきます。
私は現在、プログラミングスクールに通っており、メンターさんに助けていただきました。
(本当に、ありがとうございました。)1.Railsファイル上で、自分がこうしたいというファイルの状況を作り出す。
①左側で、削除したかったファイルを全て右クリック押して削除
②今回残しておきたい「20200829063700_devise_create_users.rb」のファイル中に、右側9行目にあたる(今回テーブルにカラムとして追加したかった)nicknameを追加して記載。2.ターミナル上で、以下のコマンドを実行する。
「rails db:migrate:reset」とは、削除して作ってマイグレートしてくれるコマンドだそうです。#ターミナル上で、以下コマンドを実行 foo@fogefogenoMacBook-Air bar % rails db:migrate:reset#実行後、表示された内容 Dropped database 'pictweet_development' Dropped database 'pictweet_test' Created database 'pictweet_development' Created database 'pictweet_test' == 20200825113455 CreateTweets: migrating ===================================== -- create_table(:tweets) -> 0.0130s == 20200825113455 CreateTweets: migrated (0.0131s) ============================ == 20200829063700 DeviseCreateUsers: migrating ================================ -- create_table(:users) -> 0.0412s -- add_index(:users, :email, {:unique=>true}) -> 0.0603s -- add_index(:users, :reset_password_token, {:unique=>true}) -> 0.0429s == 20200829063700 DeviseCreateUsers: migrated (0.1469s) =======================出来ましたー!!
そもそも、やらかしたこと
ここからは、デバック中含め「やらかしてしまったなー」と感じたポイントを書いていきます。
やらかしたことその1
一つ一つ試さないで(ロールバックしないで)、(興味本位で)不要なマイグレーションファイルを一気に複数作ってしまったこと。
やらかしたことその2
実験→正常(カリキュラム通り)→実験→実験の順で行ったこと。
rollbackの方法では、手前から差し戻しするんだったと、デバック中に思い出す。
途中に、正常を挟んでしまったため、rollbackで戻すなら「いったん全戻し」かつ「正しいの作り直し」になってしまうことに気づく。やらかしたことその3
何も考えず、実験に使った不要マイグレーションファイルを右クリックして、一気に削除したこと。
ふと、削除ってこんな感じでいいんだっけ?そういえばrollbackっていう単語あったけどそれやってないけどいいんだっけ?と疑問に思い調べたところ、
●右クリックでマイグレーションファイルを消すとファイル上は削除されるが、ターミナル上は消えない(Rails内に残ってしまう)
●消したファイルは見えないので、同じファイル名などで今後作成したときバグになりうることがある
という事がわかった、対処し始めた。やらかしたことその4
やみくもにデバックしてしまい、(履歴も上手に直さず)、沼に陥ったこと。
特に、「あ、サーバーが起動しているからダメなのかな?」と思ったが、コントロールC(サーバーを落とすショートカットキー)が効かず、ターミナルを×で閉じてしまったこと。コマンド履歴消えた。やみくもにデバックしてしまったこと
ここからは、上記「やらかしてしまったこと」に気づき、やみくもにデバックしてしまったことを書いていきます。
やみくもデバック その1
キータで見つけた「NO FILE」の削除方法をやってみよう。
参考にさせていただいた記事
【Rails】マイグレーションファイルの削除
Ruby on Rails 『NO FILEのmigrationを削除する方法』?touthして、migrateして、downしようと思ったが、downがうまくいかない。
?キータで書かれていた通り、touth時に、ファイル名を消した名前ではなく「hoge」名にしたからか?やみくもデバック その2
ファイル名を、「hoge」ではなく、削除した名前に変えてみよう
?うまくいかないやみくもデバック その3
クラス名がそういえば一致してないや
?うまくいかない
クラス名とファイル名が違うからこのエラーでは?と気づけた記事やみくもデバック その4
そういえば、上記1〜3について、手前のファイルではなく、一番古いファイルからコマンド実行していた。
一番古いファイルではなく、手前のファイルからやってみよう。
?うまくいかない
手前から直さないといけないことに気づけた記事やみくもデバック その5
そういえば、サーバー立ち上げていたからかな?「コントロールC」のショートカットキーが上手くいかず×で閉じる。
?うまくいかないし、履歴も消えてしまったし、本当にミスった。やみくもデバック その6
そもそもちゃんとエラー見てなかった。binって書いてある。
大枠のフローが載っている処理っぽい。
クラス名やファイル名が、存在していないよっていうエラーぽいけど、上記を行っても上手くいかない。以上です。
まとめ
調べれば調べるほど、マイグレーションファイルは安易に追加したり、削除したりしてはいけないことを痛感しました。
特にチーム開発の時では、ファイル削除だけだと、他の人が何をしたのか見えないため、バグの原因になってしまうことを学べたのが大きかったです。
また何度も、マイグレーションの状況を確認したり、downさせたいコマンドを打ったりしたので、マイグレーションに対する理解も深まりました。
そして、
●やみくもデバックしてしまうと沼にハマることを痛感
●やみくもデバックしないためにもエラーが起こった時に、いろんな可能性が選択肢として浮かぶレベル
まで成長したいと思いました。メンターさんには、解決策と温かい言葉をかけていただいて本当に感謝です。
読んでいただいてありがとうございました。
- 投稿日:2020-08-30T21:22:26+09:00
【エラー】アソシエーション
特技はエラー生成・・・
アソシエーションの蓋を開けてみれば大したことのないエラーです。
<% current_user.rooms.each do |room|>userというテーブルとroomテーブル関係のアソシエーションを組んでいた記述で、roomsがないよと言われたんですが、どこを探しても間違っていないと思いきや、アソシエーションはやっぱりアソシエーションが原因でした。
/models/room.rb
has_many :room_users has_many :users, through: :room_users/models/user.rb
has_many :room_users has_many :users, through: :room_usersここで何が違ったのか全く気づけなかったのですが、アソシエーション指定がどちらもusersになっていました。
なので
/models/user.rbhas_many :room_users has_many :rooms, through: :room_usersでOKです。
- 投稿日:2020-08-30T21:06:40+09:00
【エラー】localhostに接続できなかったサーバーの重複
特技はエラー生成・・・
localhost:3000にアクセスしたら急に読み込みが止まったままになってしまい、ターミナルを確認したところ
こんなような記述が(他略)Address already in use - bind(2) for "127.0.0.1" port 3000 (Errno::EADDRINUSE)原因は、ローカルサーバー(rails s)を切らずに(control cをせずに)ターミナルのタブを消してしまっていたので接続が重複して飽和状態になっていたとのこと
lsof -i :3000
を入力してポート接続を確認→PIDの番号を確認kill -QUIT <PIDの番号>で、ポートを空けてあげると直りました。
こちらの記事を参考にしました
https://qiita.com/nagao_norihiro/items/aba40bd4e0eac9f9a92d#comments
- 投稿日:2020-08-30T19:45:32+09:00
Rails label_tag 英語が小文字表示になってしまう(humanize)
はじめに
label_tag
を用いてフォームを作成していた時にニュース ID
をnameに指定しても表示がニュース id
となってしまうことが起きたので、Rails
のソースコードを読んで小文字変換されてしまう理由を調べてみました。前提として
<%= label_tag 'ニュース ID' %> > ニュース idソースコード
# File actionview/lib/action_view/helpers/form_tag_helper.rb, line 215 def label_tag(name = nil, content_or_options = nil, options = nil, &block) if block_given? && content_or_options.is_a?(Hash) options = content_or_options = content_or_options.stringify_keys else options ||= {} options = options.stringify_keys end options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for") content_tag :label, content_or_options || name.to_s.humanize, options, &block end引用先
https://api.rubyonrails.org/最初にあげたコードでは
name
しかしてしていないので、content_or_options
,options
,block
は引数として渡していない。
のでコード読んでいくと、最後の'name.to_s.humanize'が怪しそうだったのでhumanize
について調べてみます。humanize
属性名(英語)を調整してくれるメソッド。
具体的には
先頭にアンダースコアがある場合は削除します。
末尾に"_id"がある場合は削除します。
アンダースコアがあれば、スペースに置き換えます。
頭文字を除くすべての単語を小文字にします。
最初の単語を大文字にします。
最初の単語の大文字化は、 :capitalize オプションを false に設定することでオフにすることができます(デフォルトは true)。
オプションのパラメータ keep_id_suffix を true (デフォルトは false) に設定することで、末尾の '_id' を保持して大文字化することができます。
humanize('news_number') # => "News number" humanize('news_id') # => "News" humanize('news_id', capitalize: false) # => "news" humanize('_id') # => "Id" humanize('news_id', keep_id_suffix: true) # => "News Id"今回の場合だと
humanize('ニュース ID') # => "ニュース id"
頭文字を除くすべての単語を小文字にします。
こちらが適応されていてid
が小文字になってしまっていたということでした。最後に
ロジックを知るためにも、Railsのソースコードを読むということが良いとわかっていつつも、二次情報に頼ってしまっていたということもあったので、理解度を上げるためにもちゃんと読まないとダメだなーと感じました。その上で二次情報なども活用することで理解度を高めていけるのかなと勉強になりました。
- 投稿日:2020-08-30T18:46:20+09:00
rails AWSのデプロイが反映されない場合
- 投稿日:2020-08-30T18:46:01+09:00
[過去POST][初心者向け]ページネーションの原理がわからない。railsにて
過去POST
過去自分がTECH::CAMPのメンターをしていた時期にメモしていた内容を公開します。
rails でよしなにやってくれるので知らなくてもいいのですが、たまに聞かれることがあったので書きました。
初心者向けのざっくりとした解説です。Q.ページネーションの原理がわからない。
class TweetsController < ApplicationController def index @tweets = Tweet.order("created_at DESC").page(params[:page]).per(5) end def new end def create Tweet.create(tweet_params) end private def tweet_params params.permit(:name, :image, :text) end endここで
.page(params[:page]).per(5)
とありますが、これが納得できないという人がいました。
これもSQL文の発行に関わるものです。
# page(params[:page]).per(5) params[:page]= 1の場合SQLは(順番は一旦無視します。)
SELECT `tweets`.* FROM `tweets` LIMIT 5 OFFSET 0ページが代わり
# page(params[:page]).per(5) params[:page]= 2の場合
SELECT `tweets`.* FROM `tweets` LIMIT 5 OFFSET 5となります。
ここで重要なのがLIMIT 5 OFFSET 5です。
まず
LIMIT 5
は、データをmax5件まで取得するという意味です。
OFFSET 5
は、データを前から5件をスキップするという意味です。つまり
SELECT `tweets`.* FROM `tweets` LIMIT 5 OFFSET 5というのは、ツイートテーブルから、ツイートレコードを前から5件飛ばし、
その後最大5件のレコードを取得してくるというものです。
- 投稿日:2020-08-30T18:41:24+09:00
[過去POST][初心者向け]N+1問題が理解できない。.includesを使うとどうなるのか(Railsにて)
過去POST
過去自分がTECH::CAMPのメンターをしていた時期にメモしていた内容を公開します。
N+1問題が理解できない。includes(:user)とする意味がわからない。
class TweetsController < ApplicationController # 中略 def show @tweet = Tweet.find(params[:id]) @comments = @tweet.comments.includes(:user) end # 中略 endで使用している。
これはなにも考えなければ
@comments = @tweet.comments.includes(:user)
の部分は
@comments = @tweet.comments
でもできる。しかしこれではN+1問題が発生してしまう。
(要は、SQLの発行回数が多くなってしまう。)【問題の概要】
この文の意味は
詳細を表示したツイートについているコメントのレコードを取得
コメントのレコードに紐つくユーザーレコードの取得をしている
単純に
@tweet.comments
のように書くと、コメントレコードごとにユーザーレコードを取ってくるという動作をしてしまう。
つまりSQL文でかくと、
(tweetは一旦省略、コメント数は4件、ユーザーは2人とする。)SELECT 'comments'.* FROM 'comments’を実行すると
SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 1 LIMIT 1 SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 2 LIMIT 1 SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 2 LIMIT 1 SELECT 'users'.* FROM 'users' WHERE 'users'.'id' = 2 LIMIT 1のようにcommentsレコードの数だけ、userレコードを取得するクエリが発行される。
これがN+1問題。では
.includes(:user)
をつかうとどうなるのか
SELECT 'comments'.* FROM 'comments' SELECT 'users'.* FROM 'users' WHERE 'users'.'id' IN (1, 2)のように一文で終わる。
これがincludes
の意味です。
(:user)
はuser_id
ですね。
- 投稿日:2020-08-30T18:36:19+09:00
[過去POST]TECH::CAMPのメンターをしていた時の回答メモを一部公開します
過去POST
過去自分がTECH::CAMPのメンターをしていた時期にメモしていた内容を公開します。
小分けにしようとしましたが小粒なものはまとめておきます。
記憶が曖昧なので間違っている箇所もあるかもしれないです…Q.ハッシュの中身が取り出せない
movie = {"title" => "ハリーポッター", "genre" => "ファンタジー", "year" => "2001年"}
から取りたい文字列を取るコードを書きましょう。
のような問題だったと思います。該当コード
def movie_info(movie, data) puts movie[data] end movie = {"title" => "ハリーポッター", "genre" => "ファンタジー", "year" => "2001年"} puts "以下から一つを選んで入力してください。 ・title ・genre ・year" info = gets.chomp movie_info(movie, info)解説
ここで
puts movie[:data]
としてしまうとうまくいかないです。
その原因は型が違うからです。
info = gets.chomp
ここでキーを「文字列」として受け取っているので
ハッシュの定義も文字列"title"を使っているのでここでは問題はおこりません。
puts movie[:data]
一方で、こう記述すると、シンボル型で出力してしまい、
型が違うためなにも出力されません。ちなみに型を調べるのは.classを使うと調べられます。
ex) info.class
もしシンボル型を使いたいなら
movie = {title: => "ハリーポッター”}
のようにハッシュの定義を変えて
info = gets.chomp.to_sym
のように書き換えるとうまくいきます。
puts movie[data.to_sym]
でも大丈夫です。(シンボル型に変換)Q.devise, no method error
devise関連でno method errorが出た時。current_sign_in_atがないなど。
対処方法
deviseによって作成した、該当のマイグレーションファイルを確認
該当箇所周辺のコメントアウト外して
データベースを作成し直す
$ rake db:migrate:reset
このコマンドで一気にできます。このコマンドは、データベースを一度ドロップして、
今あるマイグレートファイルを元にデータベースを作り直すというコマンドです。
もちろんデータベースに入っていたデータは消えます。
色々データが入っていて残しておきたい場合、CSVでデータを出力してどっかに保存してからやるべきかと思います。直前にマイグレートしたものならrollbackで戻っても良いかもしれません。
$ rake db:migrate:status
で現在どのファイルがmigrateされているのか確認できます。
*ちなみに
db:reset
やっても意味ないです。スキーマファイルから作り直すだけなので。原因
deviceをインストールした際、デフォルトでコメントアウトされている項目をそのままにしてしまい
使用したいメソットが生成されなかったため。
もう一度マイグレーションをやり直すことで解決するはず。Q.heroku上で、idが10刻みで付与される
開発環境では問題ないが、本番環境でのみ、変わってしまう。
あまり気になる人はいない気がしますが、これはclearDBのデフォルトの設定です。
herokuでは、dbサーバーとしてcleardbを使っているようです。【参照】
http://w2.cleardb.net/faqs/#general_16
When I use auto_increment keys (or sequences) in my database, they increment by 10 with varying offsets. Why?
ClearDB uses circular replication to provide master-master MySQL support. As such, certain things such as auto_increment keys (or sequences) must be configured in order for one master not to use the same key as the other, in all cases. We do this by configuring MySQL to skip certain keys, and by enforcing MySQL to use a specific offset for each key used. The reason why we use a value of 10 instead of 2 is for future development.
- 投稿日:2020-08-30T18:27:14+09:00
【自分用メモ】ポリモーフィックな関連付けをして、フォームを作成する場合の方法論
ポリモーフィックな関連付けをした場合のフォーム
PlayerモデルとManagerモデルがあったとして、それぞれにTagモデルをポリモーフィック関連付けをしたい。
ポリモーフィック関連を扱ったアプリでフォームを作成したい場合、何がベストなのか考えている。ActiveRecordのおかげでTagオブジェクトに紐づく親オブジェクトは、
@tag.taggable
で簡単に取得できる。ただ、PlayerオブジェクトやManagerオブジェクトに紐づくTagオブジェクトを新規に作成する場合、
どのように対応するのがベストなのか色々と考えている。ちなみに、ポリモーフィック関連でない「1対多」のアソシエーションは以下のような形になる。
def tag_params params.require(:tag).permit(:body).merge(player_id: params[:player_id]) endポリモーフィック関連の特有の問題
ここで問題なのが、merge以下の箇所。
params[:player_id]
の場合もあれば、params[:manager_id]
の場合もある- ポリモーフィックなので、
taggable_type(紐付け先のテーブル名)
についての情報も取得する必要があるそこで、いくつか方法論を調べたり、考えたりしてみた。
例えば、Playerモデルに紐づくTagオブジェクトをcreateする場合で考えてみた。
- 第1の手段
params[:player_id]
だけでなく、隠しパラメータとしてparams[:taggable_type]
を送ってもらう- paramsを
Tag.new
に上書きする- データベースを経由しないので、悪手である気がする
- 第2の手段
- 同じく隠しパラメータとして
params[:taggable_type]
も送ってもらう- paramsをもってデータベースにアクセスする
- 紐付け先のテーブルのレコードに基づく形で、
.tags.build
して新しいTagオブジェクトを生成する- 中身に関するparamsで上書きする
- 第3の手段
- requestURLを活用する形でデータベースにアクセスする(RESTfulなURL設計にする必要がある)
- 紐付け先のテーブルのレコードに基づく形で、
.tags.build
して新しいTagオブジェクトを生成する- 中身に関するparamsで上書きする
- 第4の手段
- ルーティングでネストし、コントローラのディレクトリを切る
player/tags_controller.rb
にて、params[:player_id]
をもってデータベースにアクセスする- 紐付け先のテーブルのレコードに基づく形で、
.tags.build
して新しいTagオブジェクトを生成する- 中身に関するparamsで上書きする
- 第5の手段
accepts_nested_attributes_for
を活用する- 方法についてはまだ調べていない
- 複数のモデルに関する属性を更新する場合に使うとよさそう(今回は若干過剰?)
- フォームオブジェクトも検討すべき?(これこそ過剰な感じがする)
第1の手段について
自分なりに考えてみた方法なので、悪手の可能性が高い。
(ちなみに第1と第2の方法を掲載したブログを発見することはできなかった。)Tagオブジェクトが紐づく先のモデルが異なると、
フォームから送信されるparamsのハッシュのキーが異なってしまう。つまり、
player_id: 2
やmanager_id:3
というparamsが送られてくる場合、
strong_paramsをどうするのか問題を解決する必要がある。そこで、フォームから送られてくるparamsの形式を揃えてしまえばよい。
- 隠れパラメータとして
taggable_id
とtaggable_type
をフォームから送る
<%= form.hidden_field :taggable_id, value: player.id %>
<%= form.hidden_field :taggable_type, value: player.class %>
- もちろん、Managerオブジェクトであれば、valueは
manager.id
やmanager.class
になる- 隠れパラメータを活用して、
Tag.new
を更新する
- データベースを経由していないので、存在しないテーブルに紐づくレコードが保存されてしまうかも
- ただし、モデルの方での制約はあると思われる(未検証)
tags_controller.rbdef create @tag = Tag.new(tag_params) # 他は省略するが`@tag`をsave + redirectする(失敗した場合、render) end private def tag_params params.require(:tag).permit(:body, :taggable_id, :taggable_type) end第2の手段について
発想としては、ほぼ第1の手段と同じである。
第1の手段の場合、データベースを経由せず、
Tag.new
にtaggable_id
やtaggable_type
といった
paramsを引数として直接指定しているので、データベースを経由するような形にマイナーチェンジする。tags_controller.rbdef create @tag = @taggable.tags.build(tag_params) # 他は省略するが`@tag`をsave + redirectする(失敗した場合、render) end private def set_taggable # constantizeメソッドを使うことで文字を定数化できる @taggable = tag_params[:taggable_type].constantize.find(tag_params[:taggable_id]) end def tag_params params.require(:tag).permit(:body) end第3の手段について
こちらについては、いくつかのブログで情報が出てきた。
requestURLのパスを活用する方法である。tags_controller.rbdef create @tag = @taggable.tags.build(tag_params) # 他は省略するが`@tag`をsave + redirectする(失敗した場合、render) end private def set_taggable # `/players/2/tags`の場合、'players'と'2'という要素を取得できる resource, id = request.path.split('/')[1,2] # `/player/2/tag`の場合、Player.find(2)となる # singularizeメソッドにより、'players'が'player'になる # classifyメソッドにより、'player'が'Player'になる # constantizeメソッドを使うことで文字を定数化できる @taggable = resource.singularize.classify.constantize.find(id) end def tag_params params.require(:tag).permit(:body) end第4の手段について
こちらについては、GoRailsという英語のサイトで紹介されている。
猫Railsさんのブログから見つけることができた。
- Comments With Polymorphic Associations (Example) | GoRails
- GitHub: gorails-screencasts/gorails-episode-36
ルーティングでplayerやmanagerのresourcesの下に、tagsのresourcesをネストさせてしまい、
コントローラのディレクトリを分ける方法である。
tags_controller.rb
という親玉コントローラ- 親玉コントローラを継承する
player/tags_controller.rb
という子分コントローラ- その親玉コントローラを継承する
managers/tags_controller.rb
という子分コントローラ以上3つのコントローラを作ってしまうのであまりDRYではない気もするが、
モデルによって異なるロジックを書きたい場合、こちらの方法を採用する方がよいだろう。Railsの規約に則っているような気がするので、この方法がベストなのかなという気がする。
(印象論で適当に言っているだけですが)routes.rbresources :players do resources :tags, module: :players endtags_controller.rbdef create @tag = @taggable.tags.build(tag_params) # 他は省略するが`@tag`をsave + redirectする(失敗した場合、render) end private def tag_params params.require(:tag).permit(:body) endplayers/tags_controller.rb# tags_controller.rb を継承する class Players::TagsController < TagsController before_action :set_taggable private def set_taggable @taggable = Player.find(params[:player_id]) end end第5の手段について
accepts_nested_attributes_for
を使う方法もある。
この方法については、いくつかのブログがヒットした。調べだすと大変なので、詳細についてはここで書かないこととするが、
複数のモデルに関する属性を一度に作成したり更新したい場合、積極的に検討して良いかもしれない。また、
accepts_nested_attributes_for
の利用はDHH的にあまり推奨されていない
という話を聞いたことががあるので、フォームオブジェクトを作ってみるのがよいかもしれない。
(今回の場合にそこまでやると、実装がかなり大変になってしまうけど)注意書き
初学者が書いています。
しかも読みやすさという点でも完成度が低いので、保険をかけてます。
- 投稿日:2020-08-30T18:14:23+09:00
【ruby】splitメソッドで、文字列から配列にしたい。逆も然り。
文字列を配列化させて、配列を文字列にして、、、というパターンの時に、どういう書き方が期待する値になってくれるでしょうか。
ということで、ダメなパターンも交えて、整理していきましょう!
文字列を配列にする
splitメソッド
を使っていきます。split 英訳:分断する
つまり、まとまりのあるもの(文字列等)を分断させて、要素とさせるメソッドになります。(配列化)
引数は、切り取る部分の境目を指定するイメージになります様々なまとまりのパターンから、配列化をしてみました。
させたい配列は、["foo", "bar", "baz"]
とします。さて、どの書き方が合っているでしょうか。
# 配列化 # 返り値 "foo bar baz".split ["foo", "bar", "baz"] # ○ "foo bar baz".split('') ["f", "o", "o", " ", "b", "a", "r", " ", "b", "a", "z"] "foo bar baz".split(',') ["foo bar baz"] "foobarbaz".split ["foobarbaz"] "foobarbaz".split('') ["f", "o", "o", "b", "a", "r", "b", "a", "z"] "foobarbaz".split(',') ["foobarbaz"] "fooxbarxbaz".split('x') ["foo", "bar", "baz"] # ○ "foo, bar, baz".split ["foo,", "bar,", "baz"] "foo, bar, baz".split('') ["f", "o", "o", ",", " ", "b", "a", "r", ",", " ", "b", "a", "z"] "foo, bar, baz".split(',') ["foo", " bar", " baz"] "foo,bar,baz".split ["foo,bar,baz"] "foo,bar,baz".split('') ["f", "o", "o", ",", "b", "a", "r", ",", "b", "a", "z"] "foo,bar,baz".split(',') ["foo", "bar", "baz"] # ○ %w[foo bar baz] ["foo", "bar", "baz"] # ○この中から、期待する配列になる書き方は、4通りということになります。
配列から、文字列にしたい
joinメソッド
を使っていきます。
引数に、要素と要素の間に差し込む値を入れていくイメージになります。# 文字列化 # 返り値 ["foo", "bar", "baz"].join "foobarbaz" ["foo", "bar", "baz"].join('') "foobarbaz" ["foo", "bar", "baz"].join(',') "foo, bar, baz"おまけ 範囲を配列化
# 範囲を配列化 #戻り値 (0..9).to_a [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ('a'..'z').to_a ["a",..,"z"] (1..5).map{ |i| i**2 ) [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- 投稿日:2020-08-30T16:02:03+09:00
RailsのDeviseとセットアップ方法
はじめに
この記事は、インターネット上の百科事典や技術情報サイト、勉強会を基に、RailsにおけるDeviseについて復習しやすいようにまとめた備忘録です。
技術的に誤っている点がございましたら、ご指摘いただけますと幸いです。Deviseとは?
DeviseとはRailsで作成したアプリケーションに認証機能を実装できるgemの一つです。
Deviseを利用することでログイン機能を簡単に実装することができます。Deviseの使い方
Gemfileの編集とインストール
Gemfilegem 'devise'以下の記事のGetting startedを参考にインストールします。
https://github.com/heartcombo/devise#getting-started$ bundle installGemのインストール完了です。
Deviseのセットアップ
$ rails g devise:installDeviseの設定ファイルをRailsアプリケーションにインストールするためのコマンドです。
$ rails g devise UserDeviseで認証するためのモデルを作成するコマンドです。
今回はUserモデルを作成すると想定してUserを入力しています。$ rake db:migrate作成したモデルを基にデータベースを作成します。
Rails5以降をインストールしている場合は、$ rails db:migrate
コマンドを使えます。$ rails g devise:viewsapp/views/devise配下にDeviseで使用するビューを作成するコマンドです。
Deviseに機能を追加する
Deviseには10個のモジュールが用意されています。
以下の各モジュールの概要についてはRailsにおけるDeviseとは?から引用させていただいております。
機能 概要 database_authenticatable サインイン時にユーザーの正当性を検証するためにパスワードを暗号化してDBに登録します。 registerable 登録処理を通してユーザーをサインアップします。また、ユーザーに自身のアカウントを編集したり削除することを許可します。 recoverable パスワードをリセットし、それを通知します。 rememberable 保存されたcookieから、ユーザーを記憶するためのトークンを生成・削除します。 trackable サインイン回数や、サインイン時間、IPアドレスを記録します。 validatable Emailやパスワードのバリデーションを提供します。独自に定義したバリデーションを追加することもできます。 confirmable メールに記載されているURLをクリックして本登録を完了する、といったよくある登録方式を提供します。また、サインイン中にアカウントが認証済みかどうかを検証します。 lockable 一定回数サインインを失敗するとアカウントをロックします。ロック解除にはメールによる解除か、一定時間経つと解除するといった方法があります。 timeoutable 一定時間活動していないアカウントのセッションを破棄します。 omniauthabl intridea/omniauthをサポートします。TwitterやFacebookなどの認証を追加したい場合は追加します。 Deviseが用意しているメソッド
current_user現在ログインしているユーザーを取得することができるメソッドです。
備考
Deviseの導入方法と追加できる機能についてまとめさせていただきました。
Deviseは趣味で何回か利用していますが、学習内容をまとめておくことで理解度が高まったように感じます。
ここまで拝読していただき、ありがとうございました。参考文献
DIVE INTO CODE MEDIA
- RailsにおけるDeviseとは?
Github
- heartcombo/devise
Railsについての知識を交換するコミュニティ
- 【オンライン開催】銀座Rails#24@リンクアンドモチベーション
- 投稿日:2020-08-30T14:09:17+09:00
Railsで一つの画面に、異なる2モデルのレコードを表示させたい(検索機能付き)
環境
Rails 4.2
mac OS Catalina 10.15.5やりたいこと
- 一つの画面に、異なる2モデル(OldHistoryとNewHistory)のレコードを日付順に表示させたい
- 検索フォームを設け、一度に両方のモデルのレコードに対して検索をかけたい
この2つのモデルは扉を開けた、閉じた、というアクションの履歴を記録していくものになります。
なぜ2つあるかというと、仕様変更により別のモデルからも似たようなアクション履歴が来るようになりましたが、前のモデルとデータが微妙に異なる為、旧モデル(OldHistory)と新モデル(NewHistory)と分かれています。方法
- 検索フォームの条件で各モデルのレコードを絞り込んだオブジェクトの配列を作成、合体させて一つの配列を作りviewに渡す
- 検索フォームの条件で各モデルのレコードを絞り込んだオブジェクトの配列を作成、それぞれをViewに渡す
1の方法は、まさに1つの画面に新旧モデルのレコードを混ぜて日付順に表示させることになります。
2の方法は、OldHistoryとNewHistoryでブラウザ上の表示が分かれてしまうので、同じURLでタブでの非同期切り替えを実装すれば一つの画面であると言えそうです。ページネーション 機能はKaminariを利用しています。
※以下は実際のコードとは名称の一部変更・コードの省略等しています。
1の方法で進める
1の仕様がベストなので、この方針で実装を進めました。
(ただしこの方法は結局うまくいかず、少し改良して2の方法で実装しましたが、振り返りの為に当時の流れを追って書いていきます。)検索機能はransackは使わずに実装しました。ページネーション機能はKaminariを利用しています。
コントローラーはnewの方を使います。
history_search_paramsで絞り込み条件をもらって、新たに作ったhistory_search
というscopeに渡しています。コントローラー
new_histories_contoroller.rbdef index @search_params = history_search_params old_histories = OldHistory.includes_for_histories.history_search(@search_params) .order(occured_at: :desc) new_histories = NewHistory.includes_for_histories.history_search(@search_params) .order(occured_at: :desc) old_and_new_histories_array = (@old_histories + @new_histories) @old_and_new_histories = Kaminari.paginate_array(old_and_new_histories_array).page(params[:page]).per(30) end private def history_search_params params.permit(:occured_at_from, :occured_at_to, :user_name, :item_name_or_room_name, :event) end希望としては
@old_and_new_histories = (old_histories + new_histories).page(params[:new_history_page]).per(30)といきたいところですが、
(old_histories + new_histories)
をするとオブジェクトのクラスがActiveRecord_AssociationRelation
ではなくArray
クラスになってしまい、pageメソッドが使えなくなってしまったので、paginate_array
というKaminariのメソッドに引数を渡しています。モデル
モデルでは新たに作った
history_search
というscopeをさらに細かいscopeに分け、パラメータの中身ごとにSQL直書きでLIKE検索で絞り込みを行っています(実際のコードはかなり深いアソシエーションを探索するのでSQL直書きでなんとか実装しました)。
.blank?
を各scopeに設けることで絞り込み条件がなかった場合には余計な処理をさせずに次のscopeに移るようにしています。
history_search
というscopeはold_history.rb
とnew_history.rb
の両方に書き、その中身はそれぞれのモデルのカラム名などによって変える必要がありますが、今回は片方のモデルの分だけ下に載せています。今回scopeに引数を渡していますが、引数を渡すのであればRailsガイドではクラスメソッドを利用することを推奨しています。
スコープで引数を使用するのであれば、クラスメソッドとして定義する方が推奨されます。
ただし注意点があり、クラスメソッドではfalseの場合にnilを返しますが、scopeの場合allメソッドの結果を返します。
ただし1つ注意点があります。それは条件文を評価した結果がfalseになった場合であっても、スコープは常にActiveRecord::Relationオブジェクトを返すという点です。クラスメソッドの場合はnilを返す
scope :history_search, -> (search_params) do return if search_params.blank? occured_at_from(search_params[:occured_at_from]) .occured_at_to(search_params[:occured_at_to]) .user_name_like(search_params[:name]) .item_name_or_room_name_like(search_params[:item_name_or_room_name]) .event_eq(search_params[:event]) end scope :occured_at_from, -> (from) { where('? <= occured_at', from) if from.present? } scope :occured_at_to, -> (to) { where('occured_at <= ?', to) if to.present? } scope :user_name_like, -> user_name { return if user_name.blank? user_ids = User.where("name LIKE ?", "%#{user_name}%").pluck(:id) sole_box_ids = Solebox.where(user_id: user_ids).pluck(:id) where(sole_box_id: sole_box_ids) } scope :item_name_or_room_name_like, -> (item_name_or_room_name) { return if item_name_or_room_name.blank? event_history_ids = EventHistoryBackup.where("item_name LIKE ?", "%#{item_name_or_room_name}%").pluck(:event_history_id) where(id: event_history_ids) } scope :event_eq, -> (event) { return if event.blank? where(event_type: event) }ビュー
Viewでは
@old_and_new_histories
という配列をeachで回します。
ただしこの配列はoldとnewの2種類がごちゃ混ぜになっているので、オブジェクトの種類によって表示を変えなければいけません。
例をあげると、
oldの方にはroom_name
というカラムがありますが、newの方にはありません。
逆にnewの方にはitem_name
というカラムがありますが、oldの方にはありません。
occured_at
はどちらのモデルにもあります。
全て適切に表示する必要があります。
decoratorを使って以下のように実装しました。index.rb<% @old_and_new_histories.each do |old_and_new_history| %> <tr> <td><%= old_and_new_history.occured_at %></td> <td><%= old_and_new_history.user_name %></td> <td><%= old_and_new_history.decorate.item_name_or_room_name %></td> <td><%= old_and_new_history.decorate.event %></td> </tr> <% end %>old_history_decorator.rbdef item_name_or_room_name return room.name if room.present? '削除済' endnew_history_decorator.rbdef item_name_or_room_name return item.name if item.present? '削除済' endこうしてdecoratorファイルに同じ名前のメソッドを用意して中身の処理を変えることで、
old_and_new_history
に
old_history
オブジェクトが渡ってきたときはroom.name
を返し、
new_history
オブジェクトが渡ってきたときはitem.name
を返すことが出来ます。これで一通り実装が出来ました(確か出来てました)。
問題点
開発環境では少ないレコード数で開発していたので気がつかなかったのですが、大量のレコードで動かしてみると検索にかなりの時間がかかりました。
old_and_new_histories_array = (@old_histories + @new_histories)
この部分で全レコードに対するSQL発行をしていることが原因です。
これを解決するには「WHERE」、「SORT」、「LIMIT」などなどを一度のSQLで行わないといけないかな、と思いましたが、実現できませんでした。仮にできたとしても本当に処理が早くなるのかどうか。2の方法で進める
「検索フォームの条件で各モデルのレコードを絞り込んだオブジェクトの配列を作成、それぞれをViewに渡す」という方法です。
こんなイメージです。
new_histories_contoroller.rbdef index @tab_type = tab_type_params[:tab_type] @search_params = history_search_params @old_histories = OldHistory.includes_for_histories.history_search(@search_params) .order(occured_at: :desc).page(params[:old_history_page]).per(30) @new_histories = NewHistory.includes_for_histories.history_search(@search_params) .order(occured_at: :desc).page(params[:new_history_page]).per(30) end private def history_search_params params.permit(:occured_at_from, :occured_at_to, :user_name, :item_name_or_room_name, :event) end def tab_type_params params.permit(:tab_type) endこれならSQL実行はすぐに終わります。
モデルにscopeの記述をするのは1の方法と一緒です。
ビューは少し変わります。
以下にhtml、css、jsのコードを載せます。
Viewページは非同期のタブ切り替えで実装します。
oldとnewの表示はrenderでそれぞれ別のファイルを参照するようにします。
それぞれのファイル内に<%= paginate old_histories, param_name: param_name, remote: true %>
のようにページネーション部分を書きます。
別のファイルに分けることで、1の方法で行ったdecoratorの工夫(同じ名前のメソッドを用意する)は不要になります。index.html.erb<div> <%= hidden_field_tag :tab_type, @tab_type %> </div> <div class="box-body"> <div id="js_tabBtn" class="clearfix"> <div id="old" class="select_btn"> <strong class="tab-menu_label"><%= t('.old_type') %></strong> </div> <div id="new" class="select_btn"> <strong class="tab-menu_label"><%= t('.new_type') %></strong> </div> </div> <div id="tab_old" class="js_content"> <%= render 'old_histories', param_name: :old_history_page, old_histories: @old_histories %> </div> <div id="tab_new" class="js_content"> <%= render 'new_histories', param_name: :new_history_page, new_histories: @new_histories %> </div> </div>index.js.erb$("#tab_old").html("<%= j(render 'old_histories', param_name: :oldt_history_page, old_histories: @old_histories) %>"); $("#tab_new").html("<%= j(render 'new_histories', param_name: :new_history_page, new_histories: @new_histories) %>");history.scss#js_tabBtn { margin: 5px; border-bottom: solid #c4c4c4 1px; div { float: left; list-style-type: none; padding: 4px 10px; color: #0073BB; text-decoration: none; display: block; text-align: center; } } #js_tabBtn .active { color: black; .tab-menu_label { border-bottom: solid black 3px; padding-bottom: 5px; } } .js_content{ display: none; } .js_content.active{ display: block; } #js_tabBtn div { cursor: pointer; }new_history.js// アクティブでないタブがクリックされたら、そのタブ&対応するコンテンツにactiveクラスを追加し、その兄弟divからactiveクラスを削除する。 $(function () { $("#js_tabBtn .select_btn").on("click", function () { if ($(this).not("active")) { $(this).addClass("active").siblings("div").removeClass("active"); let index = $("#js_tabBtn .select_btn").index(this); $(".js_content") .eq(index) .addClass("active") .siblings("div") .removeClass("active"); } }); // oldのタブがクリックされたら#tab_typeの値をoldにする。逆もしかり。 $("#js_tabBtn #old").on("click", function () { $('#tab_type').val('old'); }); $("#js_tabBtn #new").on("click", function () { $('#tab_type').val('new'); }); // #tab_typeの値がoldもしくはblankだったらoldの方をactiveに、そうでなければnewの方をactiveにする。 if ($('#tab_type').val() == "old" || $('#tab_type').val() == "") { $('#old').addClass("active"); $('#tab_old').addClass("active"); } else { $('#new').addClass("active"); $('#tab_new').addClass("active"); } });検索後に選択したタブを保持する方法として、js-cookieというものを始め導入しましたが、一度ページを離れてから戻ってきてもcookieによって最後に選択したタブの方がactiveになっていました。
このページにアクセスした際はデフォルトでoldのタブを表示させたかったので、デフォルトではoldをactiveにしておき、cookieは使わずに検索ボタンでsubmitした際にhidden_field
に:tab_type
を持たせて(タブ選択で:tab_type
の値を変える)そのパラメータによって検索後の画面のactiveを決定するようにしました。最後に
こうやって文章にすると方針転換は発生しながらもスラスラ実装できているように見えますが、実際はめちゃくちゃ悩みながらなんとか実装できました。
以下には気をつけた方がいいかなと思いました。
- 開発環境でもなるべく本番と同じデータを使った方が良い
- オブジェクトのクラスを意識する
- SQLの発行を意識する
- 投稿日:2020-08-30T11:34:25+09:00
Railsで「いいね!」機能を作る - ①アソシエーションに別名をつける
背景
最近人にプログラミングを教える機会があるのですが、使っているテキストに、初学者さんにはまあまあ難しそうだなー。と思える記述がありました。
自信を持って教えられるか不安なところもあったので、勉強がてら、噛み砕いてまとめてみようと思います。
データ構造
基本のデータ構造は以下の通り。
Twitterみたいなアプリで、ユーザー(users
)はたくさんの投稿(posts
)を持っている。そして、自分の投稿も含む全ての投稿に「いいね!」(likes
)を付けられる。という感じです。シンプルな構造なのですが、後半、ちょっとひねりが必要でした。
アソシエーションを書き起こしてみる
まずは、Railsの規則通りにシンプルに書けるところからアソシエーションを書き起こしてみます。
models/user.rbclass User < ApplicationRecord has_many :posts has_many :likes endmodels/post.rbclass Post < ApplicationRecord belongs_to :user has_many :likes endmodels/like.rbclass Like < ApplicationRecord belongs_to :user belongs_to :post endとてもシンプルですね
この時、例えばcontroller
で以下のように書くと、controllers/users_controller.rbclass UsersController < ApplicationController def show @posts = User.find(params[:user_id]).posts end end「そのユーザーの投稿一覧」を取得できます。(今回は例としてユーザー詳細ページで一覧表示する形にしています)
問題点
この時、例えば「そのユーザーが『いいね!』した投稿一覧」を表示したいと思ったときには、少し工夫が必要です。
おそらく、下記
@favorite_posts
のように記載すれば「そのユーザーが『いいね!』した投稿一覧」は取得できるように予想しますが、controllers/users_controller.rbclass UsersController < ApplicationController def show @posts = User.find(params[:user_id]).posts @favorite_posts = User.find(params[:user_id]).likes.posts # ↑こちらはあくまでも予想のコード、実際に動作しません。 end end
User
モデルで、has_many :posts
は既に使われていて、models/user.rb(再掲)class User < ApplicationRecord has_many :posts has_many :likes # has_many :posts, through: :likes とは書けない。 end
has_many :posts, through: :likes
と書くと、名前が重複してしまうからです。
ちなみに、through:
を使わないで、has_many :posts
から「いいね!」した投稿を取得する方法もあるにはあるのですが、難しい上に、データの読み出しにコストがかかるので、お勧めはしません。
アソシエーションに別名をつけて解決
そんな時は、
user
→likes
→posts
という関係の流れに別名をつけて解決します。
つまり、下記の図の赤枠で囲ったアソシエーションに別名を付ければいいのです。別名の付け方は下記の通りです。
models/user.rbclass User < ApplicationRecord has_many :posts has_many :likes has_many :favorites, through: :likes, source: :post end上記のコードでは
user
→likes
→posts
というアソシエーションに、favorites
という別名をつけています。ただし、そのままだとrailsは
favorites
というテーブルを探してしまうので、source: :posts
というオプションをつけてposts
テーブルを参照するようにします。上記のように書くことで、
user.favorites
と記載すると、「ユーザーが『いいね!』した投稿」を取得することができます。(下記は、こんな感じで使えますよーというサンプルです)controllers/users_controller.rbclass UsersController < ApplicationController def show @posts = User.find(params[:user_id]).posts @favorite_posts = User.find(params[:user_id]).favorites end end早い人は、プログラミング初心者から数週間でこの内容を理解して卒業していくんですね。すごいなー。
私も自信持って教えられるように、次は「いいね!」をする、「やめる」のコードについても研究したいと思います。
追記:逆の関係(投稿に「いいね!」したユーザーを取得)
なお、投稿に「いいね!」したユーザーは次のような記述でアソシエーションを結ぶことができます。
models/post.rbclass Post < ApplicationRecord belongs_to :user has_many :likes has_many :users, through: :likes end先ほどつけた別名とは関係なく、
ユーザー(user)
と投稿(post)
の関係は、
user
が自分の作った投稿をもつpost belongs_to user
の関係と、
postが
自分に「いいね!」をしたユーザーを持つpost had_many users through likes
という二つの関係があるので、それそのまま記載すればOKです^^
- 投稿日:2020-08-30T10:36:06+09:00
Rails Tutorial 第12章 完了
2020/8/22 1.5時間
12.1.2まで進めました。
2020/8/23 2.0時間
12.3.2まで進めました。
2020/8/24 0.5時間
リスト12.18の途中まで進めました。
2020/8/25 0.5時間
2020/8/26 0.5時間
リストの入力間違いでエラーになっていたのをデバッグしてやっと動きました。テキストから手打ちしていると、入力ミスで結構バグが出ることが分かります。
2020/8/27 0.5時間
2020/8/28 1.0時間
2020/8/29 4.0時間
うち1.5時間はSendGridのエラー調査です。
2020/8/30 0.5時間
Sendgridはあいからずエラーですが、先に進めることにしました。
12章を完了です。
所要時間は11.0時間です。digestは作るたびに違う値になる
tokenが同じ値ならdigestは何度作っても同じなのかと思ったのですが、2回実行してみたところ違う値になっていました。
>> a.password_digest => "$2a$10$92Y.9S6JdU9HutSoZu6TdOSDFHsodZ5PJKZfzBxmflqIYNwuUA6Su" >> a.password_digest => "$2a$10$yZ9mPXl/uWJF1czSLso9yejl7A4ALE9HvRWJOuozwLldjb98vT1yO"SendGridのAddonの追加でエラーになった件は解消
8/19にSendGridのAddonの追加でエラーになった件は、8/29になぜか解消していました。
前回リンクを貼ったネットの他の方と同じです。理由は以下のどちらかだと推測します。
1.時間が立ったことによりバグ?のようなエラーが修正された
2.第12章まで進めることでエラーを回避できるubuntu:~/environment/sample_app (master) $ heroku addons:create sendgrid:starter Creating sendgrid:starter on ⬢ fathomless-mesa-1xxxx... free Created sendgrid-crystalline-46xxx as SENDGRID_PASSWORD, SENDGRID_USERNAME Use heroku addons:docs sendgrid to view documentation ubuntu:~/environment/sample_app (master) $ heroku config:get SENDGRID_USERNAME app173xxxx@heroku.com ubuntu:~/environment/sample_app (master) $ heroku config:get SENDGRID_PASSWORD uxxxxxxx78xxxSendGridで違うエラー Authentication failed:
メールを送るところで、違うエラーになりました。
.. Completed 500 Internal Server Error in 606ms (ActiveRecord: 42.2ms) ..ArgumentError (SMTP-AUTH requested but missing secret phrase): ..app/models/user.rb:59:in send_activation_email' ..app/controllers/users_controller.rb:23:in `create'入力間違いに気が付き修正しました。
誤:user_password
正:passwordconfig/environments/production.rb(誤)ActionMailer::Base.smtp_settings = { :address => 'smtp.sendgrid.net', :port => '587', :authentication => :plain, :user_name => ENV['SENDGRID_USERNAME'], :user_password => ENV['SENDGRID_PASSWORD'], :domain => 'heroku.com', :enable_starttls_auto => true再実行したところ、違うエラーになりました。
..Net::SMTPAuthenticationError (535 Authentication failed: account disabled
ネットで他の方を調べたところ、SendGridでアカウントを一時凍結された方がいたので、時間をおいて試してみることにしました。
- 投稿日:2020-08-30T10:07:40+09:00
ActiveRecord::NotNullViolation in Deviseエラー
【概要】
1.結論
2.ActiveRecord::NotNullViolationtとは何か
3.なぜActiveRecord::NotNullViolation in Deviseになるのか
4.どのように解決するか
5.補足
1.結論
db/migrate/""""devise_create""""".rbに
記載してある"t.string password(またはpassword_confirmation)"の記載を削除する!
2.ActiveRecord::NotNullViolationtとは何か
この意味は、「DBの規則上、空欄(NULL)にしてはいけない項目がデータで保存されかけましたよ!そんなことしちゃいけませんよ!」という機械からのご指摘です!
3.なぜActiveRecord::NotNullViolation in Deviseになるのか
2.と掛け合わせると、「デバイスでDBの規則上、空欄(NULL)にしてはいけない項目がデータで保存されかけましたよ!コントローラーで保存して登録されたときだよ!」と行っています。
なぜこのようなことになるかというと、gem'devise'がわざわざpasswordを作ってくれたのに、
被せて作ろうとしているのでどっちのpasswordがいいかわからずNULLになったと思われます!gem'devise'というものはpasswordとそれに対するpassword_confirmationを作ってくれるgemです!
なので
db/migrate/""""_devise_create_""""".rbt.string :password null:false"(password_confirmation)をプログラムしてカラムを作成する必要はないんです!
4.どのように解決するか
db/migrate/""""_devise_create_""""".rbt.string password, null:false" (またはpassword_confirmation)と記載されていると思うので削除しましょう!
5.補足
ちなみにgem'devise'にはpasswordに対してもう一つ付け加えれれている機能があります!
それはバリデーションです!
6文字以上にしなければ、入力ができないように制限が既に機能として備わっているので、
model/uservalidates :password, length { minimum: 5 }を記載する必要がなくなります!
- 投稿日:2020-08-30T09:54:02+09:00
【Rails】NoMethodError: undefined method `new' for BigDecimal:Classの対処法
はじめに
NoMethodError: undefined method 'new' for BigDecimal:Class
の詳しい対処法が日本語であまり載ってなさそうなので、備忘録として残しておきます。対処法
結論として
Gemfile
のbigdecimal
を下記内容に修正したら、うまく行きました。gem 'bigdecimal', '1.3.5'gemの内容を書き換えたのでその内容を反映させるために、
bundle install
を行なってください。$ bundle install意味
バージョン 特徴 サポートされているRubyのバージョン範囲 2.0.0 BigDecimal.newとサブクラス化の使用ができない 2.4 .. 1.4.x BigDecimal.newとサブクラス化を使用の際、警告がいつも表示される 2.3 .. 2.6 1.3.5 警告なしでBigDecimal.newとサブクラス化を使用できる .. 2.5 参考
https://stackoverflow.com/questions/60226893/rails-nomethoderror-undefined-method-new-for-bigdecimalclass
https://github.com/ruby/bigdecimal#which-version-should-you-select
- 投稿日:2020-08-30T09:52:24+09:00
【Rails環境構築】Docker + Rails + MySQL (初心者も30分でOK!)
Dockerを使った環境構築
「環境構築はコードを書くよりも難しい」と聞いたことがあります。
Dockerは簡単!
何故かと言うと、、、
コピペするだけでいいから
ただし、理解するまで少し時間がかかるかも、、、
まずは簡単な概要からDocker
仮想環境を構築するための道具
コンテナ
仮想環境そのもの
(DockerはDockerエンジンの上にコンテナが動く)イメージ
Dockerコンテナを実行するために必要なもの。
環境構築の手順
①.
プロジェクト(ディレクトリ)を作成し、移動する
$ mkdir アプリ名 $ cd アプリ名②.
Dockerfile、docker-compose.yml、Gemfile、Gemfile.lockを作成する
$ touch Dockerfile docker-compose.yml Gemfile Gemfile.lock③.
エディタを開き、Dockerfile、docker-compose.yml、Gemfileを下記からコピペする
Dockerfile
Dockerのイメージを自動で生成してくれるファイル
FROM ruby:2.5 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myappFROM : 使用するイメージとコマンド
RUN : コマンドの実行
WORKDIR : 作業ディレクトリの設定
COPY : コピー元(ホスト側)とコピー先(仮想環境側)のファイルまたはディレクトリを指定Gemfile
source 'https://rubygems.org' gem 'rails', '~> 5.2.3'docker-compose.yml
version: '3' services: db: image: mysql:5.7 environment: MYSQL_USER: root MYSQL_ROOT_PASSWORD: password ports: - "3306:3306" volumes: - ./db/mysql/volumes:/var/lib/mysql web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp - gem_data:/usr/local/bundle ports: - 3000:3000 depends_on: - db tty: true stdin_open: true volumes: gem_data:version : docker-composeのバージョン。
depends_on : 依存関係を示していて起動順を制御できる。ここでは「db→web」へ起動する。Docker composeとは
Docker composeとは、複数のコンテナから成るサービスを構築・実行する手順を自動化し、管理を容易にする機能。Docker composeでは、composeファイルを用意してコマンドを一回実行することで、そのファイルから設定を読み込んですべてのコンテナを起動することができる。
serviceについて
Docker composeでは、アプリケーションを動かすための各要素をserviceと呼んでいる。通常はweb(rails)とdb(mysql)と名付ける。
rm -f tmp/pids/server.pid
についてpidとはプロセスIDのことである。pidは、開発用Webサーバーを起動するときにtmp/pids/server.pidに書き込まれ、終了するときに削除される。server.pidにpidが書かれているとサーバーが起動中と判断されてしまう。
portsについて
ports: - 3000:3000上記は、コンテナ内のポート3000番をホストの3000番にマッピングするという意味。これにより、コンテナ内のWebサーバーへ
http://localhost:3000
でアクセスできるようになる。volumeについて
volumes: - ./db/mysql/volumes:/var/lib/mysqlは、ホストの./db/mysql/volumesをコンテナ内の/var/lib/mysqlにマウントするという意味。簡単にいうと、Dockerのコンテナと、ローカルを同期している。
volumes : - .:/myappは
.
がホストのディレクトリ全てを意味し、それをコンテナ内のmyapp
にマウントしている。④.
下記コマンドを実行する
$ docker-compose run web rails new .--force --database=mysql --skip-bundlerunコマンドではimageの構築から、コンテナの構築・起動までしてくれる。
引数にサービスを指定する必要あり。
docker-compose.ymlのvolumes:- .:/myappの部分でdockerとローカルのディレクトリを同期するように設定しているため、このコマンドを実行後、同時にローカルにも同様のファイルが生成される。⑤.
config/database.yml内の(password: password、host: db)に変更する
default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password #passwordを追記しました host: db #localhostからdbに変更しました⑥.
下記コマンドを実行する
$ docker-compose buildGemfileが更新されたとき
build
を実行する⑦.
下記コマンドを実行する
$ docker-compose up -ddocker-compose.yml通りにコンテナが起動する。
docker-compose.ymlの変更を反映させる際にも実行。
-d
オプションはバックグラウンド起動。⑧.
下記コマンドを実行する
$ docker-compose run web rails db:createデータベースを作成する。
docker-compose run web
でローカルからコマンドを実行できる。(コンテナに入る必要はない)⑨.
下記コマンドを実行し、2つのコンテナが立ち上がっているか確認する。
(完成!)$ docker ps現在起動しているコンテナを表示するコマンド。
localhost:3000
にアクセスするといつものYay!You're on Rails!が表示される。
コンテナを削除したい場合は下記を実行する
$ docker-compose down
docker ps
でコンテナが削除されているか確認する。
またコンテナを生成・起動する場合はdocker-compose up -d
を実行する。まとめ
かなり簡単だったんじゃないかなと思います!
これでアプリ開発していきましょう!
- 投稿日:2020-08-30T08:03:23+09:00
クラスの情報を継承したクラスの作り方
内容
複数のクラスに同じメソッドを定義したい場合、それぞれのクラス全てにそのメソッドを定義すると、同じ記述を何度も繰り返す事になってしまいます。
(例1)
クラス1 クラス2 クラス3
メソッドA メソッドA メソッドA
メソッドB メソッドC メソッドD
複数のクラスが同じメソッドを持つときにクラスが増えれば増えるほどコード量が多くなり、管理も難しくなります。これを防ぐためにクラスの継承について学んでいきましょう。クラスの継承とは
あるクラスに定義されたメソッドを、別の新規クラスで利用できるようにした上でクラスを定義することを継承と言います。
クラスの継承には、親クラスと子クラスの関係があります。元となるクラスを親クラス、親クラスのメソッドを引き継ぎ新しく作成するクラスを子クラスと呼びます。
パトカーやトラックの上位概念である「車」のクラスを作り、そこへ共通の特徴を定義しておきます。車の特徴(親クラス)を継承することで、パトカー(子クラス)とトラック(子クラス)に車の特徴を記述する必要がなくなります。結果、パトカーとトラックだけの特徴を書くだけで済む上、それぞれの特徴が分かりやすくなっています。クラスの継承をする際には、クラスを宣言する際に「<」を用いて、以下のように記述します。
class 子クラス名 < 親クラス名 endそれでは、先ほどの車の例を実際にクラスの継承を用いたコードで書いてみます。
まずは親となるクラスを定義します。Carクラス(親クラス)
class Car def speed_up puts "加速します" end def speed_down puts "減速します" end def horn puts "プップー" end end親クラスには、車が持つ共通の動作を定義しています。
次に子クラスをそれぞれ定義します。今回はパトカーとトラックをそれぞれPatrolCar, TruckCarとして定義します。PatrolCarクラス(子クラス)
class PatrolCar < Car # クラスの継承 def siren puts "ピーポーピーポー" end endTruckCarクラス(子クラス)
class TruckCar < Car # クラスの継承 def carry puts "荷物を載せます運びます" end end親クラスに共通のメソッドを定義することで、子クラスのコード量が少なくなり見やすくなっています。また、これは継承を使う大きなメリットですが、親クラスで定義された共通のメソッドを変更するだけで、変更を子クラスへ容易に反映できることが上記のコードから分かります。
最後に
クラスの継承を使うことによって共通のメソッドを繰り返し書くことなくスッキリとしたコードを書けるので皆さん書いてみてください!