おぴよの気まぐれ日記

おぴよの気まぐれ日記

岡山やプログラミング、ファッションのこと、子育てや人生、生き方についての備忘録。

【Vue】API読み込み中にグルグル(ローディング/スピナー)を表示する

こんちには。opiyoです。

vueでデータが読み込まれるまでの時間をローディング画像/スピナー画像を表示させて急に画像が表示されてユーザーを困らせないようにしたい!

API通信中にローディング画像を表示させて通信完了したら取得したデータに切り替えたい!

こんな悩みを解決していきたいと思います。

結論から言ってしまうと

  • 「vue-simple-spinner」を使う
  • v-showを使って表示/非表示を制御する

API連携する際にどうしてもタイムラグが出てしまってました。ローディング画像が無いと、急にシュッとデータが表示されるで何だかなーって思っていたので、グルグルを表示する方法を調べてみました!

vueでローディング/スピナーを表示する「vue-simple-spinner」のインストール

npmの場合

npm install vue-simple-spinner --save

CDNの場合(今回こっち使います)

<script src="https://cdn.jsdelivr.net/npm/vue-simple-spinner@1.2.8/dist/vue-simple-spinner.min.js"></script>

githubはこちらから。

https://github.com/dzwillia/vue-simple-spinner

github見ると、最新は1.2.10かなーと思って読み込むurlも変えてみたんだけどエラーになったので1.2.8でやりました。

vueでローディング/スピナーを表示する「vue-simple-spinner」の使い方

1. ライブラリを取り込む

<script src="https://cdn.jsdelivr.net/npm/vue-simple-spinner@1.2.8/dist/vue-simple-spinner.min.js"></script>

2. コンポーネント=componentsを定義する

const Spinner = window.VueSimpleSpinner;
var app = new Vue({
  components: {
    Spinner
  }
})

3. テンプレート呼び出し

<spinner></spinner>

サンプル

では早速、画面を表示してみましょう!

「vue-simple-spinner」を使ってローディング画像を表示
「vue-simple-spinner」を使ってローディング画像を表示

グルグル = ローディング画像がちゃんと表示されていますね。

vueでAPI通信中にローディング/スピナーを表示する

次は、より実践的なものを試してみたいと思います!

API連携中はローディング画像を表示。API連携が完了したら取得したデータを表示させる。ってのをやってみます!

1. 表示/非表示を制御するフラグを定義する

var app = new Vue({
    el: "#app",
    data: {
        message: 'Vueのローディング画像を検証するよ',
        isLoading: true
    },
})

2. API通信後にフラグをfalseにする

今回はワンチャンの画像を取得できるAPIを使ってみます!

APIを取得する方法はaxiosを使い、取得後にフラグを変更させます。

mounted :function(){
    target = this
    axios.get('https://dog.ceo/api/breeds/image/random')
        .then(function(response) {
            target.image = response.data.message;
            target.isLoading = false
        })
        .catch(function(response) {
            console.log(response);
        });
}

3. フラグを関知する設定を定義する

APIで取得したワンチャンの画像 = imageを表示させます。

また、v-showを使ってisLoadingtrue/falseを制御します。

<div id="app">
        <p>{{ message }}</p>
        <spinner v-show="isLoading"></spinner>
        <img v-show="!isLoading" :src="image" alt="">
</div>

実際に動かして見ると............

API取得後のイメージ画像
API取得後のイメージ画像

リロードすると.......

API取得後のイメージ画像を再読み込み
API取得後のイメージ画像を再読み込み

ローディング画像が表示された後に新しい画像に変わってる事が確認できますね!

今回使ったプログラム

今回使ったプログラムは、こちらのgithubにもアップしておきました。

GitHub - nakanoTaku/vue-simple-spinner: vue-simple-spinnerの検証プログラム

が、こっちにも全文書いておきます!

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="app">
        <p>{{ message }}</p>
        <spinner v-show="isLoading"></spinner>
        <img v-show="!isLoading" :src="image" alt="">
    </div>

<!--  vue関連読み込み  -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-simple-spinner@1.2.8/dist/vue-simple-spinner.min.js"></script>
<script>
    const Spinner = window.VueSimpleSpinner;
    var app = new Vue({
        el: "#app",
        data: {
            message: 'Vueのローディング画像を検証するよ',
            image: '',
            isLoading: true
        },
        components: {
            Spinner
        },
        mounted :function(){
            target = this
            axios.get('https://dog.ceo/api/breeds/image/random')
                .then(function(response) {
                    target.image = response.data.message;
                    target.isLoading = false
                })
                .catch(function(response) {
                    console.log(response);
                });
        }
    })
</script>
<style>
    #app {
        margin: 20px;
        text-align: center;
    }
    img {
        width: 120px;
        height: 120px;
    }
</style>
</body>
</html>

まとめ

vueは便利で面白いですねー。

API使って何か処理するみたいなことは多くの場面で使うだろうから、実務でも使うことが出てきそうです。

今日は、「vue-simple-spinner」を使ったローディング画像の表示方法をまとめてみました。

使い方のおさらいです。

  1. cdnでライブラリを読み込む
  2. コンポーネントを定義する
  3. htmlでテンプレートを読み込む

こんな感じです。

が、vueは絶賛勉強中なので間違いあれば「マサカリ」お待ちしております!!

【Ruby on Rails】子の作成時に親のupdated_atを更新したり更新しなかったりする方法

