20200602のRailsに関する記事は30件です。

Rubyの『クラス』関係の問題を集めてみて解いてみた。

Rubyのクラスが分からない

チェリー本を読んでいざアウトプットしていこうと思ったものの、自分でプログラムを作るとなると中々解けずに難しい。。。

そんな僕と同じ悩みを抱えている方のために基礎問題を集めてみました。

基礎問題と言えど正直、曖昧な知識のままこの問題を解いていくと分からないことがあります。

出来れば答えを見ずに進めていくのが良いと思いますが、考えてもどうしても分からない場合は記事の下の方に回答を書いておきますので参考にして下さい。

そして、回答を読んでも分からないと言うことが僕自身ありましたので、分からなかった点まで記載していきます。

問題(3問)

①クラス変数とクラスメソッドについて

sample.rb
class Car
  def self.run
    @@count += 1
  end

  def count
    @@count
  end
end

car1 = Car.new
car1.run

car2 = Car.new

car2.run
car1.run

puts Car.count

こちらの記事の問題を参照しました。

●<問題>
上のプログラムは実行するとエラーが出ます。
ある箇所を修正して期待通りの出力ができる様にソースを変更してください。

(期待出力)
・インスタンスメソッドrunを呼ぶとクラスの共有カウンタが1ずつ増加する。
・クラスメソッドcountを呼ぶと現在のカウンタの値が返される。

3と表示されればオッケーです。

●<ヒント>
以下の記事が、この問題を解く上で非常に参考になりました。
https://qiita.com/mogulla3/items/cd4d6e188c34c6819709

②特異メソッド

この記事を引用させていただきました。

food.rb
class Food
  def eat
    puts "I like."
  end
end

natto = Food.new()
wasabi = Food.new()
karaage = Food.new()

natto.eat #=>I like.
wasabi.eat #=>I don't like.
karaage.eat #=>I love.

●<問題>
コメントアウト通りの出力になる様にコードを変えてください。

③Musicクラスを継承したRapクラスを定義する

こちらの記事を参照しました。

●<問題>
Rapクラスへ記述するコードを下記の出力結果から予測してください。

sample.rb
class Music
  def mc
    puts "This is #{@genre} of #{self.class.to_s}"
  end

  def initialize(genre)
    @genre = genre
  end
end

Rap.new("mc-battle").mc

(出力例)

This is mc-battle of Rap
Yo, mic check 1, 2.

ー※※※※※※※※※※※解答※※※※※※※※※※※ー

①クラス変数とクラスメソッドについて

クラス変数とクラスメソッドの使い方を理解していないと解けないですね。

●<クラス変数>

  • クラスとそのインスタンスがスコープになる
  • 定数と似ているがクラス変数は何度でも値を変更できる点で異なる
  • クラスメソッド、インスタンスメソッド、クラス定義式内でアクセス可能

以下の記事が参考で、この問題を解く上で非常に参考になりました。
https://qiita.com/mogulla3/items/cd4d6e188c34c6819709

まず、見るべき点は2、6行目です。

sample.rb
class Car
  def self.run #2行目
    @@count += 1
  end

  def count #6行目
    @@count
  end
end

car1 = Car.new
car1.run

car2 = Car.new

car2.run
car1.run

puts Car.count

気づくことはなかったでしょうか?

最後の行を見てください。
puts Car.count

これは、Carクラスcountメソッドをそび出しているみたいですが、おかしくありませんか?

クラスをそのまま呼び出す際は、クラスメソッド出なければいけません。

修正すると、

sample.rb
class Car
  def self.run #2行目
    @@count += 1
  end

  def self.count #6行目
    @@count
  end
end

#-中略-

puts Car.count #最後の行

そして、2行目もおかしいですね。

こちらはインスタンスメソッドにしなければいけません。

sample.rb
class Car
  def run #2行目/def self.runから変更
    @@count += 1
  end
end

car1 = Car.new
car1.run

なぜなら、classのそとではcar1とインスタンスを作成しているからそのインスタンスを呼び出すためのメソッドとして、インスタンスメソッドにしなければいけません。

しかしこれで終わりではありません。

クラス変数 @@count が定義されていません。

sample.rb
class Car
  @@count = 0 #クラス変数の定義

  def run #クラスメソッド
    @@count += 1 #@@countはクラス変数
  end

  def self.count #インスタンスメソッド
    @@count
  end
end

car1 = Car.new
car1.run

car2 = Car.new

car2.run
car1.run

puts Car.count

# => $ ruby sample.rb 
# => 3

これでオッケーです。

②特異メソッド

そもそも、特異メソッドとは?
1つのインスタンス固有のメソッドのことを指します。

特異メソッドは、def オブジェクト名.メソッド名で定義できます。
https://qiita.com/k-penguin-sato/items/d637dced7af32e4ec7c0

よって、

food.rb
class Food
  def eat
    puts "I like."
  end
end

natto = Food.new()
wasabi = Food.new()
karaage = Food.new()

#--追加--↓

def wasabi.eat
  puts "I don't like."
end

def karaage.eat
  puts "I love."
end

#--追加--↑

natto.eat
wasabi.eat
karaage.eat

同じクラスのインスタンスであるオブジェクトに、同じ名前の動作が異なるメソッドを定義します。

これが特異メソッドで、これはクラスに対してではなくクラスからできたインスタンスに対してのメソッドになります。

よってそれぞれのメソッドからの出力ができます。

(結果)

I like.
I don't like.
I love.

③Musicクラスを継承したRapクラスを定義する

sample.rb
class Music
  def mc
    puts "This is #{@genre} of #{self.class.to_s}"
  end

  def initialize(genre)
    @genre = genre
  end
end

Rap.new("mc-battle").mc

self.class.to_sRap.newの様に作られたインスタンス自体のクラス名を文字列に変換する。

では、Rapクラスと、を作っていきます。

sample.rb
#--中略--
class Rap < Music
end

出力文字を見てください。
Yo, mic check 1, 2.

Musicクラスでは定義されていない出力ですので、Rapクラスで出力していきます。

最後の行のRap.new("mc-battle").mcを見てみると、メソッドはmcが使われていますので、記載していきます。

sample.rb
#--中略--
class Rap < Music
  def mc
  end
end

Musicクラスを継承しているので必要な部分を記載すると

sample.rb
#--中略--
class Rap < Music
  def mc
    puts "Yo, mic check 1, 2."
  end
end

しかしこれで終わりではないです。

まだ、現時点ではMusicクラスの呼び出しができていません。

どうすればいいでしょうか?

「super」メソッドを実行します。

superメソッドは、スーパークラスの中でその呼び出されたメソッドと同じメソッド名を持つメソッドを探して実行します。

それでは、書き換えると

sample.rb
class Music
  def mc
    puts "This is #{@genre} of #{self.class.to_s}"
  end

  def initialize(genre)
    @genre = genre
  end
end

class Rap < Music
  def mc
    super #追加コード
    puts "Yo, mic check 1, 2."
  end
end

Rap.new("mc-battle").mc

完成です。

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

[rails]Haml 投稿フォームについて

送信フォーム(hamlでの表示方法)

%input.class-name__text{tpe: "text"}
%input.class-name__submit{type: "submit" ,value: "POST"}
1.テキストを入れるフォーム
2.送信ボタン

▼ボタンのオプションの解説

クイックリファレンス

リンクをつける

= link_to '#' do
  %i.fa.fa-trash

リンクの表示を変えて、下線などを消す

