- 投稿日:2019-08-16T22:29:40+09:00
RSpec でインスタンスメソッドがスタブできないときはインスタンスをモックする必要があるよという話
はじめに
下記のようなコードを書いたとします。
instance_stub.rbclass Human def meet greeting = Greeting.new greeting.hello end end class Greeting def hello 'Hello!' end end上記のコードに対して、
Human#meetをテストしたいのですが、Greeting#helloは実際には呼び出したくなかったとします。そのため、
Greeting#helloをスタブする以下のようなテストコードを書いたとします。わかりやすいように、スタブした際の返り値を変更して、返り値が正しく変更されているかどうかを検証しています。spec/instance_stub_spec.rbrequire_relative '../instance_stub' describe Human do context 'meet' do it 'returns a stub message instead of a real message' do greeting = Greeting.new allow(greeting).to receive(:hello).and_return('Hello from stub!') expect(Human.new.meet).to eq('Hello from stub!') end end endこのコードは正しく動作しません。以下のようなエラーが発生します。
Failures: 1) Human meet returns a stub message instead of a real message Failure/Error: expect(Human.new.meet).to eq('Hello from stub!') expected: "Hello from stub!" got: "Hello!" (compared using ==) # ./spec/instance_stub_spec_miss.rb:10:in `block (3 levels) in <top (required)>'テスト中は
Greeting#helloを呼び出したくないのでスタブしようとしていたのに、スタブできていません (Greeting#helloが実際に実行されてしまっています)。この原因と解決法について説明します。
TL;DR
テストコード内で生成したインスタンスと実際のコード内で生成したインスタンスは別のオブジェクトであるため、スタブしたいインスタンスメソッドを持つクラスのインスタンスをテストコード内で生成しても正しくスタブすることはできない。
正しくスタブするためには、
newをスタブする必要がある。spec/instance_stub_spec.rbrequire_relative '../instance_stub' describe Human do context 'meet' do it 'returns a stub message instead of a real message' do - greeting = Greeting.new + greeting_mock = instance_double(Greeting) - allow(greeting).to receive(:hello).and_return('Hello from stub!') + allow(Greeting).to receive(:new).and_return(greeting_mock) + allow(greeting_mock).to receive(:hello).and_return('Hello from stub!') expect(Human.new.meet).to eq('Hello from stub!') end end end原因
まず原因に関してですが、行番号を付与して先ほどのテストコードを再掲します。
spec/instance_stub_spec.rb1 require_relative '../instance_stub' 2 3 describe Human do 4 context 'meet' do 5 it 'returns a stub message instead of a real message' do 6 greeting = Greeting.new 7 8 allow(greeting).to receive(:hello).and_return('Hello from stub!') 9 10 expect(Human.new.meet).to eq('Hello from stub!') 11 end 12 end 13 end6 行目で
Greetingクラスのインスタンスを生成しています。8 行目でGreetingインスタンスのhelloメソッドをスタブして'Hello from stub!'を返すようにしています。一見するとこれで
Greeting#helloがスタブされるように見えますが、先ほど紹介したように、これだと正しくスタブされていません。理由は、このテストコード内 (
spec/instance_stub_spec.rb) で生成したGreetingインスタンスと、実際のコード内 (instance_stub.rb) で生成したGreetingインスタンスは、どちらもGreetingインスタンスですが、オブジェクトが別だからです。これを説明するには、以下のプログラムを IRB で実行するとわかりやすいでしょう。
irb(main):001:0> class Foo; end => nil irb(main):002:0> Foo.new == Foo.new => false irb(main):003:0> Foo.new => #<Foo:0x00007faa9aafee88> irb(main):004:0> Foo.new => #<Foo:0x00007faa99b1fa58>中身が空の
Fooクラスを作って、2 つのFooインスタンスを比較していますが、falseが返ってきます。もう少し考察してみましょう。
Fooインスタンスを生成した際の返り値を見てみると、インスタンスを生成するたびにオブジェクトの ID が変わっていることがわかります (ちなみに、オブジェクトの ID はFoo.new.object_idで取得することができます)。つまり、全く同じ
Foo.newを実行しても、実行するたびに別のオブジェクトが生成されるため、一致しないのです。実際のコードとテストコードを再掲します。
instance_stub.rb1 class Human 2 def meet 3 greeting = Greeting.new 4 greeting.hello 5 end 6 end 7 8 class Greeting 9 def hello 10 'Hello!' 11 end 12 endspec/instance_stub_spec.rb1 require_relative '../instance_stub' 2 3 describe Human do 4 context 'meet' do 5 it 'returns a stub message instead of a real message' do 6 greeting = Greeting.new 7 8 allow(greeting).to receive(:hello).and_return('Hello from stub!') 9 10 expect(Human.new.meet).to eq('Hello from stub!') 11 end 12 end 13 end先ほどの話を踏まえて考えると、
instance_stub.rbの 3 行目のGreeting.newとspec/instance_stub_spec.rbの 6 行目のGreeting.newは、見た目は同じに見えてもオブジェクトが異なります。そのため、
spec/instance_stub_spec.rbの 8 行目は、もし 6 行目で生成したGreetingインスタンスと同じオブジェクトであればスタブできますが、実際にはinstance_stub.rbの 3 行目で生成したGreetingインスタンスとは別のオブジェクトなのでスタブできていないということになります。解決法
ではどのようにすれば良いかというと、
Greetingインスタンスを生成する際のnewメソッドをスタブすれば期待した通りにスタブできます。spec/instance_stub_spec.rbrequire_relative '../instance_stub' describe Human do context 'meet' do it 'returns a stub message instead of a real message' do greeting_mock = instance_double(Greeting) allow(Greeting).to receive(:new).and_return(greeting_mock) allow(greeting_mock).to receive(:hello).and_return('Hello from stub!') expect(Human.new.meet).to eq('Hello from stub!') end end end先ほどのテストコードでは
Greetingインスタンスを直接生成していましたが、今回はGreetingクラスのnewをスタブして、greeting_mockという名前のモックを返すようにしています。そしてこの
greeting_mockがhelloというメソッドをスタブして、'Hello from stub!'を返すようにしています。このテストコードを実行すると期待した通りに動作します。実際のコードで
Greetingインスタンスを生成した際にモックオブジェクトにすげ替えられて、そのモックオブジェクトに対してhelloメソッドが呼ばれるため、スタブされる際の返り値である'Hello from stub!'が返却されます。new をスタブしなくても良い例
ここまでの内容を理解したところで、自分は混乱してしまいました。それは『使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」』を読んだときです。
この記事で紹介されているコード例を引用させていただきます。
# 注:本当に動かす場合はtwitter gemが必要です require 'twitter' class WeatherBot def tweet_forecast twitter_client.update '今日は晴れです' end def twitter_client Twitter::REST::Client.new end endit 'エラーなく予報をツイートすること' do # Twitter clientのモックを作る twitter_client_mock = double('Twitter client') # updateメソッドが呼びだせるようにする allow(twitter_client_mock).to receive(:update) weather_bot = WeatherBot.new # twitter_clientメソッドが呼ばれたら上で作ったモックを返すように実装を書き換える allow(weather_bot).to receive(:twitter_client).and_return(twitter_client_mock) expect{ weather_bot.tweet_forecast }.not_to raise_error end上記の実際のコードでは
twitter_clientメソッド内でTwitter::REST::Clientクラスのインスタンスを生成しています。そして、テストコードではTwitter::REST::Clientのnewメソッドをスタブしていません。上記のテストコードは正しく動作するのですが、
newメソッドをスタブしていないのになぜ正しくスタブできているのだろうと疑問に思いました。しかし、落ち着いて考えれば正しくスタブできていることがわかります。
Twitter::REST::Clientクラスのインスタンスを生成しているだけのtwitter_clientメソッドを丸ごとスタブしているからです。このテストコードでは
WeatherBotクラスのtweet_forecastメソッドで例外が発生しないことを検証していますが、このメソッド内で呼び出されているtwitter_clientメソッドを丸ごとスタブしているため、twitter_clientメソッドの中身のTwitter::REST::Client.newは実行されていないことになります。そのため、正しくスタブできているというわけです。
インスタンスを生成するメソッドを別で用意して丸ごとスタブしてみる
先ほどの
instance_stub.rbにおいて、上記の項目で取り上げさせていただいた Twitter クライアントのコード風に、インスタンスを生成するだけのメソッドを作って書き換えてみましょう。すると以下のようなコードになります。method_stub.rbclass Human def meet greeting_instance.hello end def greeting_instance Greeting.new end end class Greeting def hello 'Hello!' end end以前は
Human#meetでGreetingインスタンスを生成していましたが、Greetingインスタンスを生成するだけのgreeting_instanceメソッドを用意してそれを呼び出すように変更しました。上記のコードにおいて、
greeting_instanceメソッドを丸ごとスタブしてGreeting#helloの返り値を変更するようなテストコードを書くと以下のようになります。spec/method_stub_spec.rbrequire_relative '../method_stub' describe Human do context 'meet' do it 'returns a stub message instead of a real message' do greeting_mock = instance_double(Greeting) human = Human.new allow(human).to receive(:greeting_instance).and_return(greeting_mock) allow(greeting_mock).to receive(:hello).and_return('Hello from stub!') expect(human.meet).to eq('Hello from stub!') end end end先ほどの Twitter クライアントのテストコードと同様に、インスタンスを生成するだけの
greeting_instanceメソッドを丸ごとスタブして、モックオブジェクトを返すようにしています。そのモックオブジェクトに対して
helloメソッドが実行された際に、スタブして'Hello from stub!'を返すようにしています。
greeting_instanceメソッドを丸ごとスタブしているため、Greeting.newは実際には実行されません。そのため、上記のテストコードも正しくスタブできていることになります。もちろん、
Greeting.newの部分をスタブした場合でも期待した通りに動作します。つまり、method_stub.rbのコードに対して、spec/instance_stub_spec.rbのテストコードを実行しても正しく動作するわけです (require_relativeでロードするファイルの対象を変更する必要はあります)。method_stub.rbclass Human def meet greeting_instance.hello end def greeting_instance Greeting.new end end class Greeting def hello 'Hello!' end endspec/instance_stub_spec.rbrequire_relative '../method_stub' describe Human do context 'meet' do it 'returns a stub message instead of a real message' do greeting_mock = instance_double(Greeting) allow(Greeting).to receive(:new).and_return(greeting_mock) allow(greeting_mock).to receive(:hello).and_return('Hello from stub!') expect(Human.new.meet).to eq('Hello from stub!') end end endこの場合は、
greeting_instanceメソッドをスタブする代わりに、その中身であるGreeting.newをスタブしていることになります。どちらの場合でもインスタンスの生成部分をスタブしていることになるので、正しく動作します。まとめ
スタブしたいインスタンスメソッドを持つクラスのインスタンスをテストコード内で生成しても、実際のコード内のインスタンスとは別のオブジェクトなので、インスタンスメソッドを正しくスタブできないよというお話でした。
ところで、この記事ではモックオブジェクトを作る際に
instance_doubleを使用しました。doubleやinstance_doubleの違いについての記事も書きましたので、興味があれば併せてご覧ください。RSpec における double / spy / instance_double / class_double のそれぞれの違いについて
参考にしたサイト
- 投稿日:2019-08-16T22:17:10+09:00
引数の基礎
引数がとても苦手でしたので、復習のためにまとめておきます。
rubyではメソッド内で定義していないものを使うことはできません。そのため、以下の記述はエラーになります。
rubydef hoge puts huga end huga = "眠い" hoge #エラーになるhogeメソッドはhugaをputsしたいわけなのですが、hugaはhogeメソッドの中で定義されたものではありません。もちろんhugaの定義をhogeの中ですればOKです。が、それだと引数の説明にならないのでこのまま引数を使ってエラーを解決します。
引数には本引数と仮引数があリます。
rubydef hoge(仮引数) puts huga end huga = "眠い" hoge(本引数)本引数は、メソッドを呼ぶ方に書きます。仮引数は呼ばれたメソッドの方に書きます。
実際に本引数と仮引数を入れてみます。
rubydef hoge(huga) puts huga end huga = "眠い" hoge(huga)今回のhogeメソッドはputs hugaをしたいわけですが、hugaがメソッド外で定義されているせいでエラーが起きていましたので、メソッドを呼び出す方の本引数にhugaを入れて、仮引数の方にもhugaを入れました。これによって最終行の引数の全てにhugaが入った状態になります。なのでエラーが出ずにターミナルには「眠い」と表示されます。
ここまでは、本引数と仮引数を同じ名前にしましたが、名前が異なっていても使えます。
rubydef hoge(kotoba) puts kotoba end huga = "眠い" hoge(huga)本引数 : huga
仮引数 : kotobahogeメソッド内のputsの右側が「kotoba」に変わりました。これは、メソッドの中で引数として渡ってきたものを使うためには仮引数と同じ名前でないと使えないからです。例えば以下のようにするとエラーになります。
rubydef hoge(kotoba) puts huga end huga = "眠い" hoge(huga) #エラーになる仮引数はkotobaなのにputsの右側がhugaになってます。やっぱりhugaはhogeメソッドの中で当然ながらこれもダメです。
rubydef hoge(huga) puts kotoba end huga = "眠い" hoge(huga) #エラーになる相変わらずputsの右側のkotobaはhogeメソッドで定義されていません。
1.メソッドで使いたいメソッド外部の変数名など本引数の名前が一致していること
2.仮引数とメソッド内において仮引数で渡されたものを使いたいところの名前が一致していること上記2点が守れていれば、本引数と仮引数の名前が異なっていても問題なく動きます。
rubydef hoge(name2) puts name2 end name1 = "眠い" hoge(name1)name1同士は同じ名前
name2同士も同じ名前こうなっていればOKです。
追記:
1.本引数は実引数ということの方が多いようです。2.本引数の中にはローカル変数だけでなく式を書くこともできます。なので何か変数名をいれなくても仮引数を書いたメソッドで使いたいものを本引数の部分に式として書いても使えます。
hoge.rbdef hoge(name) puts "私の名前は#{name}です" end puts hoge("ヤマダ")ターミナルruby hoge.rb 私の名前はヤマダです本引数の部分には変数名などではなく文字列をそのままいれていますが、問題なく結果が出ています。
- 投稿日:2019-08-16T20:47:01+09:00
#ruby の Timecop.freeze で Date を渡すと日付がずれるので Time.local を渡した方が良いかも
Passing Date with # ruby's Timecop.freeze will cause the date to shift, so it may be better to pass Time.local
Timecop.freeze Date.new(2019, 01, 01) # => 2018-12-31 15:00:00 +0000 Date.today # => Mon, 31 Dec 2018 Timecop.freeze ::Time.local(2019, 01, 01) # => 2019-01-01 00:00:00 +0000 Date.today # => Tue, 01 Jan 2019Original by Github issue
- 投稿日:2019-08-16T20:19:32+09:00
現役ポールダンサー初めてのwebサービスをRuby on railsで作りました
qiita初投稿です!
私の本業はポールダンス、エアルリアルダンスをメインとするパフォーマンス業です。(時々インストラクター的なこともやってます)
そんな私がこの度Rubyonrailsでwebサービスを初めて作って公開しました。
なぜダンサーがプログラミングを?わざわざ、、、?などなどはこちらのブログに書いてますが、最初は単純にキャリアチェンジを考えていたところからプログラミング学習は始まりました。そこからやはり自分でサービスを作ってみよう!ということになって作ったサービスがこちらです。
https://poletricks-world.herokuapp.com/
デザインや機能共にまだまだ修正すべきところはありますし、目新しい機能というものはありませんが
機能、デザイン、エラーで困ったところなどを綴ります。
アイディア
練習するときに見ることができるアプリがあればいいな(インスタだと、検索が面倒、技で検索できない、達成度が感じれられない、動画止められない)
・動画が止められて
・達成感が味わえて
・わかりやすい動画
・且つ無料
私が練習を始めた頃に欲しいなと思った機能です。これらを網羅したアプリを作りたかったのです。
プロトタイプを作ることを始めました。開発環境
Rails 5.2
ruby 2.4.0
cloud9(途中からAWSのcloud9に移行)プロトタイプを作る
プロトタイプは結構大まかでもいいのかなーと思っていましたが、私のメンターさんはできるだけ細かくデザインして、その機能をあとは乗せていくだけの方がいいよ、とのことで
イラレを使って作成していきました。
ちなみにイラストレーターを使ったのは今年のお正月が初めてです。ここは独学でアドビのイラストレーター初心者の動画をひたすら見ながら練習したり、ググったりして覚えました。すごいたくさんのことは全然できませんが
プロトタイプを作るくらいまでならサクサクできるように1ヶ月ほどでなりました。大まかなページ展開
・トップページ
・サインイン、ログインページ
・動画一覧ページ
・動画詳細ページ
・ブログページ
サインイン、のところはgemのdeviseを使いました。
動画は最初、AWSを使う予定でしたが、こんがらがるのでまずはYoutubeにアップロードして動画リンクを入れていくことにしました。動画一覧にはyoutubeのサムネイル表示がされるようにしています。Topページ
ナビゲーションバーのなかにリンク(ブログとコンタクト)、ログインログアウト、サインアップページ
ヘッダー画像とアカウント作成ボタン
動画例
使い方
アバウト
プロフィール
フッター(リンク、SNS)という構成です。動画一覧ページ
ユーザーができた技数が表示される(マスターできた数/存在する技数 の計算式)
動画リンク(サムネイル+マスターボタン+カテゴリーラベル+動画リンク)
ここの動画は全てadminの私がnewで追加していくように実装しました。(newにアクセスすれば動画を自分で足すことも可能だけど今の所私のみ、動画をアップできるようにする)
ブックマークの色が変わるところはAjax
検索機能はgemのransackを使いました。動画詳細ページ
動画のyoutube
コメント欄(削除可能)←技のポイントなどを書き込めたら面白いなあと思い。
関連動画表示(ここはマスターボタンなどは乗せないで、カテゴリーで紐づかせて表示されるようにしました)
動画自体はループで表示されるようにした方がいいか、迷いどころなのでテストユーザーのフィードバックで都度変更していこうと思います。Bookmarkページ
ここはかなり時間がかかりました。動画一覧ページでブックマークしたものをユーザーごとのマイページとしてブックマークされるようにしました。masterボタンをキャンセルすると動画一覧ページも色がかわります。
同じくajaxを使い、masterした技のボタンがmaster→cancelと変更される様にしました。主につまづいたところ
つまづいたことが多すぎてもうわからないくらいですが
。デザインがうまくいかない(現時点でもまだ修正中!!)
Bootstrapを使ってますが、使ったら使ったで自由度がない部分もあり、折り合いが難しいです。
ヘッダーの画像は最初イラレのみで私の写真を切りはりしたのですが、汚く見えてしまったので
Photosopで加工→イラレでサイズ、バックの修正などを行いました。多分もっといい方法はあると思いますが現時点ではこれが一番でした。herokuデプロイできない問題
テックアカデミーをやっていた時に使ったことがあるので余裕や!と思っていたけれど、
ふつーにエラーでなかなかデプロイできませんでした。
そもそもですが、別のブランチをmasterブランチにマージしていなかったため、まずはそこがエラーが出ていたようです。
とりあえずメンターに相談したらherokuの設定をする順番が間違っていたみたい。Herokuでよくエラーが出た時の解決方法
何度かテストユーザーさんに使ってもらう上で、cloud9上で問題なく表示され、動作できたのでherokuにアップするとよく
We're sorry, but something went wrong.
表示が出ていました。
大体が以下のことで解決です。
・git commit してないのでする
・heroku db:migrationする
・heroku restartする
でほぼほぼ解決しました、一回表示するべきコードが消えていた時があって、その時はコードを付け足し直してエラーは解決しました。AWSのcloud9
途中から以前のcloud9からAWSのcloud9に移行しました。
移行自体はスムーズに行ったのですが、、、
ある日急に容量がいっぱいであるというエラーが出て、ターミナルが使えなくなりました。
githubにコードはギリギリまであげていたので、再度git cloneして新たなワークスペースで作成。
そしてなんとかまたできるようになったもののまたある程度コードを書くと警告が出て、
またもやターミナルが使えなくなり、サポートに問い合わせるも具体的なことはわかりかねるよでした。
次にAWSでもう一度ワークスペースを作る際、変更したのはEC2インスタンスです。(ここがキモでした)今までmicroを使っていたのですがこれをsmallに変更
サーバーはubuntuです。
これで無事AWSに新たなワークスペースが作ることができました。
そのあと、AWSのアクセスキー、シークレットキーを登録しエラーと格闘しながら無事にまたheroku上で公開する事ができました。テストユーザーに利用してもらう
わたしの周りはインストラクターやショーにすでに出ている人たちだったのでポールダンスを習ってそこまで間もない生徒さんが必要でした。
友人の経営しているスタジオの生徒さんに協力してもらいフィードバックをいただくことになりました。
特に自分で作ってると裏側がわかるので導線などは、当たり前にここからログインするよね?
探しにいくよね?という前提で作っていたので、
動画ページに移動の方法に迷った、削除ボタンの位置がわかりにくい、戻り方がわからない、、
などなど導線をわかりやすくする工夫が必要になりました。
あとは重要なチェックボタンを押したあと、取り消しボタンがないこと、取り消しボタンがないため、同じボタンを押すと、Master数がどんどん同じ技で計上されてしまうようになっていたのでそこを修正する必要がありました。まとめ
細かいエラーなどはまた別途ブログで書いていこうと思いますが
6か月弱かけてなんとかここまで作る事ができました。
プロの方からするとはっきり言ってかなり単純なwebサービスとなっているように思われるかもしれませんが、個人的にはかなり頑張りました。自分で初めてプロダクトを作って見て、何度も投げ出しそうになったけど(というかまだデザイン終わってない)形にできて本当によかったし、もっと色々手を出して見たくなりました。
引き続き改良を試みつつ、技術のアップデートしたいです!
https://poletricks-world.herokuapp.com/
※2019年8月現在、エラー復旧中にAWSのcloud9のターミナルが使えなくなり、諸々修正が不可になりgit cloneするもバージョンコンフリクトなどで解消せず、、ローカルでぽちぽち作り直してます。一難去ってまた一難。。とりあえずこのurlとは別で新たにappを作っていこうと思います。
- 投稿日:2019-08-16T17:27:59+09:00
Rails ActiveRecordの結合時のクエリの内容について
参考
以下のページを参考にさせていただきました。ありがとうございます。
ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い
【Rails】テーブル結合データ構造
companiesテーブル
id name 1 A社 2 B社 servicesテーブル
id company_id name 1 1 Aアプリ1 2 1 Aアプリ2 3 1 Aアプリ3 4 2 Bの野望 パターン1 all(pluckなし)
companies = Company.all companies.each do |company| company.services endSELECT `companies`.* FROM `companies` SELECT `services`.* FROM `services` WHERE `services`.`company_id` IN (1,2)パターン2 : all
companies = Company.all companies.each do |company| model.services.pluck(:name).join(',') endSELECT `companies`.* FROM `companies` SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 1 SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 2パターン3 : join
companies = Company.joins(:services) companies.each do |company| model.services.pluck(:name).join(',') endSELECT `companies`.* FROM `companies` INNER JOIN `services` ON `services`.`company_id` = `companies`.`id` SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 1 SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 1 SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 1 SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 2パターン4 : preload
companies = Company.preload(:services) companies.each do |company| model.services.pluck(:name).join(',') endSELECT `companies`.* FROM `companies` SELECT `services`.* FROM `services` WHERE `services`.`company_id` IN (1,2)パターン5 : includes
companies = Company.includes(:services) companies.each do |company| model.services.pluck(:name).join(',') endSELECT `companies`.* FROM `companies` SELECT `services`.* FROM `services` WHERE `services`.`company_id` IN (1,2)パターン6 : eager_load
companies = Company.includes(:services) companies.each do |company| model.services.pluck(:name).join(',') endSELECT **省略** FROM `companies` LEFT OUTER JOIN `services` ON `services`.`company_id` = `companies`.`id`
- 投稿日:2019-08-16T17:27:44+09:00
Ruby on Railsでプロジェクト管理WEBアプリを開発
はじめに
UdemyでRuby on Railsのコースを学習したので、そこで学んだ知識を生かしてWEBアプリを開発しました。
ソースコード
GitHubで公開しています
https://github.com/Ryota7101/milestone.git主な機能
- miliaによるマルチテナント機能
- タスク機能
- タスクへのカテゴリ付け
- ファイルアップロード機能
- プレミアム会員機能
- 管理者機能
アプリ詳細
サインアップ
サインアップ時にフリープランかプレミアムプランかを選択できます。
フリープランの場合はカード情報入力欄が表示されません。
プレミアムプランに切り替えると、クレジットカード情報入力欄が表示されます。
※情報を登録して処理する機能を設定してないので、ここで入力した情報は保存されません。
プロジェクト作成
ファイルアップロード機能
テキストファイルや画像をアップできます。
※ただし、クラウドなど保存先を設定していないので、現状ではどこにもファイルは保存されません。
タスク
管理者機能
Organization作成者は自動的に管理者として登録されます。
管理者がログインしてる場合は、ナビゲーションバーに
* メンバー追加
* プラン変更
* タスクのカテゴリ管理
のボタンが表示されます。タスクのカテゴリ
タスクのカテゴリ管理(作成や削除)は管理者のみが行えます。
タスクを作成したり、タスクにカテゴリを設定するのは非管理者でも行えます。カテゴリを作成すると、カテゴリ一覧ページに表示されます。
使用する場合は、タスク作成時にチェックを入れます。
プラン変更
メンバーを招待する
管理者はメンバーをプロジェクトに招待できます。
招待したい人の氏名やメールアドレスを入力すると、リンク付きのメールが相手に送信されます。
届いたメールのリンクをクリックすると、パスワード設定画面に移動するので、パスワードを入力します
管理者がプロジェクトページを見ると、招待した相手の情報が表示されるので、Addボタンを押すと招待が完了します。
招待が完了すると、メンバー欄にメンバーの情報が表示されます。
招待された相手がログインすると、管理者が作成したプロジェクトやファイル、タスクなどが表示されます。
※招待されたメンバーは管理者でないので、ナビゲーションバーにカテゴリ管理などのボタンは表示されません。
別の組織を作成
試しに別の組織でサインアップして、これまで作成した情報が表示されないことを確認します。
これまでに作成した情報が表示されず、初期画面となっているため、組織ごとにデータが分かれてることがわかります。
終わりに
Udemyのコースの内容が2015年ごろのもので、その通りにコードを描いても動かないことが多く、苦戦しました。(コースではRails4、著者は5で作成)
相変わらずコード書いて実際にweb上で動かして確認して・・とやってしまい、テストはおざなりになってしまった。。
実戦でRails使いたいです
- 投稿日:2019-08-16T17:27:44+09:00
Ruby pn Railsでプロジェクト管理WEBアプリを開発
はじめに
UdemyでRuby on Railsのコースを学習したので、そこで学んだ知識を生かしてWEBアプリを開発しました。
ソースコード
GitHubで公開しています
https://github.com/Ryota7101/milestone.git主な機能
- miliaによるマルチテナント機能
- タスク機能
- タスクへのカテゴリ付け
- ファイルアップロード機能
- プレミアム会員機能
- 管理者機能
アプリ詳細
サインアップ
サインアップ時にフリープランかプレミアムプランかを選択できます。
フリープランの場合はカード情報入力欄が表示されません。
プレミアムプランに切り替えると、クレジットカード情報入力欄が表示されます。
※情報を登録して処理する機能を設定してないので、ここで入力した情報は保存されません。
プロジェクト作成
ファイルアップロード機能
テキストファイルや画像をアップできます。
※ただし、クラウドなど保存先を設定していないので、現状ではどこにもファイルは保存されません。
タスク
管理者機能
Organization作成者は自動的に管理者として登録されます。
管理者がログインしてる場合は、ナビゲーションバーに
* メンバー追加
* プラン変更
* タスクのカテゴリ管理
のボタンが表示されます。タスクのカテゴリ
タスクのカテゴリ管理(作成や削除)は管理者のみが行えます。
タスクを作成したり、タスクにカテゴリを設定するのは非管理者でも行えます。カテゴリを作成すると、カテゴリ一覧ページに表示されます。
使用する場合は、タスク作成時にチェックを入れます。
プラン変更
メンバーを招待する
管理者はメンバーをプロジェクトに招待できます。
招待したい人の氏名やメールアドレスを入力すると、リンク付きのメールが相手に送信されます。
届いたメールのリンクをクリックすると、パスワード設定画面に移動するので、パスワードを入力します
管理者がプロジェクトページを見ると、招待した相手の情報が表示されるので、Addボタンを押すと招待が完了します。
招待が完了すると、メンバー欄にメンバーの情報が表示されます。
招待された相手がログインすると、管理者が作成したプロジェクトやファイル、タスクなどが表示されます。
※招待されたメンバーは管理者でないので、ナビゲーションバーにカテゴリ管理などのボタンは表示されません。
別の組織を作成
試しに別の組織でサインアップして、これまで作成した情報が表示されないことを確認します。
これまでに作成した情報が表示されず、初期画面となっているため、組織ごとにデータが分かれてることがわかります。
終わりに
Udemyのコースの内容が2015年ごろのもので、その通りにコードを描いても動かないことが多く、苦戦しました。(コースではRails4、著者は5で作成)
相変わらずコード書いて実際にweb上で動かして確認して・・とやってしまい、テストはおざなりになってしまった。。
実戦でRails使いたいです
- 投稿日:2019-08-16T16:11:03+09:00
【AWS】開発環境では動くが本番だと�動かない事例集
Qiita初投稿になります。
何か不備や誤りがあったらご指摘をいただけるととてもありがたいです!経緯
スクールでの課題や個人アプリの開発でAWSを使っており、デプロイも双方担当をさせていただいておりました。
その中で、苦戦をした大きな比率を占めるのが、開発環境だと動くのに何故か本番環境だと動かない事例です。
本番で動かないと実際のサービスだと何も意味がないにも関わらず、このパターンが結構ありましたので、これから対処をする方がどういう方法や考え方で解いていけばいいのかをまとめます。
デプロイで苦戦をすると本当にイライラしてしまうので、イライラを少しでも沈めるのに貢献をできたらなと思います。開発環境(個人アプリもスクールの課題も同一です)
言語:Ruby 2.5.1
フレームワーク:Rails 5.2.3
データベース:MySQL5.6
ライブラリ:JQuery
自動デプロイ:capistrano
Webサーバー(本番):nginx
APサーバー(本番):unicorn
Web・APサーバー(開発):puma
今回は双方ともにEC2、S3を用いております。前提とエラーの基本の見方
EC2インスタンスの再起動が1番今まで救われたケースが多いです。まずは試してみましょう。(MySQLとnginxの再起動も必要です)
そして、エラーを解決をする基本はエラーログを見て忠実に動くことです。これは本番環境でも開発環境でも一緒です。
エラーが出る場合、開発環境だとターミナルやビューに出てきます。ただ、本番環境だと出てこないため、本番環境だと別でコマンドで見ていきます。
エラーログの本番環境の場所はlinuxコマンドでログインをして確認をします。注意をするのは自動デプロイの前後でエラーが吐き出される場所が異なることです。自動デプロイをしたにも関わらず元のままの位置でファイルを開いても意味がないです。#手動デプロイ後 /var/www/アプリ名/log/production.log #capistranoでの自動デプロイ後 /var/www/アプリ名/current/log/production.loglogにまで移動をしたらcat等のコマンドでエラーログを見ることができます。
①pumaとの性能の違いで、拡張子がついていなかった時
ActionView::Template::Error (The asset "bannar_image" is not present in the asset pipeline.):
※本番環境のエラーログです。
これが一番はじめのエラーでした。こちらに関しては上記のエラー文を見ると、bannar_imageが間違っているのが何となく分かると思います。
このbannar_imageという記述があったのが、image_tagになるので、image_tagに関して調べてみました。index.erb#エラーが起きたコードのイメージ <%= image_tag 'bannar_image' %> #他のサイトで見た見本のコード <%= image_tag 'flower.png' %>実際にビューファイルを比べてみると何か違和感が、、、上のコードは実は拡張子がなかったのです。
そのため、本番環境では動かなかったのです。ちなみにこの時はそれまで自動デプロイが初めてできた瞬間に出たエラーだったため、自動デプロイのcapistranoのエラーだと勘違いをしてしまっておりました。
自動デプロイのエラーばかり見ていたので、何もエラーログがないのに原因不明のエラーが起こっていると勘違いして、だいぶハマりました。
デプロイがどういう流れになっていてどういう仕組みになっているのかを理解をしていないとこういうところで非常に苦戦をします。というかpumaは何故拡張子がないのに動くのか、、、
②application.jsにJQueryが2つあった時
こちらはそこまで難しくないエラーです。お互いに干渉してしまうため、エラーになるようです。
今出せないのですが、エラーログも出ていて、そんなに複雑なエラーログではありませんでした。application.js=require rails-ujs =require jquery-ujs下のコードはヴァージョンが古い時に使っていたようでして、Rails5.1以降は上のコードを使うみたいです。
これはスクールの発表で先輩グループの発表の際に触れていたので、すぐに分かりました。
①もだけどそもそもpumaの時点でエラー起きてくれないのかな。。。③データベース関連で開発環境とmigrationの状況が異なってしまった時
ActionView::Template::Error (Unknown database 'freemarket_sample_54c_production'):
チーム開発でmearge済みのmigrationファイルを変更をしてその後に再度meargeをしたり、
rake db:reset等を繰り返しているといつか起きてしまうかと思います。
私たちのチームではどうしようもなくなってしまい、結局一から本番環境のデータベースを構築をし直したのですが、これは本来あまりやっていいことではないかと思っています。
もちろん開発の初期段階ですので正直困りはしなかったのですが、実際のサービスだと既に重要な顧客データが多数入っているためです。
こちらは他に何かやり方あれば教えていただけると非常に嬉しいです。ちなみにcapistrano導入後はデータベースをcreateしたり、dropをさせるときもエラーを見るのと同じくcurrent上でコマンドは打ちます。
rake:db:seedも同様で、capistrano導入後はディレクトリの位置が変更をされるためです。④credentials.yml関連
ActiveSupport::MessageEncryptor::InvalidMessage
このエラーは序盤結構多かったです。こちらに関しては他に詳しい記事も多数あるかと思いますので割愛します。
credentials.ymlに関しては最初は結構苦戦をしましたが、慣れるとAPIkeyの管理がすごく簡単でした。
怖がるものではないかと思います。⑤データベース内に数値が入っていなかった時
ActionView::Template::Error (undefined method `id' for nil:NilClass):
すごく単純なようで起きがちなエラーです。
特にテーブルを増やしてカラムに新しく外部キーを増やすと起こりがちです。
私はデータベースをSQLで直接いじっていましたが、Sequelpro等で本番でも見れるようにすることができるので、入れたら楽に設定をできると思います。
※ベーシック認証をしてからだと思うのですが、途中からSequelproは使えなくなりました。おそらく弾かれてしまったのだと思います。もしかするとベーシック認証を使っていてもできるのかもしれませんが、私は調べきれませんでした。⑥JQueryで発火をしなかった※データが入っていない時
エラーログは出ません。
インクリメンタルサーチをしようとしたのですが、本番環境のデータで名前を検索をする際に、そもそも該当の名前のデータが入っていませんでした。。。これは完全に単なる勘違いです。即解決できましたが、全くエラーログが出てこないので原因が不明になるので少しの時間、不安になりました。
これも上記の⑤と一緒でSequelproとか入れたらわかりやすそうです。まとめ
基本はエラーログをしっかりと見ていけば何とかなりました。ただ、データベース関連は視覚化ができていなかったので、苦労をしました。
これに関してはエラーログの構造等を理解しだしてから、格段に理解が深まったので、細かいWebアプリの仕組みに関しては理解をしといたほうがいいなと感じました。
今回挙げた事例に関しては正直かなり簡単なほうだと思っています。これから先も出てくる事例は多いと思うので、また何かあれば記事に挙げていきます。参考記事
https://www.javadrive.jp/rails/template/index11.html
https://www.bokukoko.info/entry/2017/10/27/231129
https://qiita.com/scivola/items/cc06ddbfd94d3118f693
- 投稿日:2019-08-16T16:11:03+09:00
【AWS】開発環境では動くが本番だと動かない事例集
Qiita初投稿になります。
何か不備や誤りがあったらご指摘をいただけるととてもありがたいです!経緯
スクールでの課題や個人アプリの開発でAWSを使っており、デプロイも双方担当をさせていただいておりました。
その中で、苦戦をした大きな比率を占めるのが、開発環境だと動くのに何故か本番環境だと動かない事例です。
本番で動かないと実際のサービスだと何も意味がないにも関わらず、このパターンが結構ありましたので、これから対処をする方がどういう方法や考え方で解いていけばいいのかをまとめます。
デプロイで苦戦をすると本当にイライラしてしまうので、イライラを少しでも沈めるのに貢献をできたらなと思います。開発環境(個人アプリもスクールの課題も同一です)
言語:Ruby 2.5.1
フレームワーク:Rails 5.2.3
データベース:MySQL5.6
ライブラリ:JQuery
自動デプロイ:capistrano
Webサーバー(本番):nginx
APサーバー(本番):unicorn
Web・APサーバー(開発):puma
今回は双方ともにEC2、S3を用いております。前提とエラーの基本の見方
EC2インスタンスの再起動が1番今まで救われたケースが多いです。まずは試してみましょう。(MySQLとnginxの再起動も必要です)
そして、エラーを解決をする基本はエラーログを見て忠実に動くことです。これは本番環境でも開発環境でも一緒です。
エラーが出る場合、開発環境だとターミナルやビューに出てきます。ただ、本番環境だと出てこないため、本番環境だと別でコマンドで見ていきます。
エラーログの本番環境の場所はlinuxコマンドでログインをして確認をします。注意をするのは自動デプロイの前後でエラーが吐き出される場所が異なることです。自動デプロイをしたにも関わらず元のままの位置でファイルを開いても意味がないです。#手動デプロイ後 /var/www/アプリ名/log/production.log #capistranoでの自動デプロイ後 /var/www/アプリ名/current/log/production.loglogにまで移動をしたらcat等のコマンドでエラーログを見ることができます。
①pumaとの性能の違いで、拡張子がついていなかった時
ActionView::Template::Error (The asset "bannar_image" is not present in the asset pipeline.):
※本番環境のエラーログです。
これが一番はじめのエラーでした。こちらに関しては上記のエラー文を見ると、bannar_imageが間違っているのが何となく分かると思います。
このbannar_imageという記述があったのが、image_tagになるので、image_tagに関して調べてみました。index.erb#エラーが起きたコードのイメージ <%= image_tag 'bannar_image' %> #他のサイトで見た見本のコード <%= image_tag 'flower.png' %>実際にビューファイルを比べてみると何か違和感が、、、上のコードは実は拡張子がなかったのです。
そのため、本番環境では動かなかったのです。ちなみにこの時はそれまで自動デプロイが初めてできた瞬間に出たエラーだったため、自動デプロイのcapistranoのエラーだと勘違いをしてしまっておりました。
自動デプロイのエラーばかり見ていたので、何もエラーログがないのに原因不明のエラーが起こっていると勘違いして、だいぶハマりました。
デプロイがどういう流れになっていてどういう仕組みになっているのかを理解をしていないとこういうところで非常に苦戦をします。というかpumaは何故拡張子がないのに動くのか、、、
②application.jsにJQueryが2つあった時
こちらはそこまで難しくないエラーです。お互いに干渉してしまうため、エラーになるようです。
今出せないのですが、エラーログも出ていて、そんなに複雑なエラーログではありませんでした。application.js=require rails-ujs =require jquery-ujs下のコードはヴァージョンが古い時に使っていたようでして、Rails5.1以降は上のコードを使うみたいです。
これはスクールの発表で先輩グループの発表の際に触れていたので、すぐに分かりました。
①もだけどそもそもpumaの時点でエラー起きてくれないのかな。。。③データベース関連で開発環境とmigrationの状況が異なってしまった時
ActionView::Template::Error (Unknown database 'freemarket_sample_54c_production'):
チーム開発でmearge済みのmigrationファイルを変更をしてその後に再度meargeをしたり、
rake db:reset等を繰り返しているといつか起きてしまうかと思います。
私たちのチームではどうしようもなくなってしまい、結局一から本番環境のデータベースを構築をし直したのですが、これは本来あまりやっていいことではないかと思っています。
もちろん開発の初期段階ですので正直困りはしなかったのですが、実際のサービスだと既に重要な顧客データが多数入っているためです。
こちらは他に何かやり方あれば教えていただけると非常に嬉しいです。ちなみにcapistrano導入後はデータベースをcreateしたり、dropをさせるときもエラーを見るのと同じくcurrent上でコマンドは打ちます。
rake:db:seedも同様で、capistrano導入後はディレクトリの位置が変更をされるためです。④credentials.yml関連
ActiveSupport::MessageEncryptor::InvalidMessage
このエラーは序盤結構多かったです。こちらに関しては他に詳しい記事も多数あるかと思いますので割愛します。
credentials.ymlに関しては最初は結構苦戦をしましたが、慣れるとAPIkeyの管理がすごく簡単でした。
怖がるものではないかと思います。⑤データベース内に数値が入っていなかった時
ActionView::Template::Error (undefined method `id' for nil:NilClass):
すごく単純なようで起きがちなエラーです。
特にテーブルを増やしてカラムに新しく外部キーを増やすと起こりがちです。
私はデータベースをSQLで直接いじっていましたが、Sequelpro等で本番でも見れるようにすることができるので、入れたら楽に設定をできると思います。
※ベーシック認証をしてからだと思うのですが、途中からSequelproは使えなくなりました。おそらく弾かれてしまったのだと思います。もしかするとベーシック認証を使っていてもできるのかもしれませんが、私は調べきれませんでした。⑥JQueryで発火をしなかった※データが入っていない時
エラーログは出ません。
インクリメンタルサーチをしようとしたのですが、本番環境のデータで名前を検索をする際に、そもそも該当の名前のデータが入っていませんでした。。。これは完全に単なる勘違いです。即解決できましたが、全くエラーログが出てこないので原因が不明になるので少しの時間、不安になりました。
これも上記の⑤と一緒でSequelproとか入れたらわかりやすそうです。まとめ
基本はエラーログをしっかりと見ていけば何とかなりました。ただ、データベース関連は視覚化ができていなかったので、苦労をしました。
これに関してはエラーログの構造等を理解しだしてから、格段に理解が深まったので、細かいWebアプリの仕組みに関しては理解をしといたほうがいいなと感じました。
今回挙げた事例に関しては正直かなり簡単なほうだと思っています。これから先も出てくる事例は多いと思うので、また何かあれば記事に挙げていきます。参考記事
https://www.javadrive.jp/rails/template/index11.html
https://www.bokukoko.info/entry/2017/10/27/231129
https://qiita.com/scivola/items/cc06ddbfd94d3118f693
- 投稿日:2019-08-16T16:07:08+09:00
識別子
■ローカル変数
先頭:英小文字又は_
構成文字:英数字又は_
スコープ:最初に代入式が使用された位置から、その代入を含むブロックまたはメソッド定義の終わりまで。
初期化前に参照した際の挙動:参照箇所より前に代入文が記述してあるが実行されなかった場合はnil
代入文が記述されていない場合は例外発生sample_nil.rb#ローカル変数aへの代入文があるが実行されないため結果はnilとなる def method(i) if i > 0 then p "pass" a = "hello" end p a end method(-1)sample_nil.rb#ローカル変数aへの代入文がないため例外(NameError)となる def method(i) p a end method(-1)■グローバル変数
先頭:$
構成文字:英数字又は_
スコープ:どこからも参照可能
初期化前に参照した際の挙動:nilsample_nil_global.rb#グローバル変数$aへの代入文がないためnilとなる $b = 1 def method p $a end method■クラス変数
先頭:@@
構成文字:英数字又は_
スコープ:そのクラスの全インスタンスから参照可能
初期化前に参照した際の挙動:例外発生■インスタンス変数
先頭:@
構成文字:英数字又は_
スコープ:そのインスタンス内で参照可能
初期化前に参照した際の挙動:nil
- 投稿日:2019-08-16T15:45:13+09:00
Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #10 リメンバーミー機能編
こんな人におすすめ
- プログラミング初心者でポートフォリオの作り方が分からない
- Rails Tutorialをやってみたが理解することが難しい
こんなことが分かる
- リメンバーミー機能の実装方法
一緒に勉強していこう
注意:リメンバーミー機能は前回(#9)の導入を済ませた上で機能します。今回の流れ
- ログイン画面にリメンバーミーのチェックボックスを表示する
- リメンバーミー機能を実装する
チェックボックスを表示する
まずはログイン画面にチェックボックスを表示する。
クラス名をつける際、Bootstrap4と3では異なるので注意する。
スタイリングはいい感じだったのでほぼそのまま。app/views/sessions/new.html.erb<% provide(:title, "ログイン") %> <div class="container form-container login-container"> <div class="row"> <div class="col"> <div class="form-logo-img"> <%= link_to image_tag('lantern_lantern_logo.png', width: 100), root_path, class: "logo-img" %> </div> <h1 class="form-title">ログイン</h1> <%= form_with(scope: :session, url: login_path, local: true) do |form| %> <div class="form-group"> <%= form.email_field :email, class: 'form-control', placeholder: "メールアドレス" %> </div> <div class="form-group"> <%= form.password_field :password, class: 'form-control', placeholder: "パスワード" %> </div> <div class="form-group form-check"> <%= form.check_box :remember_me, class: 'form-check-input' %> <%= form.label :remember_me, '次から保存(ログイン省略)', class: 'form-check-label' %> </div> <div class="form-group"> <%= form.submit "ログイン", class: "btn btn-info btn-lg form-submit" %> </div> <% end %> <p class="form-go-to-signup-or-login">新しくはじめる方は<%= link_to "こちら", signup_path %></p> </div> </div> </div>/app/assets/stylesheets/application.scss.erb// 中略 .form-submit { width: 100%; // margin-top: 1rem; // 削除 }リメンバーミー機能を実装する
チェックボックスはparams[:session][:remember_me]に保存される。
それぞれオン→'1'、オフ→'0'。
だから実装は超簡単。app/controllers/sessions_controller.rbclass SessionsController < ApplicationController # 中略 def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user params[:session][:remember_me] == '1' ? remember(user) : forget(user) redirect_to user else flash.now[:danger] = 'メールアドレスかパスワードが正しくありません' render 'new' end end # 中略これでリメンバーミー機能の実装は完了。
今日は短いけどここまで。
- 投稿日:2019-08-16T13:57:38+09:00
#Mysql2::Error: Can't connect to MySQL server onについて
起こったこと
備忘録です。低レベルですが、同じエラーを起こしてしまった人のために。
MacOSのアプデを自動でしているため、再起動に入ってしまった。
アプデ後、bundle startをしてもうまく起動しない。
いつもならターミナルがbashからrubyに変わるのに跳ねられてしまう。
色々調べていると、次のエラー文がみられた。Mysql2::Error: Can't connect to MySQL server on なんちゃら
あんまり英語が得意ではないが、MysqlがCan't connectなので接続で来てないらしい。
さてここで重要なのは、僕が初心者すぎてMysqlなんぞやってレベルであること。とりあえずググってみた
解決策は[ $mysql.server start ]
スクリーンショットの検索結果は、残念ながら解決策よりも高等な解決策を示されていた。
以下のURLを参考にしていただきたいです。
https://qiita.com/itooww/items/13055c8bb1d226ee5844原因は再起動によるMySQLの活動停止
当たり前だが、再起動とか電源切るとアプリケーションは止まる。
ただ、エンジニアになって自分がどういう物を使っているかをイメージできないことも多い。
バグだとか、壊れたわけではないが原因がわからないと解決の仕様が無い。。。こうならないための対策
MySQLの自動起動です。これでいちいち立ち上げなくてもよくなります!
https://qiita.com/rinkun/items/c1649bcbe9a79bf2b07e
- 投稿日:2019-08-16T13:01:03+09:00
【備忘録】Rails5でモデル名を変更する方法
前提条件
OS:Mac OS X
Rails:5.2.3
groupモデルをroomモデルに変更します。モデル名の変更方法
最初に結論から。
そのあとそれぞれのコマンドの解説をしています。
※置換で一気に変更する際は、migrationファイルなど変更したくないファイルには気をつけてください。1. 変更するモデルに対応するDBのテーブル名を変更
20190815230119_rename_groups.rbdef change # :groupsが変更前 rename_table :groups, :rooms end変更内容をDBに反映
ターミナル$ rails db:migrate2.モデルに関連するファイル名を全て変更
ターミナル# find 対象ディレクトリ | xargs rename -s 変更前のファイル名 変更後のファイル名 $ find ./ | xargs rename -s group room $ find ./ | xargs rename -s Group Room■ 実行結果の例 ■
groups_controller.rb
↓
rooms_controller.rb3.モデルが使用されてるファイルの中身を全て変更
ターミナル# find 対象ディレクトリ -name "検索する文字列" | LC_ALL=C xargs sed -i -e "s/変換前/変換後/g" $ find ./ -name "*room*" | LC_ALL=C xargs sed -i -e "s/group/room/g" $ find ./ -name "*room*" | LC_ALL=C xargs sed -i -e "s/Group/Room/g"■ 実行結果の例 ■
class Admin::GroupsController < ApplicationController
↓
class Admin::RoomsController < ApplicationController4.バックアップとしてできたファイルを削除
なぜかファイル名の末尾に-eとつくバックアップのファイルが生成されたため削除する。
ターミナル# find 対象ディレクトリ -name "検索する文字列" | xargs rm $ find ./ -name "*.*-e" | xargs rm■ 実行結果 ■
下記のようなファイル名の末尾に-eとつくファイルが削除された。
groups_controller.rb-e使用したコマンドの解説
findコマンド
ファイルやディレクトリを検索できる。
オプション コマンドの説明 -name ファイル名を指定して検索できる xargsコマンド
前に実行したコマンドの結果を引き継げる。
ターミナル# find 対象ディレクトリ | xargs rename -s 変更前のファイル名 変更後のファイル名 $ find ./ | xargs rename -s group room上記の例だと、「find ./」で自分がいる階層のファイルとディレクトリを検索している。
xargsを使ってその検索結果を引き継ぎ、引き継いだ検索結果の中からファイル名がgroupの
ものをroomに変更している。sedコマンド
ファイルの中身のテキストを置換できる。
オプション コマンドの説明 -i ファイルを上書きする s 正規表現で置換処理をする g マッチした全ての文字列を置換する -eコマンドはちょっとよくわかんない。
ご存知の方、ご教授いただけると助かります。LC_ALL=C
出力する言語や時刻などのフォーマットを設定する。
LC_ALL=Cはデフォルトの言語(英語)で出力される。デフォルトの言語を日本語にしているせいかこれつけないとillegal byte sequence
っていうエラーがでるからつけてます。終わりに
モデル名の変更に時間がかかったので自分用のメモとしてこの記事を書きました。
皆様のモデル名の変更するときの参考になれば幸いです。なぜバックアップのファイルが出力されたのか謎。
- 投稿日:2019-08-16T10:52:22+09:00
Database Cleanerの使い方(学習時のメモ)
Database Cleanerとは
Database CleanerとはRubyのデータを削除するためのgemで提供されているパッケージ
今回はRspecテスト実行時に作成したテストデータをテスト終了後にデータベースから削除するために利用setup方法
Gemfileに追記する
(テスト環境でのみ利用できればよかったため以下のような記載に)group :test do gem 'database_cleaner' endそしてターミナルで以下を実行
$ bundle install使い方
RSpecの場合のコードのサンプルは以下
spec_helper.rbRSpec.configure do |config| config.before(:suite) do DatabaseCleaner.strategy = :transaction DatabaseCleaner.clean_with(:truncation) end config.around(:each) do |example| DatabaseCleaner.cleaning do example.run end end end解説
実行順序
before(:suite) before(:all) before(:each) after(:each) after(:all) after(:suite)実行タイミング
:suite =>rspecコマンド実行時にテスト全体の前または後に呼び出される :all => 各contextの前または後に呼び出される :each =>各exampleの前または後に呼び出されるStrategies
データベースのCleanする方法のことをStrategieと呼び、以下の3種類が準備されている
Transaction =>一連の処理をTransactionという単位で囲み、その始点の状態にデータを戻すことで、データベースをCleanする Truncation =>指定したテーブルに格納されているデータをすべて削除する"TRANCATE TABLE 文"を使用して、データベースをCleanする テーブルをいったん削除して改めてテーブルを作成する Deletion =>テーブルはそのままにレコードデータを削除する"DELETE 文"を使用して、データベースをCleanするデータ削除対象テーブルの指定
:truncationのstrategyを使用するときは、
以下のように実行対象(または実行除外対象)のテーブルを指定できるDatabaseCleaner.strategy = :truncation, {:only => %w[widgets dogs some_other_table]}DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]}補足情報
rails_helper.rbファイルに以下のコードが書かれている場合、rspecのテスト時は
before(:each)でtransactionを張ってafter(:each)でrollbackする処理を実行してくれている。rails_helper.rbRSpec.configure do |config| config.use_transactional_fixtures = true end参考文献
https://github.com/DatabaseCleaner/database_cleaner
https://qiita.com/shoichiimamura/items/25942acc1d1bd78ef9c3
- 投稿日:2019-08-16T10:52:22+09:00
Database Cleanerの使い方
Database Cleanerとは
Database CleanerとはRubyのデータを削除するためのgemで提供されているパッケージ
今回はRspecテスト実行時に作成したテストデータをテスト終了後にデータベースから削除するために利用setup方法
Gemfileに追記する
(テスト環境でのみ利用できればよかったため以下のような記載に)group :test do gem 'database_cleaner' endそしてターミナルで以下を実行
$ bundle install使い方
RSpecの場合のコードのサンプルは以下
spec_helper.rbRSpec.configure do |config| config.before(:suite) do DatabaseCleaner.strategy = :transaction DatabaseCleaner.clean_with(:truncation) end config.around(:each) do |example| DatabaseCleaner.cleaning do example.run end end end解説
実行順序
before(:suite) before(:all) before(:each) after(:each) after(:all) after(:suite)実行タイミング
:suite =>rspecコマンド実行時にテスト全体の前または後に呼び出される :all => 各contextの前または後に呼び出される :each =>各exampleの前または後に呼び出されるStrategies
データベースのCleanする方法のことをStrategieと呼び、以下の3種類が準備されている
Transaction =>一連の処理をTransactionという単位で囲み、その始点の状態にデータを戻すことで、データベースをCleanする Truncation =>指定したテーブルに格納されているデータをすべて削除する"TRANCATE TABLE 文"を使用して、データベースをCleanする テーブルをいったん削除して改めてテーブルを作成する Deletion =>テーブルはそのままにレコードデータを削除する"DELETE 文"を使用して、データベースをCleanするデータ削除対象テーブルの指定
:truncationのstrategyを使用するときは、
以下のように実行対象(または実行除外対象)のテーブルを指定できるDatabaseCleaner.strategy = :truncation, {:only => %w[widgets dogs some_other_table]}DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]}補足情報
rails_helper.rbファイルに以下のコードが書かれている場合、rspecのテスト時は
before(:each)でtransactionを張ってafter(:each)でrollbackする処理を実行してくれている。rails_helper.rbRSpec.configure do |config| config.use_transactional_fixtures = true end参考文献
https://github.com/DatabaseCleaner/database_cleaner
https://qiita.com/shoichiimamura/items/25942acc1d1bd78ef9c3
- 投稿日:2019-08-16T06:17:37+09:00
Pay.jpのキー
Ruby 2.5.1
Rails 5.2.3スクールの課題(チーム開発で)デプロイ担当となってしまった私は、Pay.jpのAPIのキーがないというエラーに直面しましたので、少しでも参考になればとやったことを書いておきます。
Pay.jp利用のための公開鍵、秘密鍵の登録
local環境ではdotenv-railsを利用して環境変数を管理していましたが、本番でAPIキーがないことを示すエラーが出ていました。環境変数の読み込みがうまくいっていないということですね。
何はともあれ.bash_profileに書き込む
ローカル、リモート共にキーの情報をbashに直書き!
$ vim ~/.bash_profileAPIの秘密鍵、公開鍵の情報をコピー&ペースト。
$ source ~/.bash_profile有効化するには上記コマンドが必要。記述が変だとエラーが出るので注意しましょう。
結局失敗。
.env.developmentを作成しなかったためでは?
envのメリットは環境ごとに扱う変数の管理ができること。
.env.development
.env.test
.env.developmen
と種類がある。私のチームでは.env.developmentしかなかった。よって、prodductionを作成。
①作成 失敗
②touch コマンドで本番(EC2)current以下に作成 失敗リモートの/env.environment以下に書き込む
ターミナルで以下の操作
$ sudo vim /etc/environment環境変数を有効にするため一旦ログアウト、サイドリモートに接続し直す。
$ env | grep キーの名前これで値が取れているか確認できます。
私の場合はさらにunicornを再起動したらうまくいきました。
- 投稿日:2019-08-16T03:14:29+09:00
Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #9 永続セッション, cookie編
こんな人におすすめ
- プログラミング初心者でポートフォリオの作り方が分からない
- Rails Tutorialをやってみたが理解することが難しい
- ポートフォリオを作成しながら勉強したい
前回:#8 ログイン/ログアウト, FactroyBot編
次回:#10 リメンバーミー機能編こんなことが分かる
- 永続化するメソッドが知れる
- cookieについて分かる
- attr_accessorについて分かる
- クラスメソッドとインスタンスメソッドの違いが分かる
一緒に勉強しませう
今回の流れ
- 永続化の手順をざっくり解説
- 各単語やメソッドを確認
- 永続化を実装
- バグを除去
こういう流れでやって行きます。
どうやってログインを永続化させる?
ログインを永続化させるリメンバーミー機能。
(リメンバーミー機能の実装は次回)
流れ的にはこうらしい(以下Tutorial 9.1 Remember me 機能を引用)
- 記憶トークンにはランダムな文字列を生成して用いる
- ブラウザのcookiesにトークンを保存するときには、有効期限を設定する
- トークンはハッシュ値に変換してからデータベースに保存する
- ブラウザのcookiesに保存するユーザーIDは暗号化しておく
- 永続ユーザーIDを含むcookiesを受け取ったら、そのIDでデータベースを検索し、記憶トークンのcookiesがデータベース内のハッシュ値と一致することを確認する
なんのこっちゃ。
それによく分からない言葉やメソッドやらがいっぱいある。
- cookie
- remember_digest
- remember_token
- User.new_token
- User.digest
- remember
- attr_accessor ← これはRubyの話だけど
これも1つずつ解説しよう。
永続化の仕組みをざっくり解説
これから以下の工程で永続化を行います。
超ざっくりにまとめるとこんな感じ。
- サーバに記憶トークンとIDの暗号作ってもらう
- パソコンに保存する
- 次来た時保存した暗号をサーバと照らし合わせる
- OKだったら勝手にログインする
クッキー(cookie)とは?
クッキーはパソコンとサーバの橋渡しです。
...質素すぎますね。クッキーちゃんというキャラを想像してみよう(強引)
...ここにいました。
はサーバと私たちのパソコンを繋ぐメモ係です。
私たちが「次もログインしたままがいいなあ、サーバさんに伝えといてくれる?」
とお願いすると、は「分かった!」と言ってくれました。
しかし
はか弱いです。
サーバに重要な情報を渡す途中で盗まれたりしたら大変!
なので名前をつけた情報(remember_token)を暗号(remember_digest)にしちゃいました。これで安全!
はサーバさんに情報を伝えるおつかいに行ったのでした。
分かりやすい解説↓
クッキー(cookie)とは?初心者でも分かるように図解remember_token / remember_digestとは?
remember_tokenを暗号化 → remember_digestに代入
こういう関係性。User.new_token / User.digest / rememberとは?
User.new_token → remember_tokenを作るメソッド
User.digest → remember_tokenとかの値を暗号にするメソッド
remember → 暗号をremember_digestに代入するメソッドUser.digestと関連する解説↓
ハッシュ値 (hash value)とはremember_digestに保存するまでを実装する
ここまでくると各メソッドがどういう風に動くかが見えてくる。
- User.new_tokenメソッドでremember_tokenを作る
- User.digestメソッドでremember_tokenを暗号化する
- rememberメソッドで暗号化したremember_tokenをremember_digestに保存する
では早速実装しよう。
その前に1つ。
User.digestやUser.new_tokenはこんな風に書き換えできる。class << self def digest(string) end def new_token end endこれを踏まえた上で実装する。
まずUserモデルにremember_digest属性を追加。bash$ rails generate migration add_remember_digest_to_users remember_digest:string $ rails db:migrateその後user.rbにメソッドを書く。
app/models/user.rbclass User < ApplicationRecord attr_accessor :remeber_token # 中略 class << self def digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end def new_token SecureRandom.urlsafe_base64 end end def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end endattr_accessorは何をしているのか
勘のいい方は分かるかもしれませんが、
本来remember_tokenなんて属性はUserモデルにありません。
(あるのはさっきmigrationで作ったremember_digestだけ)だからremember_token属性に代入できるわけがない。
それをいい感じにやってくれるのがattr_accessor。attr_accessor :remember_tokenこうするとremember_token属性を仮で作ってくれる。
だから存在しないはずのremeber_tokenに代入することができる。分かりやすい解説↓
Rubyのattr_accessorって何?[和訳]
Railsから入った人へ【attr_accessor】って?
永続cookiesでガチセッションするRailsチュートリアル9章どうしてUser.digestやUser.new_tokenにはUser.がつくのか
これも素朴な疑問です。
これらはクラスメソッドと呼ばれるもので、今まで定義してきたインスタンスメソッドとは少々異なります。
- クラスメソッド → クラスオブジェクトからでしか呼び出せない
- インスタンスメソッド → インスタンスオブジェクトからでしか呼び出せない
例えばよくこんなことをしますよね。
user = User.newここでのuserはインスタンスオブジェクト。
この時、↓できるのがインスタンスメソッド。user.hoge今のができず、↓するのがクラスメソッド。
User.hogeどうしてTutorialでは使い分けているのか
User.digestやUser.new_tokenはユーザーオブジェクトが不要です。
つまり暗号化するだけ、トークンを作るだけの処理にユーザ情報は不要。
必要なのは作ってからremember_digestで固有のユーザに代入するときだけ。
(だからrememberはインスタンスメソッドです)それを明示的に示すためにクラスメソッドを使用したのです。
クラスメソッドはクラスそのものの変更や参照する役割にも使用される。
そのような場合にもクラスメソッドを使用する余地がありそう。分かりやすい解説↓
【Ruby】クラスメソッドとインスタンスメソッドについてザクッと分かりやすく説明してみる
Rubyのクラスメソッドとインスタンスメソッドの例クッキーに保存する
続いてIDとトークンをクッキーに保存する。
クッキーを保存するcookiesメソッドがあるので簡単。クッキーの保存期間を20年にするpermanentメソッド、
IDは暗号化していないので暗号化するsignedメソッドも使用する。cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_tokenそしてSessions内で保存までを一気に行うメソッドがあると便利。
だからここまで作ってきたメソッドをrememberメソッドとしてSessionsヘルパーに組み込もう。app/helpers/sessions_helper.rbmodule SessionsHelper def log_in(user) # 中略 def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end def current_user # 中略さっき作ったusers.rbに作ったrememberメソッドとは違う。
メソッド内に入っているuser.rememberこそが、user.rbのrememberメソッドです。たった今作ったSessionsヘルパーのrememberメソッドの方は、
- remember_tokenとremember_digestと作成
- IDとremember_tokenとクッキーに保存
これを一気に行うものなのですね。
クッキーのremember_tokenとUserモデル属性のremember_digestを照らし合わせる
じゃあSessionsコントローラに実装しよう。
def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user remember user redirect_to user else # 中略 end endこれでひとまず新規ユーザ作成したらクッキーに保存されるようになった。
ただこのままだとcurrent_user(#8参照)がクッキーを参照しない。そのためには、
- 一時セッションの場合
- 永続セッションの場合
これらを分けた上で処理を書く必要がある。
簡潔にまとめていただいている記事があるので、
→ 35歳だけどRailsチュートリアルやってみた。[第4版 9章 9.1 Remember me 機能 まとめ&解答例]から内容をお借りします。永続セッションの場合
session[:user_id]が存在すれば、一時セッションからユーザーを取得
cookies[:user_id]が存在すれば、永続セッションからユーザーを取得if (user_id = session[:user_id]) # 一時セッションからユーザーを取り出す elsif (user_id = cookies[:user_id]) # 永続セッションからユーザーを取り出す if # ユーザーが存在し、永続セッションの中の記憶トークンがDBの値と一致する # ログイン処理 end endというわけで処理を書いていくわけだけど、
- 記憶トークンがデータベース内の記憶ダイジェストの値が一致するか
このメソッドがない。
BCryptを参考にして書こう。app/models/user.rbclass User < ApplicationRecord # 中略 def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end endこのメソッドの、
* 引数remember_tokenはあくまでただのローカル変数
* 引数remember_digestはUserモデルの属性(始めにmigrationで生成したもの)なので混同しないよう注意が必要。
準備ができたのでcurrent_userを編集する。
app/helpers/sessions_helper.rbmodule SessionsHelper # 中略 def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end end end # 中略これで永続セッションのログインが完了になる。
永続セッションからログアウトする
今の状態だと、ログアウトが正しく機能しない。
log_outメソッドが一時セッションにしか対応していないからだ。
実装の手順はこう。
- Userモデルにremember_digestを破棄するメソッドを定義
- Sessionsヘルパーに実際のログアウトを行うメソッドを定義
というわけで実装しよう。
remember_digestを破棄するメソッドを定義する
remember_digestにnilを代入することでログイン情報を破棄できる。
それをforgetメソッドとして定義。app/models/user.rbclass User < ApplicationRecord # 中略 def forget update_attribute(:remember_digest, nil) end end実際にログアウトを行うメソッドを定義
Sessionsヘルパーのlog_outメソッドに永続セッションを破棄する処理を追加。
その処理にforgetメソッドを新たに定義する。app/helpers/sessions_helper.rbmodule SessionsHelper # 中略 def forget(user) user.forget cookies.delete(:user_id) cookies.delete(:remember_token) end def log_out forget(current_user) session.delete(:user_id) @current_user = nil end endSessionsヘルパーのforgetメソッドもまた、Userモデルのforgetメソッドと異なるので注意。
(Userモデルのforgetメソッドは、Sessionsヘルパーのforgetメソッドの中のuser.forget)これで一応ログアウトが可能になる。
細かなバグ修正
現状2つのバグが残っている。
- 複数のタブで開いたサイトからログアウトが二重に行われる時のエラー
- 複数のブラウザのうち1つはログアウト、もう1つはログアウトせず終了後再び画面を開く時のエラー
それぞれ対応しよう。
複数タブのエラー対処
エラーの理由:
1度目のログアウトでcurrent_userがnilなのにも関わらず、log_outでforget(current_user)しようと試みるから。よって、ログインしているか確認した上でログアウトするように変更する。
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController # 中略 def destroy log_out if logged_in? redirect_to root_url end end複数ブラウザのエラー対処
エラーの理由:
1つ目のブラウザでremember_digestをnilしたのにも関わらず、もう1つのブラウザではクッキーが残っているので、authenticated?に例外が起こるから。よってauthenticated?内でremember_digestがnilなら即座に認証を終了させる。
app/models/user.rbclass User < ApplicationRecord # 中略 def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end # 中略rubyの慣習的に、1文で済むif文は処理の後に書く。
よってreturn falseを先に、ifはその後。これでようやくバグも取り除いた。
次回はリメンバーミー機能を実装
Tutorialは9.2 [Remember me] チェックボックス直前まできた。
次はリメンバーミー機能を実装する。
Tutorialの解説記事になっている?同じ工程だから仕方ない!!
(そのうちポートフォリオ感出てくることを信じて...)
- 投稿日:2019-08-16T00:27:03+09:00
蟻本の区間スケジューリング問題をrubyで解いてみる(記録用)
問題
入力例
5
1 2 4 6 8
3 5 7 9 10回答例
count=gets.to_i starts=gets.chomp.split(" ").map(&:to_i) ends=gets.chomp.split(" ").map(&:to_i) se=[] kijun=0 answer=0 starts.map.with_index{|item,index| se[index]=[item,ends[index]] } se.sort_by! { |_, b| b } se.map {|item| if kijun<item[0] then kijun=item[1] answer=answer+1 end } puts answer
- 投稿日:2019-08-16T00:21:22+09:00
各桁を一桁になるまで掛け算する回数の大きい数をRubyで探す
twitterで話題になってた算数のお題のやつですが、安直に探すとせいぜい9回程度の数(数千万~数億)しか見つけられません。(11回の数を見つけた小学生凄い!)
素因数に注目する
さてどうしたものかと考えると、"各桁の数字を掛ける"という事は1桁の数を掛けていくという事、つまり途中経過は全て1桁の素因数のみを持つ合成数になっているはず(表1)なので、これを探して行くのが速い気がします。
(表1) bruteforceで見つけた9回の数 26888999 を処理して行く様子
回 数値 素因数分解 0 26888999 51413 * 523 1 4478976 2^11 * 3^7 2 338688 2^8 * 3^3 * 7^2 3 27648 2^10 * 3^3 4 2688 2^7 * 3 * 7 5 768 2^8 * 3 6 336 2^4 * 3 * 7 7 54 2 * 3^3 8 20 2^2 * 5 9 0 0 1桁の素数は2,3,5,7の4つですが、5が混じると一瞬で積が0に収束してしまいがちなので、2,3,7の合成数を探す事にしましょう。
探せる範囲で最大回数となる合成数を見つけ出したら、その合成数の1桁の因数を並べてやる事で最大回数+1の数も見つけられる事になります。実装
という訳で以下Rubyで書いた探索コード
# 回数算出 def count(n) c = 0 while n >= 10 m = 1 while n > 0 && m > 0 m *= n % 10 n /= 10 end n = m c += 1 end c end # 2,3,7の合成数を探索 def search(e2, e3, e7) max = 0 nums = [] e7.times{|k| e3.times{|j| e2.times{|i| n = (2**i)*(3**j)*(7**k) c = count(n) if c == max nums << [n, i, j, k] elsif c > max max = c nums = [[n, i, j, k]] end } } } # 一番回数が多かった2,3,7の合成数から回数+1となる数を生成 nums.sort.each{|n, c2, c3, c7| puts "#{max}: #{n} = 2^#{c2} * 3^#{c3} * 7^#{c7}" m = '2'*c2 + '3'*c3 + '7'*c7 m = m.gsub(/222/,'8').gsub(/33/,'9').chars.sort.join.sub(/23/,'6').sub(/22/,'4') puts "#{max+1}: #{m}" } end require 'benchmark' puts Benchmark::CAPTION, Benchmark.measure { # 2^29 * 3^29 * 7^9 までの合成数を探索 (てきとう) search(30, 30, 10) }結果
10: 4996238671872 = 2^19 * 3^4 * 7^6 11: 277777788888899 10: 937638166841712 = 2^4 * 3^20 * 7^5 11: 27777789999999999 user system total real 0.031000 0.000000 0.031000 ( 0.037331)10回となる2,3,7の合成数がサクっと2つ見つかり、11回となる数を2つ生成できました。
(なお探索範囲を2^199 * 3^199 * 7^199まで広げてみても11回以上になる合成数は見つからず、10回の合成数もこの2つ以外は見つかりませんでした。)他の解法






