こんちには。opiyoです。

railsでデータをsaveしたりupdateしたりするとupdated_atが更新されるけど、親データのupdated_atを更新したり、更新しないようにするにはどうすればいいの!?

こんな悩みを解決していきたいと思います。

結論から言ってしまうと

  • 親データのupdated_atを更新する: touch
  • 親データのupdated_atを更新しない: no_touching

これらを使うと簡単に実現することが出来ます!

親データに紐づく子データを夜間処理で作るようなことをしてるんだけど、朝見ると親データのupdated_atが全部夜間の日付になっていました。 何も触ってないのに表示順が変わっていたので何事かと思う経験がありました...

touchの存在は知ってたのですが、no_touchingについては全く知らなかったので改めて一緒に使い方を調べてみました!

本文

事前準備

先ずは親データと子データが1 : Nになるデータを作ります。

# 親データ
create_table "new_users", force: :cascade do |t|
  t.string "name"
  t.integer "age"
end

# 子データ
create_table "posts", force: :cascade do |t|
  t.bigint "new_user_id"
  t.string "title"
  t.text "body"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

親データ: new_user.rb

class NewUser < ApplicationRecord
  has_many :posts, dependent: :destroy
end

子データ: post.rb

class Post < ApplicationRecord
  belongs_to :new_user
end

では、このデータを基に実際に動かしていこうと思います。

railsのモデルでtouchを使って親データのupdated_atも更新する

先ずはtouchを定義します。子データのbelongs_totouch: trueを設定すると、postデータが更新されると勝手にnew_userデータのupdated_atが更新されます。

子データ: post.rb

class Post < ApplicationRecord
  belongs_to :new_user, touch: true
end
> user.updated_at.to_s
=> "2019/12/16 23:23"

> user.posts.create!(title: 'touchを検証するよ')
   (0.2ms)  BEGIN
  Post Create (0.5ms)  INSERT INTO "posts" ("title", "created_at", "updated_at", "new_user_id") VALUES ($1, $2, $3, $4) RETURNING "id"  [["title", "touchを検証するよ"], ["created_at", "2020-01-14 12:59:54.286549"], ["updated_at", "2020-01-14 12:59:54.286549"], ["new_user_id", 16]]
  NewUser Update (0.3ms)  UPDATE "new_users" SET "updated_at" = $1 WHERE "new_users"."id" = $2  [["updated_at", "2020-01-14 12:59:54.288109"], ["id", 16]]
   (1.7ms)  COMMIT

> user.updated_at.to_s
=> "2020/01/14 21:59"

上のログを見て欲しいのですが、postデータをcreateした後に親のnew_userデータをupdateしてるのが分かります!

子データを作成するときにno_touchingを使って親データのupdated_atを更新させない

次はno_touchingを使うとtouchが定義されていてもupdated_atが更新されないことを確認してみようと思います。

使い方は親モデル.no_touching do hoge endです。

> user.updated_at.to_s
=> "2020/01/14 22:05"

> NewUser.no_touching do
*   user.posts.create!(title: 'no_touchingを検証するよ')  
* end  
   (0.2ms)  BEGIN
  Post Create (0.4ms)  INSERT INTO "posts" ("title", "created_at", "updated_at", "new_user_id") VALUES ($1, $2, $3, $4) RETURNING "id"  [["title", "no_touchingを検証するよ"], ["created_at", "2020-01-14 13:07:28.128973"], ["updated_at", "2020-01-14 13:07:28.128973"], ["new_user_id", 16]]
   (1.6ms)  COMMIT

> user.updated_at.to_s
=> "2020/01/14 22:05"

こちらを実行すると先ほど実行されていた、親データのnew_userデータのupdate文は走ってないのが分かると思います!

railsで親のupdated_atを更新したり更新しなかったりする方法まとめ

改めて親データのupdated_atの扱い方法をまとめてみます!

更新する 子モデル側のモデルにbelongs_to new_user, touch trueを付ける

更新しない 子データを更新する際に親モデル.no_touching do hoge endで囲ってあげる。

以上になります。

【Ruby on Rails】時分秒を操るにはTime#changeメソッド

こんにちは。opiyoです。

今回はRailsを使った時分秒を操り方をの紹介です。

  • ある一定の時間内のデータを取得したい!
  • 時分秒だけじゃなくて年月日はできるの?

「1ヶ月」 = 「月初 ~ 月末」ってのはよく使うから知っているのですが、「時間」 = 「12時00分00秒 ~ 12時59分59秒」までの1時間内に登録されたデータを取得したい場面があって調べてみました。

changeメソッドで「時間」を操る

changeメソッドの引数にhourと指定したい時間を渡します。

> Time.current.to_s
=> "2020/01/09 22:00"

> Time.current.change(hour: 1).to_s
=> "2020/01/09 01:00"

changeメソッドで「分」を操る

changeメソッドの引数にminと設定したい分を渡します。

> Time.current.to_s
=> "2020/01/09 22:01"

> Time.current.change(min: 30).to_s
=> "2020/01/09 22:30"

changeメソッドで「秒」を操る

changeメソッドの引数にsecと設定したい秒を渡します。 ※to_sすると秒が消える....??

> Time.current
=> Thu, 09 Jan 2020 22:03:27 JST +09:00