a {
  text-decoration:none;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[rails]hamlでアイコンを表示させたい!

haml
%i.fa.fa-trash

下記、アイコンについて参考になる便利サイトです。

▼回転させたり、他の機能も使いたい!
【Rails】font-awesome-railsの使い方を徹底解説!

▼アイコンを選びたい
Font Awesome

▼色を指定したい
fontawesomeの色指定!

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

database.ymlファイルの主な役割を2つ述べてください

使い方はわかるけど、言葉で説明する際に
整理しておくシリーズ。

・rake db:createのコマンドを実行した時に作成されるデータベースの名称を指定する。
・RailsアプリケーションがSQLサーバーにアクセスするときのソケットファイルの位置を指定する

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

【Rails】gemのgoogle-cloud-visionでv1.0が出たので対応してみた

以前に書いた記事でコードを書き換える必要があったので対応してみました。

gem google-cloud-visionをv0.x系からv1.0.0にアップデートしたら要注意です。

基本的に

Migrating to google-cloud-vision 1.0
https://github.com/googleapis/google-cloud-ruby/blob/master/google-cloud-vision/MIGRATING.md

に書いてあるとおりなのですが#safe_search_detectionについてはこちらのほうがわかりやすかったです。

    require "google/cloud/vision"
    image_annotator = Google::Cloud::Vision::ImageAnnotator.new

    require "google/cloud/vision/v1"
    image_annotator = Google::Cloud::Vision::V1::ImageAnnotator::Client.new

に書き換えました。

Google Cloud Visionについては以前にも破壊的なバージョンアップがあったような...

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

質問の仕方について

基礎的な事だが、せっかくスクールであった
質問の型なので、メモ書きしておきます。

■解決したいこと
エラー文や、今起こっているトラブルを端的に書く

■自力で調べた内容
検索エンジンを用いた際の検索ワードについてや読んだ記事のリンクを書く

■仮説と検証作業の結果
検索して得た情報から立てた仮説と、その検証作業による結果を書く

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

【Error】編集後に画面遷移しない事象の解消法

概要

タスクの編集画面を実装し、確認のため編集を行い、ボタンを押下したのですが画面が変わらず。。。。:confounded:

結果としては、「redirect_to」していなかった:sweat_smile:ということでしたが、エラー解決するまでの経緯を備忘録として残します!

確認方法

【前提】
編集画面で登録ボタンを押した後は、ボタンは押下できない状況にあった。
画面遷移もされない。
①MySQLに編集データが保存されているかを確認

変更されている:ok_hand:

②エラー画面が出ていないのであれば、ターミナルにエラー情報が記入されているのではないかを確認

terminal
No template found for TasksController#update, rendering head :no_content
Completed 204 No Content in 139ms (ActiveRecord: 3.4ms)

あった:rolling_eyes:
No templateって記載されてた!!

③コントローラーを確認
→redirect_toが記載なし:scream:

修正箇所

修正前

tasks_controller.rb
def update
  @task.update(task_params)
  if @task.valid?
    @task.save
  else
    flash.now[:alert] = 'タスク名を入力してください'
    render :index
  end
end

修正後

tasks_controller.rb
def update
  @task.update(task_params)
  if @task.valid?
    @task.save
    redirect_to group_tasks_path(@group), notice: 'タスクが変更されました'
  else
    flash.now[:alert] = 'タスク名を入力してください'
    render :index
  end
end

皆さま、お気をつけください:bow_tone1:

参考

エラー204に関するURLです。
https://developer.mozilla.org/ja/docs/Web/HTTP/Status/204

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

form_with scope

form_withのscopeについて

こんなフォームがあったけどscopeってどんな意味だってなった

ruby.rb
<%= form_with scope: :session, url: sessions_path do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>

とりあえず検証ツールを見てみる
なるほどこんな働きなわけね。

<form action="/sessions" method="post" data-remote="true">
  <input type="text" name="session[name]">
</form>

つまりscopeオプションはどんなはたらき?

spopeオブジェクトに私た値がname値のプレフィックス(接頭語)になっている

name = "session[name]"

という形でパラメータが送信されている。ってこと

どうやって受け取る?

パラメータのプレフィックスにsessionがついただけなので難しく考えることはないです

controller.rb
def create
  #スコープを用いて送信された値をsession[:user_id]に代入
  session[:user_name] = session_params[:name]
end

こんな働きをしているってことですね。大したことはなかった。

本日はここまで。
form_withひとつとっても色々ありますね。

一人前のエンジニアになるまであと87日

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

ターミナルでよく使うコマンド集(ファイル操作編)

ターミナル
# カレントディレクトリのパスを表示する
$ pwd

表示例→/Users/ユーザ名/Desktop/sample
→print working directoryの略

# 今いるディレクトリの直下にあるファイルやフォルダの名前の一覧を表示します
$ ls

 →listの略

#ファイルのパーミッションの確認
$ ls -l

#移動したい時に使う 
$ cd ファイル名

change directoryの略。

#ホームディレクトリに移動する
$ cd ~/ 

#一つ上の階層のファイルに移動する
$ cd ..

<移動の注意点>

ターミナル
#相対パス
$ cd projects

#絶対パス
$ cd ~/projects

相対パスはフォルダ名(ファイル名)から初めて良い
絶対パスは/から始めます

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

[Rails] 可読性を上げるための第1歩~変数宣言の注意点~

概要

この記事は、

  • プログラムのソースコードを書く際に、読みやすくするための方法を
  • 特に「変数」宣言の仕方に絞って取り上げた

ものです。

動機

私もやっと3年仕事でプログラミングの経験を積んだ、というところですが、開発したり本を読んだりする中で、「読みやすいコードと読みにくいコード」を分けるポイントに、段々気付くことができるようになってきました。

「こういったポイントを早く知ってもらえば、読みにくいコードが減っていき、私も周囲の人もスムーズに気分よく開発できるようになる!」という思いでこの記事を書きました。

Why?

なぜ読みやすいソースコードを書くか

  • プログラミングにおいて、ソースコードの読みやすさはとても大事
  • プログラムは、書く時間よりも読まれる時間の方が圧倒的に多いとされている
  • 使い捨てが確定しているソースならいいのですが、そうではないソースコードが読みにくいと、修正が大変
  • 特に、他人の書いたソースコードが読みにくい場合、「やってらんないわ、もう書いた奴に直させよう...」と感じてしまう

⇒ 読みやすく書いておくことで、後々の効率化に繋げよう!

本題

項目一覧

※「4.」以外は、他のプログラミング言語についても当てはまると思います。

  1. 変数名は詳しく
  2. 使う直前に宣言する
  3. 1つの変数を複数の目的で使わない
  4. インスタンス変数の乱用は危険

「そんなん知ってるよ。」という方は、是非私に色々教えてください(>_<)

1. 変数名は詳しく

変数名には、なるべく詳しい情報を盛り込みましょう!

# bad
a = 1
b = 'ラプタ'
c = 'きみをのせて'

# good
number = 1
title = 'ラプタ'
song = 'きみをのせて'

理由

一目で中身が分かるようにするためです。
a, b, c ... だと、代入した瞬間は中身がわかりますが、30行先でbという変数があるとき、中身を思い出すのは一苦労です。
もう1回値を代入したところを見直さないと思い出せないかもしれません。

なので、中身がはっきりわかるような名前にしましょう。

もうひと手間

より詳しい情報が変数名から読み取れるように工夫してみましょう。

# good
number = 1
title = 'ラプタ'
song = 'きみをのせて'

# very good
rank = 1
movie_title = 'ラプタ'
ending_theme_song = 'きみをのせて'

何に使われているのかわからない変数は、いつの間にか生まれてしまいます。
「ソースコードを書いた翌日、その変数の役割をもう思い出せない」ということは普通に起こります。

なので、役割を思い出す手掛かりになる情報をなるべく盛り込みましょう。
(変数に使用する単語数は、おおむね4個くらいまでが適切とされています。)

2. 使う直前に宣言する

変数は、なるべく呼び出す直前の行で宣言するようにしましょう!

# bad
def play_movie
  main_theme_song = 'なんだっけ....(知らない)'
  ending_theme_song = 'きみをのせて'
  play_music(main_theme_song)

  # - - - - - - - - - - - - - - - - -
  #
  #              処理
  #   ending_theme_song は使われない
  #              20 行
  # - - - - - - - - - - - - - - - - -

  play_music(ending_theme_song)
end


# good
def play_movie
  main_theme_song = 'なんだっけ....'
  play_music(main_theme_song)

  # - - - - - - - - - - - - - - - - -
  #
  #              処理
  #   ending_theme_song は使われない
  #              20 行
  # - - - - - - - - - - - - - - - - -

  ending_theme_song = 'きみをのせて'
  play_music(ending_theme_song)
end

理由

  • 中身がわからなくなる
    「1.」と似たような理由ですが、変数に入っていた値が一体何だったのか、行数が空けば空くほどわからなくなってしまいます。
    近くに書いておけばすぐに確認できます。
  • 途中の処理で使われていないか不安になる
    今回のサンプルでは、間に20行処理が挟まれています。
    開発の途中でこのending_theme_songという変数の中身を変えたり、変数を削除したり、または変数名を変えたくなる時が来たとします。
    そのとき、途中の20行でこの変数が使われているかどうかを確認する必要が発生します。
    もし途中で使われているのに変更を加えてしまったら、バグが発生するかもしれませんので。

この2つの問題は、使用される直前で変数を宣言することで回避できます。

3. 1つの変数を複数の目的で使わない

ある変数の役割が途中から変わる....そんな実装はやめましょう!
これをされてしまうと、最悪の場合、そのソースを書いた人に聞かないと以降の修正ができないという事態にもなりかねません。

# bad
def play_movie
  theme_song = 'なんだっけ....'
  play_music(theme_song)

  theme_song = 'きみをのせて'
  play_music(theme_song)
end

# good
def play_movie
  main_theme_song = 'なんだっけ....'
  play_music(main_theme_song)

  ending_theme_song = 'きみをのせて'
  play_music(ending_theme_song)
end

この例では些細な問題に見えると思います。
どっちでもいいだろうと。
ですが...。

理由

  • どこから役割が変わったのかわからない
    変数の役割が途中で変わると困るのは、処理が長いときや複雑な場合です。
    入り組んだ複雑な処理の途中で変数の役割が変わってしまうと、その処理を全て読み解かないと、どこで役割が変わったのかがわかりません。
    つまり、たった一つの変数に変更を加えたいだけなのに、同じ変数名が使われている処理全てを把握しなければならなくなります。
  • そもそも役割が変わったことに気づけない
    悪夢のような状況です。
    変数は通常一つの役割で使われるものなので、途中で役割が変わるなどとは想像されません。
    仮に2つの用途があるとして、「片方の用途に合わせて変数の中身を変え、もう一方の用途のことは放置される」ということは十分起こり得ます。
    「確認せずに修正する方が悪い」と言っても構いませんが、恨まれるでしょう。

4. インスタンス変数の乱用は危険

Ruby on Railsでは、変数名の前に@をつけて、インスタンス変数を宣言することができます。
インスタンス変数は、そのクラス(controllerなど)内であれば自由にどこからでも呼び出すことができるという便利さがあるのですが、使いすぎに注意しましょう!

基本的には、

  • viewファイルで呼び出す必要のある変数だけをインスタンス変数にする

ことによってかなりマシになります。

# ---------------------------------------- #
#               Bad example
# ---------------------------------------- #
# controllers/samples_controller
before_action :set_music, only: [:show]

def show
  # 画面を開くたびに、1年以上前のmusicを削除
  @old_date_range = Date.new(1900, 1, 1)..Date.today.prev_year
  @musics = Music.where(created_at: @old_date_range)
  @musics.delete_all
end

private
  def set_music()
    @music = Music.find_by(id: params[:id])
  end

# views/samples/playmusic.html.erb
<div>
  <%= @music.title %> 
</div>
# ---------------------------------------- #
#               Good example
# ---------------------------------------- #
# controllers/samples_controller
before_action :set_music, only: [:show]

def show
  # 画面を開くたびに、1年以上前のmusicを削除
  # viewファイルで使わない変数には@をつけない
  old_date_range = Date.new(1900, 1, 1)..Date.today.prev_year
  musics = Music.where(created_at: old_date_range)
  musics.delete_all
end

private
  def set_music()
    @music = Music.find_by(id: params[:id])
  end

# views/samples/playmusic.html.erb
<div>
  <%= @music.title %> 
</div>

理由

  • インスタンス変数の危険なところ
    1. 「宣言」箇所がパッと見分からなくなりやすい
    2. 「使用」箇所がパッと見分からなくなりやすい
    3. 影響範囲が大きくなりやすい

インスタンス変数は、controller内で1度宣言されれば、そのcontroller内のどこからでも呼び出すことができます。

それはメリットではあるのですが、そのせいで、どこで宣言された変数なのか、どこで使用されている変数なのか、わからなくなることが往々にしてあります。

このことを、「影響範囲が大きすぎる」などと呼んだりもします。

その結果、その変数をソースから削除したり変更する際に、影響範囲を調べるのが困難になります。

使う必要がないところでインスタンス変数を使うと、後々自分も他人も困ることになります。

最後に

〇実感する前に取り組む

おそらくですが、後ろのものほど実感がわきにくいと思います。
ただ、「実感したときには時すでに遅し」です。

そのときはもう、読みにくいコードに囲まれてしまい、途方に暮れているかもしれません!

〇ソースコードを読みやすくする真の目的

  • 周りの人に開発を楽しんでもらう
    読みにくいコードの修正は、誰もが嫌がります。
    もしかしたら、そのシステムの担当になることすら避けられてしまうかもしれません。

    エンジニアの多くは、プログラミングを好きで楽しいと思って取り組んでいます。
    もし、読みやすいソースのシステムなら、喜んで担当してくれるようにります。
    (そういったシステムは作るのが難しく、数が少ないため....)

  • 利益を生む
    後任者をイライラさせたり残業過多に追い込めば、その分できる作業が減り、残業代は増え、組織全体の損失になります。
    逆に、読みやすいコードを書くことで業務時間が減り、取り組めることが増え、いずれ利益にもつながるでしょう。

この記事で、可読性の向上に少しでも意義を感じてもらえれば幸いです。
Happy Coding !! (((o(゚▽゚)o)))

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

あともう少し…MySQLサービス起動後、Railsサーバが起動しない3

発生した背景

MySQLサービスが復旧したところで、Railsを起動すると以下のエラーが発生した。

/Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require': dlopen(/Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/mysql2-0.5.2/lib/mysql2/mysql2.bundle, 9): Library not loaded: /usr/local/opt/mysql/lib/libmysqlclient.21.dylib (LoadError)
  Referenced from: /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/mysql2-0.5.2/lib/mysql2/mysql2.bundle
  Reason: image not found - /Users/ichikawadaisuke/projects/krown/vendor/ruby/2.5.0/gems/mysql2-0.5.2/lib/mysql2/mysql2.bundle

対応手順

MySQLサービスを再インストールしたため、既存のGemが動作しなくなったと判断。
Gemの再セッティングを実施する。
①以下コマンドにて原因を多角的に調査する

$ bundle update
$ rails s
(これで復旧するかを試してみるも、Railsサーバは起動しなかった)
$ bundle doctor
(MySQLに紐つくような目立ったエラーは発生していない)

②以下のコマンドでGemを入れ替える

$ bundle exec gem uninstall mysql2
Successfully uninstalled mysql2-0.5.2
$ bundle install --path vendor
(同コマンドで改めてGemを入れなおす)

③やっと…Railsが起動した…

Use Ctrl-C to stop
Started GET "/" for ::1 at 2020-06-02 18:46:42 +0900
ActiveRecord::NoDatabaseError (Unknown database 'krown_development'):

④あとはお決まりのコマンドを実行して、無事終了しました?

$ bundle exec rake db:create
$ bundle exec rake db:migrate
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

has_oneについてメモ

has_oneとbelongs_toの関係について

一対一の関係において外部キーを記述する方にはbelongs_toを用い、
外部キーを置かない方にはhas_oneとして関係性を構築する。

user.rb
has_one :destination
destinaton.rb
belongs_to :user
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HerokuにBugsnagを導入

Heroku × Railsです。

アドオンを追加→APIキーを取得

ダッシュボードから操作する方法と、コマンドラインから操作する方法があります。

ダッシュボードから操作する場合

  • Herokuのダッシュボードにアクセス
  • Resourcesタブを開く
  • Add-onsの下の検索窓に'Bugsnag'と入力
  • Provisionをクリック
  • Settingsタブを開く
  • Reveal Config Varsをクリックし、BugsnagのAPIキーを確認する

コマンドラインから操作

Heroku CLIがインストールされている状態で以下のコマンドを叩く

$ heroku addons:create bugsnag
$ heroku config:get BUGSNAG_API_KEY
70d9b0852a968b1d0d0e329b5507f287 #APIキー

アプリケーション側の設定

Gemfile
gem 'bugsnag'
$ bundle install
$ rails generate bugsnag 70d9b0852a968b1d0d0e329b5507f287 # APIキー

config/initializers/bugsnag.rbが生成される。デフォルトではAPIキーがベタ書きされているので、環境変数にしまう。dotenvというgemを使って以下のように記述しました。

config/initializers/bugsnag.rb
Bugsnag.configure do |config|
  config.api_key = ENV['BUGSNAG_API_KEY'] #修正
end

production環境でのみ動くように修正

config/initializers/bugsnag.rb
Bugsnag.configure do |config|
  config.api_key = ENV['BUGSNAG_API_KEY']
  config.notify_release_stages = ['production'] #追加
end

その他

raise等を使って自分で発生させている例外を捕捉したい場合、Bugsnag.notify(exception)を使う。

begin
  raise 'Something went wrong!'
rescue => exception
  Bugsnag.notify(exception)
end

参考

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

初心者向けRailsでブログを作ってみるチュートリアルその1

これはなに?

初心者向けRailsでブログを作ってみるチュートリアルその0 の続きです。

今回はブログサービスを作ります。
では、ブログサービスの機能を分解して考えてみましょう
実際のブログサービスにはいくつか機能がありますが、今回はブログサービスと呼ぶために必要なミニマムの機能を開発しましょう。

機能一覧

訪問者用機能

  • 記事閲覧機能
  • コメント機能

管理機能

  • 管理者ログイン機能
  • 記事投稿機能

この中で、次のステップから最初に最低限ブログらしいものに必要な「記事閲覧機能」を作っていこうと思います。

サイトマップ

image.png

ワイヤーフレーム

今回は最初に作る記事閲覧機能に含まれる2画面についてのワイヤーフレームを draw.io で作ってみました。
今回は、全体の機能がコンパクトなためワイヤーフレームと次回以降でやるモデリングにとどめて細かい仕様設計などはしません(実際開発者が1-3名程度のスタートアップや新規事業だともっと大雑把なワイヤーフレームしかない状態で進めてもまあまあうまくいったりします)

記事一覧画面

image.png

記事詳細画面

image.png

完成版にはコメント機能をつけますが、最初の時点では省略します

チュートリアルその1まとめ

今回は、開発に移る前の設計の初期段階として何を作るのか、というところを大まかに定義しました。
次回はプロジェクトのセットアップを行います。

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

道のりは長い…( ´Д`)y━・~~ MySQLが起動しない事象への対応

Railsを起動するため、Gem、bundleの再設定を行いました。ようやくアプリケーションを起動することができたのですが、アプリケーションにアクセスするとエラー出力され、アプリが動きません。
非常に道のりが長いですが、1つ1つ対応を進めていきます。

エラー内容:

* Listening on tcp://localhost:3000
Use Ctrl-C to stop
Started GET "/" for ::1 at 2020-06-02 11:31:07 +0900

Mysql2::Error::ConnectionError (Can't connect to local MySQL server through socket '/tmp/mysql.sock' (38)):
(省略)

①そもそもサービスが起動していなかった。(mysql.server statusで確認)

②ログを確認すると、プロセスを起動する際にファイルのオープンに失敗している
(ichikawadaisukenoMacBook-Air.local.err )

200602 11:51:16 mysqld_safe mysqld from pid file /usr/local/var/mysql/ichikawadaisukenoMacBook-Air.local.pid ended

③そういえば、先日Mysqlが稼働しなかったため、homebrewコマンドでインストールしなおした経緯がある。/usr/local/Cellar配下をみると、v5.6系とv8.0が存在していた。
競合している可能性もあるので、先日追加したv8.0系はアンインストールした。

④さらにリンクも再設定すれば復旧するかもしれないと考え、以下を試した。

CMD> brew unlink mysql@5.6
Unlinking /usr/local/Cellar/mysql@5.6/5.6.42... 0 symlinks removed
CMD> brew link --force mysql56
Linking /usr/local/Cellar/mysql@5.6/5.6.42... 99 symlinks created

⑤この後、以下のファイルで直接5.6系のサービスを起動しようとするも
/usr/local/Cellar/mysql@5.6/5.6.42/support-files/mysql.server start

 ERROR! The server quit without updating PID file (/usr/local/var/mysql/ichikawadaisukenoMacBook-Air.local.pid).

⑥出来るだけ、mysqlの再インストールは選択したくなかったのですが、時間を消耗してしまうので、以下のコマンドからまず環境をクリーンにしました。

※ファイルが存在している場合はバックアップを取得する
brew uninstall mysql56
$ sudo rm -rf /usr/local/mysql
$ sudo rm -rf /Library/StartupItems/MYSQL
$ sudo rm -rf /Library/PreferencePanes/MySQL.prefPane
$ sudo rm -rf /Library/Receipts/mysql-.pkg
$ sudo rm -rf /usr/local/Cellar/mysql*
$ sudo rm -rf /usr/local/bin/mysql*
$ sudo rm -rf /usr/local/var/mysql*  ※ここにはファイルが存在したため、bk取得
$ sudo rm -rf /usr/local/etc/my.cnf
$ sudo rm -rf /usr/local/share/mysql*
$ sudo rm -rf /usr/local/opt/mysql*

⑦最後にインストールを実行し、希望のサービスが稼働していることを確認しました。

[\W staff@term]brew install mysql56
Updating Homebrew...
==> Downloading https://homebrew.bintray.com/bottles/mysql%405.6-5.6.47.catalina.bottle.tar.gz
Already downloaded: /Users/ichikawadaisuke/Library/Caches/Homebrew/downloads/c270819d76ed326059143e1b4c6c6c0ab672e4259c328c140ec71d917babc348--mysql@5.6-5.6.47.catalina.bottle.tar.gz
==> Pouring mysql@5.6-5.6.47.catalina.bottle.tar.gz
==> /usr/local/Cellar/mysql@5.6/5.6.47/bin/mysql_install_db --verbose --user=ichikawadaisuke --basedir=/usr/local/Ce
==> Caveats
A "/etc/my.cnf" from another install may interfere with a Homebrew-built
server starting up correctly.

MySQL is configured to only allow connections from localhost by default

To connect:
    mysql -uroot

mysql@5.6 is keg-only, which means it was not symlinked into /usr/local,
because this is an alternate version of another formula.

If you need to have mysql@5.6 first in your PATH run:
  echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc

For compilers to find mysql@5.6 you may need to set:
  export LDFLAGS="-L/usr/local/opt/mysql@5.6/lib"
  export CPPFLAGS="-I/usr/local/opt/mysql@5.6/include"


To restart mysql@5.6 after an upgrade:
  brew services restart mysql@5.6
Or, if you don't want/need a background service you can just run:
  /usr/local/opt/mysql@5.6/bin/mysql.server start
==> Summary
?  /usr/local/Cellar/mysql@5.6/5.6.47: 344 files, 155.2MB
[\W staff@term]/usr/local/opt/mysql@5.6/bin/mysql.server status
 SUCCESS! MySQL running (37799)

次はまたRailsが動かなくなりました…?

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

ターミナルでよく使うコマンド集(Rails編)

バージョンを確認するコマンド

ターミナル
#Rubyのバージョンを確認するコマンド
ruby --version

#Railsのバージョンを確認するコマンド
rails --version

#RubyGemsのバージョンを確認するコマンド
gem -v

rails関連で使うコマンド

ターミナル
#railsのサーバの起動
rails s

#railsサーバの終了
control + C

#コントローラの作成
rails g controller name[option]

#railsアプリの作成
rails new アプリケーション名

#DBとmodelの作成
rails g model テーブル名 カラム:データ型 カラム:データ型

#railsに直接値を代入したり操作する際に使用
rails console

#railsコンソールの起動
rails c

#railsコンソールの終了
exit

bundle関連

ターミナル
#gemfileを元にgemのinstallを行う
bundle install

#gemのバージョンアップ
bundle update
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

macOSをバージョンアップしたらrails s出来なくなった

問題

① macOS Catalina のバージョンを10.15.4にアップデートする

② 現在制作途中のアプリケーションのディレクトリにてrails sする

③ 以下のようなエラーが出る

Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)

解決

https://nodejs.org/ja/download/current/

上記のサイトにて、最新版のNode.jsをインストールするか

ターミナルで、homebrewを用いてインストールを行う

% brew install nodejs     

すると、無事rails sでローカルサーバーを起動できた。

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

capistranoで構築した環境でassetsの中身を1から作り直したい

なぜそんなことをしたくなったのか

capistranoのassets:precompileが走っている時にEC2がメモリ不足に陥り、インスタンスを再起動させた結果、deployが中途半端なところで終わってしまいました。
もし中途半端なコンパイル結果をもとにWebサーバーが動いて実行時エラーなんて起きたときには目も当てられないので、なんとかまっさらな状態に戻そうとしてあれこれしました。

バージョン情報

rails (5.2.4.2)
capistrano (3.11.0)
sprockets (3.7.2)
sprockets-rails (3.2.1)

前提条件

current_pathはこのパスとする

  • /var/www/app_name/current

shared_pathはこのパスとする

  • /var/www/app_name/shared

先に結論

結論から言うと、やるべきことはこれです。

  1. /var/www/app_name/shared/public/assets の中身を消す
  2. /var/www/app_name/shared/tmp/cache/assets の中身を消す
  3. いつもどおりデプロイ

注意点があります。
capistranoにはdeploy:clobber_assetsのようなassetsをいかにも消しそうなコマンドが標準で用意されていますが、
これを使わずちゃんと手で消すということです。理由は後述します。

説明

capistranoのディレクトリ構成

まず、capistranoによってデプロイされたサーバーの/var/www/app_nameの中は、下記のような構造になっています。

├── current (releases内の最新のディレクトリにシンボリックリンクが貼られてる)
├── releases
│   ├── 20200601101105
│   ├── 20200601102714
│   └── 20200601105159
├── repo
├── revisions.log
└── shared

このcurrentの下、つまり、releases/20200601105159の下には、あなたのプロジェクトのリポジトリにあるような構成になっているでしょう。
そこにはapp/があり、config/があり、public/があるはずです。

このpublic/の下にはビルドされた静的ファイルが詰め込まれたassets/が入っている……ように見せかけて、
/var/www/app_name/current/public/assets/var/www/app_name/shared/assetsへのシンボリックリンクになっています。

そして/var/www/app_name/current/tmp/cacheは同様に/var/www/app_name/shared/tmp/cacheへのシンボリックリンクになっています。

assets:precompileのキャッシュの仕組み

Railsガイド アセットのキャッシュストア

デフォルトのSprocketsは、development環境とproduction環境でtmp/cache/assetsにアセットをキャッシュします。

こう書かれている通り、tmp/cache/assetsにコンパイル結果のキャッシュがあるので、これを消します。
capistranoだと/var/www/app_name/shared/tmp/cache/assetsがこれにあたるので消します。

Sprocketsのデプロイ結果のキャッシュの仕組み

assets:precompileのキャッシュとは別に、Sprocketsはデプロイ結果のファイルも取っておいてあります。
Sprocketsのコードにこんなのが書かれています。

module Sprockets
  module Exporters
    # Writes a an asset file to disk
    class FileExporter < Exporters::Base
      def skip?(logger)
        if ::File.exist?(target)
          logger.debug "Skipping #{ target }, already exists"
          true
        else
          logger.info "Writing #{ target }"
          false
        end
      end

      def call
        write(target) do |file|
          file.write(asset.source)
        end
      end
    end
  end
end

deploy:assets:precompileしているときに、標準出力でログがずらっと出てきているのを目にしていませんか? アレを実現しているのがおそらくは上のコードです。
logger.info "Writingのところを見てください。

こんな感じ:

      01 I, [2020-06-01T08:56:09.691790 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…
      01 I, [2020-06-01T08:56:09.692557 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…
      01 I, [2020-06-01T08:56:17.418572 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0002/application-d1bedc9937772b59211a…
      01 I, [2020-06-01T08:56:17.418856 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0002/application-d1bedc9937772b59211a…
      01 I, [2020-06-01T08:56:21.414096 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0003/application-5a60e0a26af1e40a9188…
      01 I, [2020-06-01T08:56:21.414368 #10361]  INFO -- : Writing /var/www/app_name/releases/20200601085319/public/assets/lb-0003/application-5a60e0a26af1e40a9188…

このskip?の判定に引っかかった場合。
つまり、この例だと/var/www/app_name/releases/20200601085319/public/assets/develop/application-232387090aed00e6b038…のdigest込みのファイルが既に存在していた場合、
Sprocketsはファイルを書き出しません。

なので、capistranoだと/var/www/app_name/shared/assetsに同じファイルがあった場合、このskip?でスキップされてしまうということです。
消しておく必要がありますね。

おや? 便利なコマンドがありそうだが……?

capistranoはcap -Tで全てのコマンドを表示してくれます。
assets関係だとこんなコマンドが見つかります。

cap deploy:cleanup_assets          # Cleanup expired assets
cap deploy:clobber_assets          # Clobber assets

標準でdeploy:clobber_assetsというコマンドが用意されているではありませんか!
clobberとはぶん殴るという意味です。強制的に新しいassetsにしてくれそうだ! こりゃいいや! と思って使うと、実は思ったような挙動をしてくれません。

$ bundle exec cap review deploy:clobber_assets

00:00 deploy:clobber_assets
      01 bundle exec rake assets:clobber
      01 I, [2020-06-01T11:22:46.062204 #6756]  INFO -- : Removed /var/www/app_name/releases/20200601094959/public/assets
      01 Removed webpack output path directory /var/www/app_name/releases/20200601094959/public/packs

いかにもassetsをディレクトリごと消してくれていそうな雰囲気ですが、よく見るとRemoveしているのはただのシンボリックリンクで、肝心の中身は消えていません。マジか。
そして、そもそもコンパイル時のキャッシュであるtmp/cacheの方は消してくれていません。

このことについてはissueも上がっているようです。

なので、「結論」に書いた通り、手動でコマンドを実行して消すか、自分でコマンドを定義してやるのが良さそうです。

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

Railsアプリを作成してGitHubにSSHでPush

VPSのセットアップしてcapistranoでデプロイする流れで、Railsアプリを作成する必要があったので簡単にまとめます。情報量の関係上、Rails5です。安全に行きたいので。

環境

Rails 5.2.2
Ruby 2.4.1
MySQL 5.7

You're on Railsを表示

$ mkdir sample_app
$ cd sample_app
$ touch Dockerfile docker-compose.yml Gemfile Gemfile.lock
Dockerfile
FROM ruby:2.4.1

RUN apt-get update -qq && \
  apt-get install -y build-essential \ 
  libpq-dev \        
  nodejs           

RUN mkdir /app_name 
ENV APP_ROOT /app_name 
WORKDIR $APP_ROOT

COPY ./Gemfile $APP_ROOT/Gemfile
COPY ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install
COPY . $APP_ROOT
docker-compose.yml
version: "3"
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"
  web:
    build: .
    command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app_name
    ports:
      - "3000:3000"
    links:
      - db

Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.2'
Gemfile.lock
#空
$ docker-compose run web rails new . --force --database=mysql --skip-bundle
$ docker-compose build --no-cache #--no-cacheオプションが無いと、bundle installが実行されないことがある
config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db

development:
  <<: *default
  database: app_name_development

test:
  <<: *default
  database: app_name_test

production:
  <<: *default
  database: app_name_production
  username: app_name
  password: <%= ENV['APP_NAME_DATABASE_PASSWORD'] %>
$ docker-compose up
$ docker-compose run web rails db:create

http://localhost:3000 にアクセスしてYay!You're on Rails!を確認

適当なページを作成

$ docker-compose run web rails g controller tests index
config/routes.rb
Rails.application.routes.draw do
  get 'tests/index'
  root to: 'tests#index'
end

http://localhost:3000 にアクセスして表示を確認

sshキーペアを作成しGitHubに登録

すでに登録済みの場合はとばしてください。

$ mkdir -p ~/.ssh/github
$ cd ~/.ssh/github
$ ssh-keygen -f id_rsa
$ vim ~/.ssh/config
~/.ssh/config
Host github.com #ここが「github」だと動かないので注意
  HostName github.com
  Port 22
  IdentityFile ~/.ssh/github/id_rsa
  User git
$ pbcopy < id_rsa.pub

GitHubにリポジトリを作成

  • GitHubにログイン
  • 画面右上のアイコン > Your repositoriesをクリック
  • Newをクリック
  • リポジトリ名を入力し Create repository をクリック

Push

$ git add -A
$ git commit -m "First commit"
$ git remote add origin git@github.com:<githubのアカウント名>/<リポジトリ名>.git
$ git push -u origin master
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】Docker+Rails6+puma+nginx+mysql【環境構築*初心者必見】

本記事の目的

本記事ではDockeを用いてRails6の環境構築を行うことを目的としています。
Rails6ではyarnというjsのパッケージ管理ツールとwebpackerがデフォルトとなっているので、Rails5の手順で環境構築を行うとエラーが出てしまいます。
よって本記事ではRails6対応の環境構築を目的としています。

※Dockerそのものについてはあまり触れませんので、Docker基礎を学習してからの方が良いかもしれません。

ディレクトリ構成

環境構築する際の全体の構成を以下に示します。
ここでは「webapp」というファイルを作成してアプリケーションを作っていきます。

全体構成
/webapp
├── containers
│   └── nginx
│       ├── Dockerfile
│       └── nginx.conf
├── docker-compose.yml
├── Dockerfile
├── environments
│   └── db.env
├── Gemfile
└── Gemfile.lock

Dockerfile(Rails用)

Rails用のDockerfileは以下のように記述します。
Rubyのバージョンは2.7.1を用います。

ここでのポイントは、yarnとNode.jsのインストールコマンドを書いている点です。

また、本記事ではNodeのバージョンを現時点での最新14.0を使っています。
もし、アプリケーションを作成した時に「Nodeをアップグレードしてください」と出た場合、setup_14〜の数字を最新のバージョンに変えてみてください。

Dockerfile
FROM ruby:2.7.1

# リポジトリを更新し依存モジュールをインストール
RUN apt-get update -qq && \
    apt-get install -y build-essential \
                       nodejs

# yarnパッケージ管理ツールインストール
RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y yarn

# Node.jsをインストール
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - && \
    apt-get install nodejs

# ルート直下にwebappという名前で作業ディレクトリを作成(コンテナ内のアプリケーションディレクトリ)
RUN mkdir /webapp
WORKDIR /webapp

# ホストのGemfileとGemfile.lockをコンテナにコピー
ADD Gemfile /webapp/Gemfile
ADD Gemfile.lock /webapp/Gemfile.lock

# bundle installの実行
RUN bundle install

# ホストのアプリケーションディレクトリ内をすべてコンテナにコピー
ADD . /webapp

# puma.sockを配置するディレクトリを作成
RUN mkdir -p tmp/sockets

Gemfile

インストールするRailsのバージョンを指定します。
Rails6に対応するように書いています。

Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6'

Gemfile.lock

こちらはファイルの作成のみでOKで、中身は書かなくて構いません。

Dockerfile(Nginx)

Dockerfile
FROM nginx:1.15.8

# インクルード用のディレクトリ内を削除
RUN rm -f /etc/nginx/conf.d/*

# Nginxの設定ファイルをコンテナにコピー
ADD nginx.conf /etc/nginx/conf.d/webapp.conf

# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

Nginx設定ファイル

nginx.conf
# プロキシ先の指定
# Nginxが受け取ったリクエストをバックエンドのpumaに送信
upstream webapp {
  # ソケット通信したいのでpuma.sockを指定
  server unix:///webapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  # ドメインもしくはIPを指定
  server_name example.com [or 192.168.xx.xx [or localhost]];

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  # ドキュメントルートの指定
  root /webapp/public;

  client_max_body_size 100m;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  try_files  $uri/index.html $uri @webapp;
  keepalive_timeout 5;

  # リバースプロキシ関連の設定
  location @webapp {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://webapp;
  }
}

DB情報ファイル

ここでのユーザー名やパスワードは変更して構いません。

db.env
MYSQL_ROOT_PASSWORD=db_root_password
MYSQL_USER=user_name
MYSQL_PASSWORD=user_password

docker-compose.yml

docker-vcompose.yml
version: '3'
services:
  app:
    build:
      context: .
    env_file:
      - ./environments/db.env
    command: bundle exec puma -C config/puma.rb
    volumes:
      - .:/webapp
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
      - log-data:/webapp/log
    depends_on:
      - db
  db:
    image: mysql:5.7
    env_file:
      - ./environments/db.env
    volumes:
      - db-data:/var/lib/mysql
  web:
    build:
      context: containers/nginx
    volumes:
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
    ports:
      - 80:80
    depends_on:
      - app
volumes:
  public-data:
  tmp-data:
  log-data:
  db-data:

Railsの生成(Rails new)

Dockerコンテナ内でRailsを生成しなければいけないので、docker-compose run コマンドを使います。
ここではGemをインストールしません。

$ docker-compose run --rm app rails new . --force --database=mysql --skip-bundle

puma.rbの編集

先ほどのコマンドで生成されたpuma.rbファイルを以下のように編集します。

puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true

database.ymlの編集

Railsはデフォルトで、localhost上でDBが動作するようになっているので、hostを先ほど作成したdbに変更します。
また、ここにおける「MYSQL_USER」 と「 MYSQL_PASSWORD」 は DBファイルで定義した環境変数名を設定してくだい。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch('MYSQL_USER') { 'root' } %>
  password: <%= ENV.fetch('MYSQL_PASSWORD') { 'password' } %>
  host: db

development:
  <<: *default
  database: webapp_development

test:
  <<: *default
  database: webapp_test

イメージのビルド

docker-compose.ymlで、app・db・webのbuildに指定されているDockerfileを元にイメージを作成します。

$ docker-compose build

コンテナ起動

ビルドが完了しイメージができたら以下のコマンドでコンテナを立ち上げます

$ docker-compose up -d

ここでコンテナが起動しているのかを以下のコマンドを入力して確認します。

docker-compose ps

そうすると以下のようにコンテナの状況が見れると思います。

    Name                  Command               State          Ports       
---------------------------------------------------------------------------
webapp_app_1   bundle exec puma -C config ...   Up                         
webapp_db_1    docker-entrypoint.sh mysqld      Up      3306/tcp, 33060/tcp
webapp_web_1   /bin/sh -c /usr/sbin/nginx ...   Up      0.0.0.0:80->80/tcp 

webpackerのインストール

起動したコンテナ内でwebpackerをインストールするので、以下のコマンドでインストールします。
Dockerfileになにかしら記入すれば、いちいちコマンドを入力しなくても良いとは思いますが、今回はこの方法を用います。

docker-compose exec app rails webpacker:install

以下のメッセージが出現すればインストール完了です。

Webpacker successfully installed ? ?

DBの作成

DBの作成は必須なので、コンテナ内でDBを作成します。

docker-compose exec app rails db:create

確認

以下のリンクをクリックして「You're on Rails」が表示されるか確認しましょう。
http://localhost
上手くいけば、Rails6で起動できていることが分かるかと思います。

スクリーンショット 2020-06-02 14.03.37.png

補足

localhostに変更内容が反映されていなくて、思うような結果が得られなければ、一度コンテナを閉じて、再起動させるのがいいでしょう。

コンテナ終了
docker-compose down
コンテナ起動
docker-compose up 

参考サイト

以下の記事を参考にしながら環境構築をしました。
本記事だけでなく、以下のサイトも確認してみてください。

①Docker + Rails + Puma + Nginx + MySQL
②DockerでRuby on Railsの環境構築を行うためのステップ【Rails 6対応】
③[Docker] Node.js以外のコンテナに追加でNode.jsの最新版をインストールするDockerfileの記述

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

カテゴリー編集機能の実装

はじめに

メルカリ風アプリを作成しています。
商品編集機能の実装を行いましたので、記載していきます。
カテゴリー機能にはancestry を使っております。
①商品出品時に登録したカテゴリーが孫カテゴリーまで表示される
②新しくカテゴリーを選択できる。
を念頭に作成しました
下のgifが完成品です。

Alt text

コードと解説

HAML

      .listing-form-box
          .listing-product-detail__category
            = f.label 'カテゴリー', class: 'listing-default__label'
            %span.listing-default--require 必須
            .listing-select-wrapper--edit
              .listing-select-wrapper--edit__parent
                = f.collection_select :category_id, Category.roots, :id, :name ,{prompt: "選択してください", selected:  @product.category.parent.parent_id}, {class: 'listing-select-wrapper--edit__parent--select', id: 'parent_category_edit', name: "" }
              .listing-select-wrapper--edit__child
                = f.collection_select :category_id, @product.category.parent.parent.children, :id, :name ,{prompt: "選択してください", selected: @product.category.parent_id}, {class: 'listing-select-wrapper--edit__child--select', id: 'child_category_edit', name: "" }
              .listing-select-wrapper--edit__grandchild
                = f.collection_select :category_id,@product.category.parent.children, :id, :name ,{prompt: "選択してください", selected: @product.category.id}, {class: 'listing-select-wrapper--edit__grandchild--select', id: 'grandchild_category_edit', name: "product[category_id]" }

collection_selectの後ろの方でselectedというのがありますが、そちらで登録していたカテゴリーを引っ張り出しています。孫カテゴリーのidを登録したいので、親カテゴリーと子カテゴリーにはnameで空欄にしています。これでカテゴリーに変更がなかった場合に孫カテゴリーが登録されます。上記の①はできました!

controller

def edit
    @product = Product.find(params[:id])
  end

  def update
    @product = Product.find(params[:id])
    if @product.update(update_params)
      redirect_to root_path, notice: '更新されました'
    else
      render :edit
    end
  end

  def get_category_children
    @category_children = Category.find(params[:category_id]).children
  end

  def get_category_grandchildren
    @category_grandchildren = Category.find("#{params[:child_id]}").children
  end

  private
  def update_params
    params.require(:product).permit(:buyer_id, :category_id, :product_name, :explain, :price, :brand, :condition, :arrive_at, :shipping_fee, :region_id, images_attributes: [:src, :id])
  end

コントローラーと後ほど書くjbuilderに関してはcreateアクションの時とほぼ一緒です。
下記のURLを参考にしました。
書いていただいた方、ありがとうございます!
https://qiita.com/gakinchoy7/items/ac1d8e64e33c3ddd377b

jbuilderとjQuery

get_category_children.json.jbuilder

json.array! @category_children do |child|
  json.id child.id
  json.name child.name
end

get_category_grandchildren.json.jbuilder

json.array! @category_grandchildren do |grandchild|
  json.id grandchild.id
  json.name grandchild.name
end

jQuery

// オプションを作成
$(function(){
  function appendOption(category){
    var html = `<option value="${category.id}" data-category="${category.id}">${category.name}</option>`;
    return html;
  }

// 子カテゴリー用のhtmlの作成。子カテゴリーのidはいらないので name=""としています。
  function appendChild(insertHTML) {
    var childSelectHTML = '';
    childSelectHTML = ` <div class="listing-select-wrapper--edit__child">
                          <select class="listing-select-wrapper--edit__child--select" id="child_category_edit" name="">
                            <option value="---" data-category="---">---</option>
                            ${insertHTML}
                          </select>
                        </div>`;
    $('.listing-select-wrapper--edit').append(childSelectHTML);
  }

// 孫カテゴリー用のhtmlを作成。孫カテゴリーのidが欲しいのでname="product[category_id]"としています。
  function appendGrandChild(insertHTML) {
    var grandChildSelect = '';
    grandChildSelect = `<div class="listing-select-wrapper--edit__grandchild">
                          <select class="listing-select-wrapper--edit__grandchild--select" id="grandchild_category_edit" name="product[category_id]">
                            <option value="---" data-category="---">---</option>
                            ${insertHTML}
                          </select>
                        </div>`;
    $('.listing-select-wrapper--edit').append(grandChildSelect);
  }

// 親カテゴリーの値が変わった時の処理を書きます
  $('#parent_category_edit').on('change', function() {
  // 親カテゴリーのデータを取得して変数にいれる
    var parentCategoryEdit = document.getElementById('parent_category_edit').value;
    if (parentCategoryEdit != '選択してください'){
    // ajaxの処理。urlを/products/get_category_childrenにしないとrenderでeditに戻された時に上手く働かない
      $.ajax({
        url: '/products/get_category_children',
        type: 'GET',
        data: { category_id: parentCategoryEdit },
        dataType: 'json'
      })
    // 成功した時の処理
      .done(function(children){
     // 元々あった子カテゴリーと孫カテゴリーを消す。
        $('.listing-select-wrapper--edit__child').remove();
        $('.listing-select-wrapper--edit__grandchild').remove();
     // insertHTMLを定義して中身にオプションをつける。
        var insertHTML = '';
        children.forEach(function(child){
          insertHTML += appendOption(child);
        });
     // オプション付きのinsertHTMLをappendChildにいれる。
     // 上のappendChildで定義された$('.listing-select-wrapper--edit').append(childSelectHTML);により一番、つまり親カテゴリーのしたに差し込まれる。
        appendChild(insertHTML);
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    }else {
      $('#child_category_edit').remove();
      $('#grandchild_category_edit').remove();
    }
  });

  // 子カテゴリーとやっていることは基本的に同じです 
  $('.listing-select-wrapper--edit').on('change', '#child_category_edit', function(){
    var childIdEdit = document.getElementById('child_category_edit').value;
    if (childIdEdit !== "---") {
      $.ajax({
        url: '/products/get_category_grandchildren',
        type: 'GET',
        data: { child_id: childIdEdit },
        dataType: 'json'
      })
      .done(function(grandchildren) {
        if (grandchildren.length != 0) {
          $('#grandchild_category_edit').remove();
          var insertHTML = '';
          grandchildren.forEach(function(grandchild){
            insertHTML += appendOption(grandchild);
          });
          appendGrandChild(insertHTML);
        }
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    }else {
      $('#grandchild_category_edit').remove();
    }
  })
})

これで商品編集画面にてカテゴリー選択が新たにできるようになりました!

最後に

ajaxに関して理解が曖昧な点があったので最初は作るのに苦労しました。
youtubeやキータを漁って良かったと思います。
上記の記述でもっと簡単に書ける等のアドバイスをいただけたら幸いです。

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

rails6 + nginx のapiに別なdocker-composeのNext.jsフロントエンドからアクセスしたい

環境

Rails 6.0.3
Nginx 1.17.9
Next.js 9.4.4
docker 19.03.8

やりたいこと

バックエンド、フロントエンドをそれぞれ別なdocker-composeで立ち上げて、フロントエンドからバックエンドのapi http://localhost/api/v1/prefs(都道府県API)を叩く。

解決したこと

  • 異なるdocker-compose間のlocalhostによる通信
  • rails6でのhostの追加

1. 異なるdocker-compose間のlocalhostによる通信

バックエンド・フロントエンドのアプリケーションをそれぞれ立ち上げて、フロント側から簡易的にapiにアクセスしようとした。

pages/index.tsx
import Link from 'next/link'
import Layout from '../components/Layout'
import 'isomorphic-fetch';

function IndexPage({ results }: {results: any}){
  return (
    <Layout title="Home | Next.js + TypeScript Example">
      <h1>Hello Next.js ?</h1>
      {results[0].pref}
      <p>
        <Link href="/about">
          <a>About</a>
        </Link>
      </p>
    </Layout>
  )
}

IndexPage.getInitialProps = async ({ req }: {req: any}) => {
  const res = await fetch('http://localhost/api/v1/prefs');
  const data = await res.json();
  return {
    results: data,
  };
}

export default IndexPage

エラった。
スクリーンショット 2020-06-02 11.21.46.png

apiサーバまでリクエストが届いていないようで、そちらにはログが出ない。
そもそもdocker-composeで立ち上げたアプリケーションはそれぞれが別のネットワークになっているので、localhostにはapiサーバが存在していない。

こちら1を参考に、フロント側のdocker-composeを編集した。

docker-compose.yml
version: '3'
services:
  react-next:
    build:
      context: ./
      dockerfile: ./Dockerfile
    container_name: react-next
    tty: true
    volumes:
      - ./app:/app
    ports:
      - '8080:8080'
    command: sh -c "yarn run dev"
# 以下追記
    networks:
      - バックエンドのネットワーク名
networks:
  バックエンドのネットワーク名:
    external: true

バックエンドのネットワーク名はdocker network lsで調べることができる。
スクリーンショット 2020-06-02 11.40.18.png
NAMEを記載しておく。
過去のたくさんアプリケーションを立ち上げていると、その分のネットワーク名がすべて出るので、該当のものを探す。

記載したらフロントエンドのコンテナに入ってみる。
今回はシェルとしてzshを入れているので、フロントエンドのコンテナ(コンテナ名:react-next)に入るコマンドは
docker-compose run --rm react-next /bin/zsh

コンテナに入ったらフロントエンドのserverコンテナにアクセスしてみる。
ping -c4 server

ちなみに、上記の変更前はpingコマンドを使ってもこの状態
スクリーンショット 2020-06-02 12.20.45.png

変更後はこちら。
スクリーンショット 2020-06-02 12.23.20.png

通信はできるようになった。
しかし、エラーは変わらず。

2. rails6でのhostの追加

通信はできるようになったのに、なぜリクエストは通らないのか。

こちら2を参考に、index.tsxを書き換えた。

pages/index.tsx
import Link from 'next/link'
import Layout from '../components/Layout'
import 'isomorphic-fetch';

function IndexPage({ results }: {results: any}){
  return (
    <Layout title="Home | Next.js + TypeScript Example">
      <h1>Hello Next.js ?</h1>
      {results[0].pref}
      <p>
        <Link href="/about">
          <a>About</a>
        </Link>
      </p>
    </Layout>
  )
}

IndexPage.getInitialProps = async ({ req }: {req: any}) => {
  // 以下変更
  const res = await fetch('http://server/api/v1/prefs');

  const data = await res.json();
  return {
    results: data,
  };
}

export default IndexPage

しかし、変わらず。

なぜなのか。

コンテナに入って、やけくそでcurl http://server/api/v1/prefsを実行した際に気になる結果が。
スクリーンショット 2020-06-02 12.29.10.png

serverホストがブロックされた、とある。
エラーメッセージで検索をしたところ、こちら3を見つけた。

このページ通りにバックエンドのrailsサーバの設定を書き換える。

config/environments/development.rb
Rails.application.configure do
  # 略

  # 追記
  config.hosts << "server"

  # 略
end

変更したらrailsサーバを再起動。
もう一度curlコマンド叩いてみる。

スクリーンショット 2020-06-02 12.46.07.png

お!

ブラウザに急ぐ。

スクリーンショット 2020-06-02 12.48.30.png

そこには燦然と輝く『北海道』の文字。

無事にapiから都道府県のデータ取得ができました。

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

【Rails】Google Mapの表示方法

目標

ezgif.com-video-to-gif.gif

前提

下記実装済み。

Googleアカウントを作成済み。
Slim導入

Google Cloud Platformに登録

1.下記リンクにアクセス

Google Cloud Platform

2.「使ってみる」をクリック

スクリーンショット 2020-06-02 10.25.36.png

3.プロジェクト名(適当で良い)を入力し、「作成」をクリック

スクリーンショット 2020-06-02 10.36.24.png

4.「請求先アカウントの作成」をクリック

スクリーンショット 2020-06-02 10.38.49.png

5.利用規約にチェックを入れ、「続行」をクリック

スクリーンショット 2020-06-02 10.40.35.png

6.請求情報を入力し、「無料トライアルを開始」をクリック

スクリーンショット 2020-06-02 10.43.38.png

APIキーを取得

1.「APIの概要に移動」をクリック

スクリーンショット 2020-06-02 10.53.15.png

2.「APIとサービスを有効化」をクリック

スクリーンショット 2020-06-02 10.55.46.png

3.「Maps JavaScript API」をクリック

スクリーンショット 2020-06-02 10.58.27.png

4.「有効にする」をクリック

スクリーンショット 2020-06-02 10.58.37.png

5.「認証情報」をクリック

スクリーンショット 2020-06-02 11.01.05.png

6.「APIとサービスの認証情報」をクリック

スクリーンショット 2020-06-02 11.01.35.png

7.「認証情報を作成」をクリック

スクリーンショット 2020-06-02 11.01.43.png

8.「APIキー」をクリック

スクリーンショット 2020-06-02 11.01.50.png

9.一旦「閉じる」をクリック

スクリーンショット 2020-06-02 11.13.09.png

10. 「APIキーの名前」をクリック

スクリーンショット 2020-06-02 11.06.41.png

11.認証情報の設定をする

①アプリケーションの制限
なしを選択する。

②APIの制限
キーを制限を選択し、プルダウンメニューからMaps JavaScript APIを選択する。

Maps JavaScript APIが選択されている事を確認して、保存をクリック
スクリーンショット 2020-06-02 11.10.13.png

12.赤枠で囲われているマークをクリックし、APIキーをコピー

スクリーンショット 2020-06-02 11.18.56.png

実装

1.APIキーを環境変数化

①「gem 'dotenv-rails'」を導入

Gemfile
gem 'dotenv-rails'
ターミナル
& bundle

②アプリケーション直下に「.env」ファイルを作成
※アプリケーションのディレクトリに移動してから下記コマンドを実行

ターミナル
$ touch .env 

スクリーンショット 2020-06-02 11.32.54.png

.envファイルを編集

.env
GOOGLE_MAP_API = 'コピーしたAPIキー' # 追記

.gitignoreファイルを編集

.gitignore
/.env # 追記

2.ビューを編集

~html.slim
/ マップを表示
#map style='height: 500px; width: 500px;'

/ APIを読み込み
- google_api = "https://maps.googleapis.com/maps/api/js?key=#{ ENV['GOOGLE_MAP_API'] }&callback=initMap".html_safe
script{ async src=google_api }

javascript:

  let map;

  function initMap() {
    geocoder = new google.maps.Geocoder()

    // マップを作成
    map = new google.maps.Map(document.getElementById('map'), {
      // マップの中心に表示する場所の緯度経度を指定
      center: { lat: 40.7828, lng:-73.9653 },
      zoom: 12,
    });

    // マーカーを立てる場所の緯度経度を指定
    marker = new google.maps.Marker({
      position: { lat: 40.7828, lng:-73.9653 },
      map: map
    });
  }

スクリーンショット 2020-06-02 11.58.42.png

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

[Rails]初学者がform_withの流れを整理・理解するための記事

はじめに

Rails学習初期に、form_withの流れが理解できなかったのでまとめます。
Rails5.2.4です。

先にまとめ

「早くまとめろよ!」という方もおられるかと思いますので、先に流れをまとめます。
スクリーンショット 2020-06-02 1.08.39.png

簡単に解説

UserモデルとPostモデルがある状況です。

①newアクションで、ビューページ(new.html.erb)を表示
newアクションを呼び出すリクエストが出されたら、まずはpostの新規投稿(new)ページが表示される
※イメージ
73ee6ee22fb145fb7034907eef85317e.png

②newアクションで生成している@postインスタンスをビューに渡す

post_controller.rb
class PostsController < ApplicationController
~~
  def new
    @post = Post.new #ここで生成されている@postが、new.html.erbに渡される
  end
~~

③インスタンスのプロパティに値を入力してsubmit
f3253df790131a6ac795eeff52d4ee5d.gif

④createアクションを呼び出す
上記の画像のように、投稿するボタン(form_withのsubmit)をクリックすると、form_withがcreateアクションを呼び出してくれます。

※form_withヘルパーメソッドは、渡されたインスタンス(この場合は@post)のプロパティを見て、createに飛ばすかupdateに飛ばすかを判断してくれる便利な子です。

form_withが考えてること
- @post が空 → 初めて作るんだな!じゃあcreate!
- @post が空じゃない → 変更したいんだな!じゃあupdate!

⑤post_paramsメソッドを呼び出す

post_controller.rb
class PostsController < ApplicationController
~~
  def create
    #createアクションが呼び出され、Post.create(post_params)が実行される→ post_paramsが呼び出される
    Post.create(post_params) 
    redirect_to root_path
  end

  private
  def post_params 
    params.require(:post).permit(:title, :content).merge(user_id: current_user.id)
  end
~~

⑥paramsで受け取った値のうち、permitで受け取る値を指定(許可)

paramsで受け取る値の状態
[1] pry(#<PostsController>)> params
=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"oLGgLMTcqSPi7cbK9j1wFhH5rqiwXYO7GyRoZcZFZe6Y5VdbHrWCOyeo37kgW/bsl+eANQrz7p/lAzZMnAS8Gg==", 
"post"=>{"title"=>"今日学んだこと", "content"=>"form_withは賢い子!"}, 
"commit"=>"投稿する", "exept"=>:index, "controller"=>"posts", "action"=>"create"} permitted: false>

見にくいので本来ない位置で改行していますが、通常は1行で返ってきます。

"post"=>{"title"=>"今日学んだこと", "content"=>"form_withは賢い子!"},
post_paramsで扱う値です。

permitで受け取る値を許可しておかないと、意図しない値がテーブルに保存される可能性があるので、指定がほぼ必須です。

⑦post_paramsの値でPostをcreateする(成功すればテーブルに保存される)
post_paramsメソッドで返された値で、Post.createすることになるので、
実質下記と同じ状態です。

  def create
    Post.create(title: "今日学んだこと", content: "form_withは賢い子!", user_id: 1) 
    redirect_to root_path
  end

この流れを確認した上で、もう一度冒頭の画像を見ると理解が深まると思います!

おわりに

form_tag、form_forとの違いはこちらの記事にまとめています。
[Rails]hamlでのform_with/form_for/form_tagの書き方

間違い等ありましたらご指摘いただけると幸いです。

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

【Rails】rails newしてPostgreSQLでデータベース作成まで

はじめに

今年はWebにも少しづつ挑戦しようということで、参考文献の一番多そうなRailsをイジイジしています。取り敢えずローカルでの環境構築を終えてRails newして色々と試しているところです。
このあたりで一度頭の中を整理しつつテンプレ化しておきたいのと、間違いの指摘など頂けたら嬉しいなあと思って記事を書いています。
あとはRails newがうまくいかない人の参考になれば嬉しいです。

環境

terminal
Windows10
ruby 2.6.6
Rails 6.0.3.1
psql (PostgreSQL) 12.3

環境構築に関しては書きませんので必要に応じてお調べください。
データベースはPostgreSQLに変更します。
それとgemについては基本的にローカルに入れてます。どちらでもいいみたいなんですが、なんとなくそうしています。

アプリケーションのディレクトリ内にGemfileを作成する

ディレクトリは適当に作ってください。

terminal
C:\Users\user\sample_app> bundle init

Gemfileに追加するgemを記述する

作成したGemfileをエディタで開き、以下を追加する。

Gemfile
gem 'rails'//コメントアウト(#)を外すだけ
gem 'pg'//PostgreSQLに変更する場合

gemのバージョンを指定したほうがいいのか、よく分からなかったので一先ず指定しないでおく。

bundle installする

terminal
bundle install --path vendor/bundle

--path vendor/bundleはgemをローカルにインストールする場合に指定する。こうすることでvendor/bundle内にgemがインストールされるらしい。
--path vendor/bundleは最初の一回目だけ指定すれば、あとは省略できるみたい。

rails newする

terminal
bundle exec rails new . -d postgresql --skip-turbolinks --skip-test

bundle execはRailsをローカルにインストールした場合につける。以降全てのrailsコマンドにつけます。
-d postgresql --skip-turbolinks --skip-testはオプションでこれら以外にも色々あるみたいなので必要に応じてお調べてください。
いまいち分からないものが多いので取り敢えずみんな書いているものだけ書く。

SQL Shellでデータベースを作成する

SQLShell(psql)
create role APPLICATION_NAME with createdb login password 'PASSWORD';
select * from pg_user;

APPLICATION_NAMEにはユーザーネーム(アプリ名)、PASSWORDには任意のパスワードをいれます。
select * from pg_user;でロールが作成されているか確認します。

database.ymlを更新する

configフォルダ内のdatabase.ymlに以下を記述します。

database.yml


default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

  username: APPLICATION_NAME//追加(データベース作成時に決めたユーザーネーム)
  password: PASSWORD//追加(データベース作成時に決めたパスワード)
  host: localhost//追加


database.ymlを保存したらデータベースも更新します。

terminal
bundle exec rails db:migrate:reset

サーバーを起動する

terminal
bundle exec rails s

ブラウザでhttp://localhost:3000/
にアクセスして画像が表示されれば成功。

rails.png

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

複数の正規表現にmatchするかの判定

こんな正規表現のバリデーションメソッドがあった時、

  def customer_number_valid_format?
    return true if a.blank?
    return true if b.blank?
    binding.pry
   regex = /\A(\d{5})-?(\d{5})-?(\d{1})-?(\d{2})\z/, /\A(\d{4})-?(\d{3})-?(\d{3})\z/
  # p regex => [/\A(\d{5})-?(\d{5})-?(\d{1})-?(\d{2})\z/, /\A(\d{4})-?(\d{3})-?(\d{3})\z/]
    return true if regex.blank?
    unless customer_number.match?(regex)  ?
      self.errors.add(:customer_number, :invalid_and_confirm, target: 'お客様番号')
    end
  end
unless customer_number.match?(regex)

ここでcustomer_number12345-12345-1-121234-123-123の時にtrueを返して欲しいが上記コードだと

[27] pry(#<Order>)> customer_number.match?(regex)
TypeError: wrong argument type Array (expected Regexp)

となる。
match?はStringクラスのメソッドだからだね。
https://docs.ruby-lang.org/ja/latest/method/String/i/match=3f.html

解決

配列に入ってるどっちかの正規表現にあってるか判定するにはRegexp.unionというものを使うらしい。

customer_number.match?(Regexp.union(regex))
[25] pry(#<Order>)> Regexp.union(regex)
=> /(?-mix:\A(\d{5})-?(\d{5})-?(\d{1})-?(\d{2})\z)|(?-mix:\A(\d{4})-?(\d{3})-?(\d{3})\z)/
# 配列ではなくパイプで繋がれ他文字列になる

[26] pry(#<Order>)> low_voltage_customer_number.match?(Regexp.union(regex))
=> true

参考: 配列を渡して正規表現オブジェクトをつくる

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

投稿時にファイルから選択した画像を表示する方法メモ(HTMLファイル上でJS)

Railsで画像投稿時にファイルから選択した画像をその場で確認できる方法。

htmlファイル
%img{id:image-file}
      :javascript
        document.addEventListener(change, function(e){
          let file = e.target.files[0];
          let fileReader = new FileReader();
          fileReader.onload = function() {
            let dataUri = this.result;
            let img = document.getElementById(image-file);
            img.src = dataUri;
          }
          fileReader.readAsDataURL(file);
        });

・addEventListener さまざまなイベント処理を実行することができるメソッド

・document Chrome等のブラウザ上で表示されたドキュメントを操作する事

・change changeイベントを発生

・target.files ファイルの取得する

・FileReader ユーザーのコンピューター内にあるファイル (もしくはバッファ上の生データ) をウェブアプリケーションから非同期的に読み込むことが出来る。

・result ファイルの内容を返す

・getElementById 指定された idの属性を持つ、要素の参照を返します

・readAsDataURL ファイルオブジェクトをData URIに変換するメソッド

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

jQuery1系をWebpackerで流用した話

背景

新規開発プロジェクトにてデザインの一部を他アプリから流用したいリクエストがありました。
そのアプリはjquery1系で動いていたため、そのまま流用しても後述の環境では動かず・・・。
Chromeの開発者ツールによるデバッグで「$ is not defined」が頻発したりとだいぶハマったので備忘録として残します。

局所的にjqueryを利用するため、アプリ内におけるグローバル化は実施しておりません。

参考

以下の回答が大変参考になりました!

Webpackでjqueryなどが認識されない

環境

Rails: 6.0.2
Webpacker: 4.2.2
jQuery: 3.5.1
パッケージマネージャ: yarn

流用したコード(抜粋)

test.js

'use strict';

(function($){
  // 各処理
  var hoge = (function () {
    var $fuga = $('viewのclassまたはid属性')
    return{
      piyo: function () {
        // 取得したclass,id属性に対してjqueryで実現したい処理
      }
     }
  })();
  $(window).on('load', function(){
    // ページが読み込まれた時に実行するモジュール化された関数たち
    hoge.piyo();
  });

})(jQuery);

CDNによるjquery読み込み

test.html

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

修正したコード(抜粋)

test.js

'use strict';

import $ from 'jquery';

$(function(){
  // 各処理
  var hoge = (function () {
    var $fuga = $('viewのclassまたはid属性')
    return{
      piyo: function () {
        // 取得したclass,id属性に対してjqueryで実現したい処理
      }
     }
  })();

  $(window).on('load', function(){
    // ページが読み込まれた時に実行するモジュール化された関数たち
    hoge.piyo();
  });

});

webpackerによるjavascript読み込み

[webpacker管理対象ディレクトリ]/packs/app.js

import '../[任意のディレクトリ]/test.js';

test.html.slim(要件に従いslimを採用しております)

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

開発中の Rails アプリケーションに後から Active Storage を追加したくなったときの話

rails new したときにはいらないと思っていた Rails の機能を後から追加したくなること、皆さんはありませんか? 最近作っているアプリでそんなことがあったので対処法をまとめておきます。

前提

API モードで作り始めた Rails アプリケーションで試していますが、API モードでなくても成り立つ話だと思います。

バージョンは公開時点で最新の 6.0.3.1 です(バージョンアップ大切)。

経緯

Active Storage の使い方は Rails ガイド なり先人の記事に詳しくまとまっているので省略します。

これに沿って作っていこうとしたところ、その一番最初の手順である rails active_storage:install の実行に失敗しました。task がないって言われた...。

┏╸..t/football-game-reporter-api · ⎇ add-game-catch-image ‹✔›
┗╸❯❯❯ bundle exec rails active_storage:install
rails aborted!
Don't know how to build task 'active_storage:install' (See the list of available tasks with `rails --tasks`)
bin/rails:4:in `<main>'
(See full trace by running task with --trace)

一覧を出してみると確かにない...。

┏╸..t/football-game-reporter-api · ⎇ add-game-catch-image ‹✔›
┗╸❯❯❯ bundle exec rails --tasks
rails about                          # List versions of all Rails frameworks and the environment
rails app:template                   # Applies the template supplied by LOCATION=(/path/to/template) or URL
rails app:update                     # Update configs and some other initially generated files (or use just update:configs or update:bin)
rails db:create                      # Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all t...
rails db:drop                        # Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to dr...
rails db:environment:set             # Set the environment value for the database
rails db:fixtures:load               # Loads fixtures into the current environment's database
rails db:migrate                     # Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)
rails db:migrate:status              # Display status of migrations
rails db:prepare                     # Runs setup if database does not exist, or runs migrations if it does
rails db:rollback                    # Rolls the schema back to the previous version (specify steps w/ STEP=n)
rails db:schema:cache:clear          # Clears a db/schema_cache.yml file
rails db:schema:cache:dump           # Creates a db/schema_cache.yml file
rails db:schema:dump                 # Creates a db/schema.rb file that is portable against any DB supported by Active Record
rails db:schema:load                 # Loads a schema.rb file into the database
rails db:seed                        # Loads the seed data from db/seeds.rb
rails db:seed:replant                # Truncates tables of each database for current environment and loads the seeds
rails db:setup                       # Creates the database, loads the schema, and initializes with the seed data (use db:reset to also drop the da...
rails db:structure:dump              # Dumps the database structure to db/structure.sql
rails db:structure:load              # Recreates the databases from the structure.sql file
rails db:version                     # Retrieves the current schema version number
rails log:clear                      # Truncates all/specified *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)
rails middleware                     # Prints out your Rack middleware stack
rails restart                        # Restart app by touching tmp/restart.txt
rails secret                         # Generate a cryptographically secure secret key (this is typically used to generate a secret for cookie sessi...
rails spec                           # Run all specs in spec directory (excluding plugin specs)
rails spec:models                    # Run the code examples in spec/models
rails spec:validators                # Run the code examples in spec/validators
rails stats                          # Report code statistics (KLOCs, etc) from the application or engine
rails time:zones[country_or_offset]  # List all time zones, list by two-letter country code (`rails time:zones[US]`), or list by UTC offset (`rails...
rails tmp:clear                      # Clear cache, socket and screenshot files from tmp/ (narrow w/ tmp:cache:clear, tmp:sockets:clear, tmp:screen...
rails tmp:create                     # Creates tmp directories for cache, sockets, and pids
rails yarn:install                   # Install all JavaScript dependencies as specified via Yarn
rails zeitwerk:check                 # Checks project structure for Zeitwerk compatibility

対処法

Rails 本家の README に書いてある通り require "active_storage/engine" すれば十分でした。

NOTE: If the task cannot be found, verify that require "active_storage/engine" is present in config/application.rb.

試してみると無事に task が追加されています。

┏╸..t/football-game-reporter-api · ⎇ add-game-catch-image ‹✚1›
┗╸❯❯❯ git diff
diff --git a/config/application.rb b/config/application.rb
index b47c87a..dec7181 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -5,7 +5,7 @@ require "rails"
 require "active_model/railtie"
 require "active_job/railtie"
 require "active_record/railtie"
-# require "active_storage/engine"
+require "active_storage/engine"
 require "action_controller/railtie"
 # require "action_mailer/railtie"
 # require "action_mailbox/engine"

┏╸..t/football-game-reporter-api · ⎇ add-game-catch-image ‹✚1›
┗╸❯❯❯ bundle exec rails --tasks
rails about                          # List versions of all Rails frameworks and the environment
rails active_storage:install         # Copy over the migration needed to the application
(以下略)

これで Active Storage を実行する準備が整いました。

原因

おそらく rails new したときに --skip-active-storage をしていたのでこの行がコメントアウトされており、それを元に戻し損ねていたのが原因です。

本家の README を読めば一瞬でわかりますね、反省。あとは使えるはずの機能が使えないあたりで require が足りていないことくらいには気付きたかったです、もっと反省。

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

selectbox テスト Capybara::ElementNotFound

前提

rspec
Capybara
導入済み

本題

今までほとんどfill_inを使用していたためselectboxのテストを行ってみたところ下記のようなエラーにハマりました。
Capybara::ElementNotFound: Unable to find field "北海道" that is not disabled

・間違い

system.article_spec.rb
select "北海道", from 1

・変更点

system.article_spec.rb
select "北海道", from "article[place_id]"
_form.htm
<select class="form-control" id="article_place" name="article[place_id]"><option value="">選択してください</option>
<option value="1">北海道</option>
<option value="2">青森県</option>
<option value="3">岩手県</option>
<option value="4">宮城県</option>
<option value="5">秋田県</option>
<option value="6">山形県</option>
<option value="7">福島県</option>
<option value="8">茨城県</option>
<option value="9">栃木県</option>
<option value="10">群馬県</option>
<option value="11">埼玉県</option>
             ・
             ・
             ・

上記で問題なくテスト成功できました。

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