> Time.current.change(sec: 59)
=> Thu, 09 Jan 2020 22:04:59 JST +09:00

changeメソッドで年月日を操る

Dateにもchangeメソッドは準備されていて、年月日を操ることができます。

> Date.current.to_s
=> "2020/01/09"
> Date.current.change(year: 2019).to_s
=> "2019/01/09"
> Date.current.change(month: 12).to_s
=> "2020/12/09"
> Date.current.change(day: 31).to_s
=> "2020/01/31"

年月日を操る時は便利メソッドがある!

  • 年を操る
> Date.current.to_s
=> "2020/01/09"
> Date.current.beginning_of_year.to_s
=> "2020/01/01"
  • 月を操る
> Date.current.end_of_year.to_s
=> "2020/12/31"
> Date.current.beginning_of_month.to_s
=> "2020/01/01"
  • 日を操る
> Date.current.end_of_month.to_s
=> "2020/01/31"
> Date.current.beginning_of_day.to_s
=> "2020/01/09 00:00"
> Date.current.end_of_day.to_s
=> "2020/01/09 23:59"

昔の記事で使い方まとめています! opiyotan.hatenablog.com

Railsで時分秒を操るchangeメソッドまとめ

例えば、現在の時刻を基準に「XX時00分00秒 ~ XX時59分59秒」の1時間内のデータを取得したい場合はこんな感じになるかなと思います。

start_time = Time.current.change(min: 00)
end_time = Time.current.change(min: 59, sec: 59)
AccessLog.where(created_at: start_time..end_time)

以上になります。

【Rails】JSONデータを返却する「gem jbuilder」の便利な使い方

こんにちは。opiyoです。

今回はJSONデータを作る際に便利な「gem jbuilder」についてです。 RailsAPIに徹する事が世の中的に多くなってきてると思うので、JSONに接する機会は多いと思います。 そんな時に便利なjbuilderの使い方について、実際のプログラムと共に紹介します。

  • jbuilderって何!?
  • jbuilder使うと、どんなことが出来るの?
  • jbuilderの色々な使い方が知りたい!

こんな悩みを解決していければと思います。

gem jbuilderとは

json形式のデータを簡単に作る為のgemです。

json形式のデータを作るのに便利なgemは僕が知る限り2つあります。 - jbuilder - jb

jbuilderの方がjsonの階層構造と同じようなイメージで作れるので非常に分かりやすい。 が、重たい。

jbの方は逆で階層構造を自分の頭の中で組み立てて行くイメージになるので分かりづらい。 が、早い。

以前レスポンスをアップする為にjbにチャレンジしたんだけど、ちょっと深い階層作ったりするのがどうしても出来なくて挫折した経験ありです...

gem jbuilderの基本的な使い方

では、早速色々な使い方をまとめていきたいと思います。

jbuilderのインストール

gem 'jbuilder'

基本の基本

json.hogeで一番上の階層が作れる

json.total_pages @events.total_pages

↓

{
  "total_pages": 10
}

階層を下げるには?

doを使ってブロックを作り、その中に値を書いてあげる

json.events do
  json.id 123
end

↓

"events": {
  "id": 123
}

複数件=配列の作り方

doを使ってブロックを作りつつ、array!でぐるぐる回すイメージ

json.events do
  json.array!(@events) do |event|
    json.id event.id
  end
end

↓

"events": [
  {
    "id": 1185,
  },
  {
    "id": 1183,
  }
]

さらに深掘りするには?

これ以降は、上を組み合わせればok。 array!の中で、doを使えば更に深くなって行くよ

json.contents do
  json.array!(@items) do |t|
    json.id t.id
    // この部分だね
    json.title do
      json.label t.title
    end
  end
end
↓

"contents": [
  {
    "id": 1,
    "title": {
      "label": "テスト"
  }
]

gem jbuilderの便利なメソッド

extract!で簡単出力

showの時に使うことが多いかな。対象オブジェクトが1件の場合はextract!を使って出力したいカラムをシンボルで渡してあげれば勝手にjson形式データを作っちゃうよ。 第一引数がオブジェクト、第二引数以降に出力したいカラム名をシンボルで。

json.extract! @item, :id, :title, :description, :created_at, :updated_at

↓

{
    "id": 1,
    "title": "テスト",
    "description": "てすと",
    "created_at": "2019-12-25T09:22:25.186Z",
    "updated_at": "2019-12-25T09:22:25.186Z"
}

if文も使えちゃう!?

jbuilderの凄いところで、普通にif文とかインスタンスメソッドとか呼べちゃう!

if @event.image.present?
  json.image_url @event.resized_image_url
  json.width @event.image.width
  json.height @event.image.height
end
json.finished @event.finished?

class Event < ApplicationRecord
  def finished?
    return true if event_dates.blank?
    event_dates.last.date < Date.current
  end
end
// array!はeachと同じように扱えるって思っておけばokだね!
json.array!(events) do |event|
  if group_event == event
    next
  end
  json.id event.id
end

ルーティングのprefixが使える

ページのURLを渡したい時はルーティングのprefixが使えちゃう。queryも付与できるからクソ便利だし、hoge_urlにしておけばrequest_urlからドメインも勝手に判断してくれる!

json.url event_url(public_id: @event.eventer.public_id, event_id: @event.id, utm_source: :test)

↓

// 勝手にlocalhost:3000を生成してくれる
"url": "http://localhost:3000/events/1185?utm_source=test",

partialが使える

viewと同じようにpartial!が使えるので、共通化できる。 が、記憶が曖昧だけどpartialすると毎回renderされるので、件数が増えれば増えるだけ遅くなる。だから、partialせずに同ファイルに書いてしまった方がレスポンスは早くなる。

json.partial! 'cms_api/events/show', event: event

gem jbuilderのまとめ

様々なjsonデータの作り方について、まとめてみました。

自分が携わっているプロジェクトでは、あまり利用してないですが今どこ行ってもRails側はAPIだけで使う = jsonで返すだけ。って事が多いと思うのでjbuilderは使う場面がいっぱいあるのではないでしょうか? 僕も引き続き積極的に使っていきたいと思います!

【Ruby】色々な繰り返し処理を学ぼう(each, for, while, times, next, break)

こんにちは。opiyoです。

今回は色々なパターンで使うRubyの繰り返し処理についてです。 それぞれ微妙な違いはあれど改めて、どういう場面で何を使うのか。実際のプログラムと共に紹介します。

  • for文の基本的な使い方が知りたい
  • こんな時やあんな時、for文をどう使うの!?
  • for文の途中で抜けたい!

こんな悩みを解決していければと思います。

rubyの eachの使い方

ごめんなさい。先にeachを書かせてもらいます!

理由は簡単で、仕事の中でforを使った記憶が無いからです...

なので、基本的にこれだけ覚えておけば問題ないと思います。それぐらいeachでほとんど事足ります。

オブジェクト.each do |変数|
  繰り返し処理
end

配列の場合

strs = ['a', 'b', 'c']
strs.each do |str|
  puts str
end

↓
a
b
c

ハッシュの場合

strs = {dog: '', cat: ''}
strs.each do |key, value|
  puts "#{value}は英語で#{key}"
end

↓
犬は英語でdog
猫は英語でcat

オブジェクトの場合

データベースから取得したオブジェクトを繰り返す場合の処理です。

users = NewUser.order(id: :desc).limit 3
 =>
<id: 16, name: "opiyo">,
<id: 15, name: "cat">,
<id: 14, name: "dog">

users.each do |user|
  puts user.name
end

↓
opiyo
cat
dog

rangeの場合

rangeは[最小値..最大値]のようなデータの事です。

この例だと今日から月末までの日付を出力する処理です。

# `end_of_month`はrailsで使えるメソッドです。
(Date.today..Date.today.end_of_month).each do |day|
  puts day
end2019/12/16
2019/12/17
2019/12/18
2019/12/19
2019/12/20
2019/12/21
2019/12/22
2019/12/23
2019/12/24
2019/12/25
2019/12/26
2019/12/27
2019/12/28
2019/12/29
2019/12/30
2019/12/31

rubyの forの使い方

for 変数 in オブジェクト do
  繰り返しの処理
end

変数に格納されたデータを1つ1つオブジェクトに渡しながらdo endの中に書かれた処理が実行されます。

では、それぞれの場合を想定して実際に動かしてみます。

文字列の配列の場合

strs = ['a', 'b', 'c']
for strs in str do
  puts str
end

↓
a
b
c

数値の配列の場合

strs = [1, 2, 3]
for str in strs do
  puts str
end1
2
3

配列の配列の場合

strs = [['a',1], ['b',2], ['c',3]]
for str in strs do
  puts str
end

↓
a
1
b
2
c
3

ハッシュの配列の場合

strs = {dog: '', cat: ''}
for str in strs do
  puts str
end

↓
dog
犬
cat
猫

ハッシュの場合はkeyvalueに分けて取得することができます!

strs = {dog: '', cat: ''}

# keyだけ取得する
for key, value in strs do
  puts key
end

↓
dog
cat

# valueだけ取得する
for key, value in strs do
  puts value
end

↓
犬
猫

rubyの whileの使い方

while 条件 do
  繰り返し処理
end

whileは、ある条件がokになるまで実行されます。

i = 0
while i <= 5
  puts i
  i += 1
end0
1
2
3
4

この場合ですとiが5より大きくなるまで処理が実行されます。

注意なのは条件がokにならないと処理が永遠に続きます。

この場合は、処理の中でiをマイナスしているので永遠に5より大きくならず無限ループになります。

i = 0
while i <= 5
  puts i
  i -= 1
end0
-1
-2
-3
-4
-5
-6
-7
-8
-9
.
.
.

rubyの timesの使い方

繰り返す回数.times do
  繰り返す処理
end

timesは繰り返したい数だけ処理します。

5.times do
  puts "test"
end

↓
test
test
test
test
test

今何回めの処理かを判定処理の中で使いたい場合は、doの後に|変数|を書いてあげます。

5.times do |i|
  puts "#{i} 回目のtest"
end0 回目のtest
1 回目のtest
2 回目のtest
3 回目のtest
4 回目のtest

注意点は0から始まる点です。

rubyの uptoの使い方

最小値.upto(最大値) do |i|
  puts 'test'
end  

timesと似ていますが、uptoの場合は最小値 -> 最大限まで繰り返し処理をします。

1.upto(5) do |i|
  puts "#{i} 番目のtest"
end1 番目のtest
2 番目のtest
3 番目のtest
4 番目のtest
5 番目のtest

rubyの downtoの使い方

最大値.downto(最小値) do |i|
  puts 'test'
end  

uptoと似ていますが、downtoの場合は最大値 -> 最小値まで繰り返し処理をします。

5.downto(1) do |i|
  puts "#{i} 番目のtest"
end5 番目のtest
4 番目のtest
3 番目のtest
2 番目のtest
1 番目のtest

rubyの繰り返しを制御する

繰り返し処理の中で条件に応じた処理が可能です。

rubyの繰り返しから次の処理へ(next)

2回目の繰り返しの場合はskipし次の処理を行います。

strs = [1, 2, 3]
strs.each do |str|
  next if str == 2
  puts "#{str} 番目の処理です。"
end1 番目の処理です。
3 番目の処理です。

rubyの繰り返しを終了する(break)




2回目以降の繰り返しを終了します。

strs = [1, 2, 3, 4, 5]
strs.each do |str|
  break if str == 3
  puts "#{str} 番目の処理です。"
end1 番目の処理です。
2 番目の処理です。

rubyの繰り返し処理のまとめ

様々な繰り返し処理について、まとめてみました。

正直、eachnextbreakだけ覚えておけば事足りると思いますが使う時が来れば積極的に使っていきましょう!

Dockerを使って「Rails / PostgreSQL」の開発環境を作ろう!

Dockerを使ってRuby on Railsの開発環境を作成する方法の紹介です。

こちら公式の手順になるのですが、Rubyのバージョンが2.5だったので今回2.6でチャレンジしてみます。

docs.docker.com

基本は公式通りで問題ないですが、2.6でrails newするとRails6系がインストールされます。Rails6系ではWebpackerが自動でインストールされる影響でそれを利用するためのyarnをインストールする必要があります。

Rails 6の変更点と新機能 | RE:ENGINES

【Rails入門】Webpackerではじめるフロントエンド開発!Rails5.1対応 | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

今回作成する環境

  • Ruby: 2.6.5
  • Rails: 6.0.0
  • PostgreSQL: 12.0

環境構築

Gemfile作成

source 'https://rubygems.org'
gem 'rails'

合わせて空のGemfile.lockも作成しておきます。

Dockerfile作成

Railsアプリを作成するために必要な各種設定を記述するファイルです。

上から順番に処理されていくようなイメージですね。

FROM ruby:2.6

# `apt-get install yarn`とするとえらーになるので、以下ように。
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
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


RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
  • RUN: コマンドの実行
  • WORKDIR: 作業ディレクトリ
  • COPY: ファイルのコピー
  • CMD: コンテナコマンドの実行

RUNとCMSの違いはこちら

entrypoint.sh

server.pidが存在するときに削除するシェルファイルです。これも作成しないとエラーになりました。

#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

docker-compose.yml作成

こちらはアプリケーションを構成するサービス(web / DB)、各サービスの取得方法やポート番号など全体の構成を定義するファイルです。

version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

rails作成

docker-compose run web rails new . --force --no-deps --database=postgresql
docker-compose build

postgresql設定

元々色々書かれていますが、一度全て消して以下だけにします。

# database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: 5

development:
  <<: *default
  database: myapp_development


test:
  <<: *default
  database: myapp_test

DBを作成します。

docker-compose run web rake db:create

アプリを起動する

ここまでで設定は完了です。

docker-compose up

localhost:3000でRailsのウェルカムページが表示されていればokです!

その他のこと

vueをインストールする

docker-compose run web rails webpacker:install
docker-compose run web rails webpacker:install:vue
docker-compose build
docker-compose up

typescriptをインストールする

$ docker-compose run web rails webpacker:install:typescript

webpacker:installするとエラーになる

apt-get install yarnとすると、エラーになりました。

$ docker-compose run web rails webpacker:install
Starting docker-sample_db_1 ... done
rails aborted!
ArgumentError: Malformed version number string 0.32+git
/usr/local/bundle/gems/webpacker-4.0.7/lib/tasks/webpacker/check_yarn.rake:12:in `block (2 levels) in <main>'
/usr/local/bundle/gems/railties-6.0.0/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/usr/local/bundle/gems/railties-6.0.0/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/usr/local/bundle/gems/railties-6.0.0/lib/rails/command.rb:48:in `invoke'
/usr/local/bundle/gems/railties-6.0.0/lib/rails/commands.rb:18:in `<main>'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/usr/local/bundle/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `block in require'
/usr/local/bundle/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:291:in `load_dependency'
/usr/local/bundle/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `require'
/myapp/bin/rails:9:in `<top (required)>'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `load'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `call'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
/usr/local/bundle/gems/spring-2.1.0/bin/spring:49:in `<top (required)>'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `load'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `<top (required)>'
/myapp/bin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
Tasks: TOP => webpacker:install => webpacker:check_yarn
(See full trace by running task with --trace)

なんか分からないけど、yarnのバージョンが何かがおかしい... なので、yarnのinstall方法を変更します。

# Dockerfile
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

これど再度docker-compose buildしてやると上手くいきました。

【Rails】Vue.js入門してみた。(rails new ~ hello?)

Ruby on Rails + Vue.jsで最初の画面「Hello Vue!」が表示されるまでの方法を紹介します。

実行環境

  • Ruby: 2.6.3
  • Rails: 6.0.0
  • yarn: 1.19.1
  • vue: 3.12.0

rails newまで

先ずはrails単体で「Hello World」が表示されるまでです。

$ mkdir vue-app
$ cd vue-app

$ ruby -v
$ rbenv versions
$ rbevn local 2.6.3


# Gemfile作成
$ bundle init

Gemfileを開くとgem "rails"がコメントアウトされているので、コメントアウトを解除する

# gem "rails" <- ここのコメントアウトを外す

bundle installする

$ bundle install --path vendor/bundle

rails newします。

ここで--webpack=vueを設定するとvueの設定がinstall済で構築できます。

が、今回は後々設定する形でやっていこうと思います。

# railsの雛形を作ります
$ bundle exec rails new . -B -d postgresql

# Gemfileが更新されるので再度install
$ bundle install --path vendor/bundle

# DBの作成します
$ bundle exec rake db:create
$ bundle exec rake db:migrate

# サーバー起動します
$ bundle exec rails s

f:id:opiyotan:20191025195746p:plain

どん!

お馴染みの画面が表示されたらokです。

vueを使ってHello World表示させる

先ずは、vueを使えるように設定します。

設定方法は、githubのwebpackerに書かれてます。 https://github.com/rails/webpacker#vue

$ bundle exec rails webpacker:install
$ bundle exec rails webpacker:install:vue

次に表示させるためのコントローラーと画面を作ります。

$ bundle exec rails g controller home index

# routes.rb
Rails.application.routes.draw do
  root 'home#index'
end

# application.html.erb
.
.
  <body>
    <%= yield %>
    <%= javascript_pack_tag 'hello_vue' %> <- これを追加します。
  </body>

これで準備は完了です!

rails sして画面を開くと...

Rails + VueでHello Worldを表示
rails + vueでHello Worldを表示する

「Hello Vue!」が表示されていればokです!

vueファイルをいじってみる

<%= javascript_pack_tag 'hello_vue' %>で読み込んだhello_vueapp/javascript/packs/hello_vue.jsを参照しています。

このファイルの真ん中に使い方が色々と書いてあります。

  • 既存のhtml / erbテンプレートの要素をターゲットにできるようにするには、上記のコードをコメントアウト
  • このマークアップをHTMLテンプレートに追加します。

なので、先ほど作ったhome/index.html.erbhello.vue.jsを変更していきます。

# index.html.erb

<div id='hello'>
  {{message}}
  <app></app>
</div>
# hello.vue.js

import Vue from 'vue/dist/vue.esm'
import App from '../app.vue'

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    el: '#hello',
    data: {
      message: "Can you say hello?"
    },
    components: { App }
  })
})

ここまで出来たらもっかいrails sで画面を確認してみます。

画面左上に「Can you say hello?」が表示されていればokです。

これは、vueで定義した変数messageの文字列をhtmlで定義した{{ message }}で表示させています。

Vue.jsは絶賛勉強中なので、随時使い方は別の記事でまとめていきたいと思います。

Tips

bundle installでエラーになった場合

$ bundle install --path vendor/bundle
.
.
.
Resolving dependencies...
Bundler could not find compatible versions for gem "sprockets":
  In snapshot (Gemfile.lock):
    sprockets (= 4.0.0)

  In Gemfile:
    sass-rails (~> 5) was resolved to 5.1.0, which depends on
      sprockets (>= 2.8, < 4.0)

    rails (~> 6.0.0) was resolved to 6.0.0, which depends on
      sprockets-rails (>= 2.0.0) was resolved to 3.2.1, which depends on
        sprockets (>= 3.0.0)

Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.

エラー文を見ると、 sass-railsのバージョンが引っかかってるのが分かるのでGemfileのバージョン指定されているのを外してあげます。

gem 'sass-rails', '~> 5'

↓

gem 'sass-rails'

rails webpacker:installでエラーになった場合

DEPRECATION WARNING: Single arity template handlers are deprecated. Template handlers must
now accept two parameters, the view object and the source for the view object.
Change:
  >> Coffee::Rails::TemplateHandler.call(template)
To:
  >> Coffee::Rails::TemplateHandler.call(template, source)
 (called from <main> at /Users/tnakano/rails/vue-app/Rakefile:6)
DEPRECATION WARNING: Single arity template handlers are deprecated. Template handlers must
now accept two parameters, the view object and the source for the view object.
Change:
  >> Coffee::Rails::TemplateHandler.call(template)
To:
  >> Coffee::Rails::TemplateHandler.call(template, source)
 (called from <main> at /Users/tnakano/rails/vue-app/Rakefile:6)
RAILS_ENV=development environment is not defined in config/webpacker.yml, falling back to production environment
rails aborted!
Webpacker configuration file not found /Users/tnakano/rails/vue-app/config/webpacker.yml. Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - /Users/tnakano/rails/vue-app/config/webpacker.yml

Please run rails webpacker:install Error: No such file or directoryこの辺りが怪しいですかね。これをそのまま検索してみるとwebpackerがインストールされてないと出るエラーみたいなので、Gemfileに追加します。

# Gemfile
gem 'webpacker', '~> 4.x'

追加したらbundle installします。

また、yarnがインストールされていない場合もエラーになるみたいなのでその場合はbrewを使ってインストールします。

$ brew install yarn

【Rails】enumをさらに便利にしてくれるgem enumerize(日本語化も)

Ruby on RailsでEnum = 列挙型を使う時に色々と便利なGemenumerizeの紹介です。

enumは例えば、ユーザーの性別とか、ステータスとか複数の項目を持つデータを扱う際に使います。

Railsだけでもenumを扱うことは出来るのですが、enumerizeを使うと簡単に日本語表示できたりformを簡単に作れたり便利です。

使い方

Gemfileenumerizeを追加してbundle installします。

# Gemfile
gem 'enumerize'

enumerizeを使うカラムをモデルで定義します。 migrationする際に、defaultを定義しておくとrake db:migrateした際に既存のデータへも値が入るので設定します。

# Migration
class AddRoleToNewUser < ActiveRecord::Migration[5.2]
  def change
    add_column :new_users, :role, :string, default: 'member', null: false
  end
end

↓
> NewUser.select(:id, :name, :role).limit(5)
=> [#<NewUser:0x00007fca26d4adf8 id: 1, name: "opiyo", role: "member">,
 #<NewUser:0x00007fca26d4a650 id: 2, name: nil, role: "member">,
 #<NewUser:0x00007fca26d49ed0 id: 3, name: nil, role: "member">,
 #<NewUser:0x00007fca26d49728 id: 4, name: "opiyo", role: "member">,
 #<NewUser:0x00007fca26d48ff8 id: 5, name: nil, role: "member">]

モデルで実際に定義していきます。

# new_user.rb
class NewUser < ApplicationRecord
  extend Enumerize

  has_many :posts, dependent: :destroy

  enumerize :role, in: %i(admin member), default: :member, scope: true
end

extend Enumerizeは書くの忘れるので注意です!

ここでもdefaultを指定しておくことで、オブジェクトを作った際にデフォルトで値が設定されます。

また、定義していない値でsaveするとvalidationを書かなくてもエラーになります。

> user = NewUser.new
=> #<id: nil, name: nil, role: "member">
> user.role = 'opiyo'
=> "opiyo"
> user.save
   (0.2ms)  BEGIN
   (4.2ms)  ROLLBACK
=> false
> user.errors.full_messages
=> ["Roleは一覧にありません。"]

設定はこれだけです。日本語表示の対応をする場合はja.ymlへの設定を行います。

# ja.yml
ja:
  enumerize:
    new_user:
      role:
        admin: '管理者'
        member: 'メンバー'

よく使うメソッド

色々な取得方法がありますので、よく使うメソッドをrails cで確認していきます。

> NewUser.role.options
=> [["管理者", "admin"], ["メンバー", "member"]]
> NewUser.role.values
=> ["admin", "member"]

> user = NewUser.new
=> <id: nil, name: nil, role: "member">
> user.role
=> "member"
> user.role.member?
=> true
> user.role.admin?
=> false
> user.role.opiyo?
NoMethodError: undefined method `opiyo?' for "member":Enumerize::Value
> user.role_value
=> "member"
> user.role_text
=> "メンバー"

# scope: trueが必要
> NewUser.with_role(:member)
  SELECT "new_users".* FROM "new_users" WHERE "new_users"."role" = $1  [["role", "member"]]
> NewUser.without_role(:member)
  SELECT "new_users".* FROM "new_users" WHERE "new_users"."role" NOT IN ('member')

formで使う(simple_form)

enumerizeを定義していると、簡単にセレクトボックスやラジオボタンが作れます。

  .hoges__new-form
    = simple_form_for(@user, url: new_users_path) do |f|
      = f.input :name, placeholder: 'お名前', hint: 'フルネームでご記入してください。'
      = f.input :age
      = f.input :gender, collection: Hoge::GENDER, label: false, include_blank: '-----選択して下さい-----'

      # セレクトボックス
      = f.input :role

      # ラジオボタン
      = f.input :role, as: :radio_buttons

      # チェックボックス
      = f.input :role, as: :check_boxes
      = f.input :birthday
      .hoges__new-form-btn
        = f.submit

enumerizeを使った色々なformの作り方
enumerizeを使った色々なformの作り方

simple_formの使い方はこちらにまとめていますので、どうぞ!

opiyotan.hatenablog.com

【Rails】開発中に送ったメールを確認する(Gem: letter_opener)

Ruby on Railsで開発中に送ったメールを確認する Gem letter_opener_webの紹介です。

環境設定

Gemfileletter_opener_webを追加する。

# Gemfile
group :development do
  gem 'letter_opener_web'
end

開発環境モードの設定ファイルにメール送信の設定をします。

# development.rb
Rails.application.configure do
  config.action_mailer.default_url_options = { host: localhost, port: 3000 }
  # `:letter_opener`を指定する
  config.action_mailer.delivery_method = :letter_opener
  # ログにエラーを表示するために`true`を設定
  config.action_mailer.raise_delivery_errors = true

# ここの指定は無くてokみたい
#  config.action_mailer.smtp_settings = {
#    address: SMTPのメールサーバ,
#    port: 587,
#    domain: 送付するドメイン,
#    authentication: "plain",
#    enable_starttls_auto: true,
#    user_name: メールサーバ認証用ユーザー,
#    password: メールサーバ認証用パスワード
#  }
end

ブラウザでメールの送信結果を確認できるように設定します。

Rails.application.routes.draw do
  if Rails.env.development?
    mount LetterOpenerWeb::Engine, at: '/letter_opener'
  end
end

これで、http://localhost:3000/letter_opener/で確認することが可能です!

Mailerファイルを作成する

$ bundle exec rails g mailer new_user

Mailerファイルを設定する

# new_user_mailer.rb
class NewUserMailer < ApplicationMailer
  default from: "hoge@example.com"

  def to_user(user)
    @user = user
    mail to: user.email, cc: 'cc@example.com', bcc: 'bcc@example.com', subject: 'ご登録ありがとうございます!'
  end
end
# to_user.html.haml
%p
  = "#{@user.name}"
  %br
  ご登録ありがとうございます。
%p
  こちらからログインして下さい。
  %br
  - url = new_user_url(@user.id)
  = link_to url, url, target: '_blank'

何もしていないとhtmlメールになりますが、format.textを指定することでtextメールで送ることも可能です。

# new_user_mailer.rb
class NewUserMailer < ApplicationMailer
  default from: "hoge@example.com"

  def to_user(user)
    @user = user
    mail to: user.email, cc: 'cc@example.com', bcc: 'bcc@example.com', subject: 'ご登録ありがとうございます!' do |format|
      format.text { render 'new_user_mailer/to_user', layout: false } # ここの部分
    end
  end
end

textメールの場合は改行や空白がそのまま本文となりますので、要注意です!

# to_user.text.erb
<%= @user.name %>様

ご登録ありがとうございます。


こちらからログインして下さい。
<%= new_user_url(@user.id) %>

<%= render 'signature' %>
# _signature.text.erb
■-------------------------------------------------------------
  opiyo株式会社
 お客様相談お問い合わせ窓口
 mail:opiyo@example.com
-------------------------------------------------------------■

実行方法と結果の確認

# rails consoleに入る
$ bundle exec rails c

# メール送信する
> NewUserMailer.to_user(NewUser.last).deliver_now
=> #<Mail::Message:70228559444700, Multipart: false, Headers: <Date: Wed, 16 Oct 2019 21:37:46 +0900>, <From: hoge@example.com>, <To: >, <Cc: cc@example.com>, <Bcc: bcc@example.com>, <Message-ID: <5da70f1ab3d89_12e833fdf64440e5c983a0@TakunoMacBook-puro.local.mail>>, <Subject: ご登録ありがとうございます!>, <Mime-Version: 1.0>, <Content-Type: text/plain>, <Content-Transfer-Encoding: base64>>


# メール送信を非同期で
> NewUserMailer.to_user(NewUser.last).deliver_later
=> #<ActionMailer::DeliveryJob:0x00007fbeb9a88db8
 @arguments=
  ["NewUserMailer",
   "to_user",
   "deliver_now",
   #<NewUser:0x00007fbeb9a8b2e8 id: 13, name: "opiyo", age: nil, gender: "", birthday: nil, email: nil, created_at: Tue, 15 Oct 2019 20:13:15 JST +09:00, updated_at: Tue, 15 Oct 2019 20:13:15 JST +09:00, token: nil>],
 @executions=0,
 @job_id="24783fb5-9c1e-4005-a247-457a9e16d78d",
 @priority=nil,
 @provider_job_id=6292,
 @queue_name="mailers">


letter_openerをwebで確認する
letter_openerをwebで確認する


letter_openerをwebで確認する(textメール)
letter_openerをwebで確認する(textメール)

【Rails】不用な値の更新を防ぐ「Strong Parameters」について

Ruby on RailsのStrong Parametersについての紹介です。

ユーザー登録やお問い合わせなど、ユーザーがフォームに入力したデータのみ登録・更新できるようにするための機能です。

webの場合は開発者ツールなどを使って存在しないformを自由に作ったり出来てしまうので、それを防ぐことは大事です。

基本的な使い方

params.require(:key).permit(filter)
  • key: paramsのキー
  • filter: 登録・更新を許可するカラム名
<input name="new_user[name]">
<input name="new_user[age]">

keyはinputラベルのnameに指定する名前です。この例の場合だとnew_userがそれにあたります。

filterの部分は登録・更新したいカラム名です。この例の場合だとnameageがそれにあたります。これらをシンボルを使って記述していきます。

実際の使い方例

実際の使い方ですが、updatecreate処理の時に使います。

new_user_paramsメソッドがStrong Parameterの処理部分です。

# new_users_controller.rb
def create
  @user = NewUser.new(new_user_params)
  if @user.save
    # 成功した時の処理
  else
    # 失敗した時の処理
  end
end

def new_user_params
  params.require(:new_user).permit(:name, :age, :gender, :birthday)
end

このように記述しておく事で万が一別のカラムが飛んで来ても無視されます。

またNewUser.create(params[:new_user])のようにStrong Parameters記述せずにparamsをそのまま登録しようとするとエラー(ActiveModel::ForbiddenAttributesError)になります。

そのほかの使い方例

関連するデータもまとめて登録する

1 : Nの関係など関連するデータも合わせて登録・更新した時にnested_attributesという仕組みがあります。こういった場合は以下のように記述する事で関連するデータもまとめて対応する事ができます。

params.require(:new_user).permit(:name, :age, :gender, :birthday, posts_attributes: [:id, :title, :_destroy])

キーが存在しない場合にエラーにならないように

テーブルなどのデータの一覧があって、対象データにチェックを付けてデータの登録・更新するような処理の場合に1つもチェックがない状態で保存するとkeyが無い状態になるのですが、そのような場合に使います。

params.fetch(:new_user, {}).permit(:name, :age, :gender, :birthday)