20190506のRailsに関する記事は17件です。

Rails6.0におけるbulk insert

Rails6.0では、bulk insert用にinsert_all insert_all! upsert_allという3つのメソッドがActiveRecordに追加されました。

Rails5.2までのbulk insertはactiverecord-importというGemを使うのが主流でしたが、それに置き換わるものになりそうです。

1. insert_allメソッド

insert_allメソッドは、その名の通り複数件のレコードを一括でINSERTします。

rails/activerecord/lib/active_record/persistence.rb
def insert_all(attributes, returning: nil, unique_by: nil)
  InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by).execute
end

on_duplicate: :skipとなっているため、id(主キー)が重複しているレコードはスキップされます。
例えば

User.insert_all([
  { id: 1, name: 'Taro', age: 20 },
  { id: 1, name: 'Jiro', age: 21 }
])

とやると、2件目の{ id: 1, name: 'Jiro', age: 21 }は無視されて、INSERTされません。

1-1. returningオプション

returningは、PostgreSQLにのみ有効なオプションです。
返り値として得られる、INSERTに成功したレコードの属性を指定することができます。
デフォルトでは、id(主キー)が返って来ます。
例えば

rails/activerecord/lib/active_record/persistence.rb
returning: %w[ id name ]

と指定すると、idnameが返ってきます。

1-2. unique_byオプション

unique_byは、PostgreSQLとSQLiteにのみ有効なオプションです。
デフォルトでは、id(主キー)のみが重複しているとスキップされます。unique_byオプションをつけると、重複でスキップするカラムを配列によって追加で指定できます。

rails/activerecord/lib/active_record/persistence.rb
unique_by: { columns: %w[ isbn ] }

以下のようにcolumns:where:を組み合わせることで、部分インデックスを指定することもできます。

rails/activerecord/lib/active_record/persistence.rb
unique_by: { columns: %w[ isbn ], where: "published_on IS NOT NULL" }

2. insert_all!メソッド

insert_all!メソッドは、insert_allメソッドと同様に複数件のレコードを一括でINSERTします。
insert_allメソッドとの違いは、id(主キー)が重複しているとスキップするのではなくエラーを返します。

rails/activerecord/lib/active_record/persistence.rb
def insert_all!(attributes, returning: nil)
  InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning).execute
end

on_duplicate: :raiseとなっているため、id(主キー)が重複しているとraise ActiveRecord::RecordNotUniqueのエラーを返します。
例えば

User.insert_all([
  { id: 1, name: 'Taro', age: 20 },
  { id: 1, name: 'Jiro', age: 21 }
])

とやると、2件目の{ id: 1, name: 'Jiro', age: 21 }raise ActiveRecord::RecordNotUniqueのエラーが発生します。

2-1. returningオプション

1-1.returningオプションと同じです。

3. upsert_allメソッド

upsert_allメソッドは、まだ存在しないレコードならINSERT、既に存在するレコードならUPDATEします。

rails/activerecord/lib/active_record/persistence.rb
def upsert_all(attributes, returning: nil, unique_by: nil)
  InsertAll.new(self, attributes, on_duplicate: :update, returning: returning, unique_by: unique_by).execute
end

on_duplicate: :updateとなっているため、id(主キー)が重複しているとupdateします。

3-1. returningオプション

1-1. returningオプションと同じです。

3-2. unique_byオプション

1-2. unique_byオプションと同じです。

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

【Imagemagick対応】Dockerを利用して、RailsをHerokuに簡単デプロイ

はじめに

  • 初心者の方でも簡単に再現可能
  • Imagemagickが使用可能
  • この記事は、以下の記事を参考にして作成しています

DockerComposeでコンテナベースのRailsアプリを作成してそのままHerokuにデプロイする
https://qiita.com/akirakudo/items/16a01271b0a39316e439

  • 引用元記事での問題点

    • rmagickが使用できない
    • 引用元記事では、ベースイメージにalphineを採用
    • このベースイメージでは、Imagemagick 7 が使用されており、gem install rmagick でエラーが発生する
    • gem rmagick では、Imagemagick 6 までしか対応していないことが原因
  • 対策

    • ベースイメージを alphine から Debian へ変更

環境構築方法

  • コンテナの概要
    • RailsコンテナとPostgreSQLコンテナから構成
    • 両者は、local環境のカレントディレクトリにマウントされている
      (※カレントディレクトリ = Dockerfileが置いてあるディレクトリのこと)
    • Railsコンテナはポートフォワーディングにより、localhost:3000からアクセス可能
  • コンテナ構成.png

  • コンテナ構築方法

    • 以下記載の手順をそのまま実行してください

1. フォルダ、ファイルの準備

  • 好きな場所にフォルダを新規作成
  • フォルダ内に4つのファイルを作成(中身は空でOK)
  • 例)この記事では、フォルダ名をhoge_folderとしています

    • hoge_folder
      ├ Dockerfile
      ├ docker-compose.yml
      ├ Gemfile
      ├ Gemfile.lock

2. ファイルの編集

  • 1. で作成した4つのフォルダに以下の内容をコピペする

① Dockerfile

/hoge_folder/Dockerfile
FROM ruby:2.6

ENV RUNTIME_PACKAGES="linux-headers libxml2-dev libxslt-dev make gcc libc-dev nodejs tzdata postgresql-dev postgresql" \
    DEV_PACKAGES="build-base curl-dev" \
    HOME="/myapp"

WORKDIR $HOME

# Counter Measure to Error:"Autoprefixer doesn’t support Node v4.8.2. Update it"
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \
        && apt-get install -y nodejs

RUN apt-get update && \
    apt-get install -y mysql-client \
    postgresql-client \
    sqlite3 \
    --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

ADD Gemfile      $HOME/Gemfile
ADD Gemfile.lock $HOME/Gemfile.lock

RUN bundle install

ADD ./ $HOME
COPY ./ $HOME

CMD ["rails", "server", "-b", "0.0.0.0"]

② docker-compose.yml

/hoge_folder/docker-compose.yml
version: '3'
services:
  db:
    container_name: postgress_db
    image: postgres:latest
  web:
    container_name: rails_app
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

③ Gemfile

/hoge_folder/Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.2'

gem 'rmagick'

④ Gemfile.lock

/hoge_folder/Gemfile.lock

注)Gemfile.lock の中身は空です

  • これでコンテナを立ち上げる準備ができました。

3. コンテナの立ち上げ準備

  • CLIを起動する(bash,コマンドプロント等、ご自身の環境でお使いのものを起動してください。)
  • Dockerfileのあるディレクトリに移動する

    • この記事で言えば、 cd /home/...(以下略).../hoge_folder
  • 以下のコマンドを入力

コマンド
$ docker-compose run web rails new . --force --database=postgresql

※実際に入力する際は、$マークを消してください

  • コマンドを実行すると、作業フォルダ内(hoge_folder)に複数のフォルダが生成される

  • Databaseの設定を変更する

    • このままではpostgreSQLコンテナにアクセスできないため
    • config/database.ymlを開いて、下記内容を上書きする
/hoge_folder/config/database.yml
  default: &default
  adapter: postgresql
  encoding: unicode
  host: db           # 上書き
  username: postgres # 上書き
  password:          # 上書き
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  • docker-compose.ymlがあるフォルダ上でビルドする
コマンド
$ docker-compose build
  • データベースを作成し、マイグレーションする
コマンド
$ docker-compose run web rails db:create
$ docker-compose run web rails db:migrate
  • ここまでで、コンテナの準備は終了です。

4. railsの起動確認

  • docker-compose up でrailsは自動で立ち上がる
コマンド
$ docker-compose up
  • Webブラウザでhttp://localhost:3000にアクセスしてみる

    • Yey ! 君はrailsの上にいるze !
    • rails_top.png
  • railsの起動停止

コマンド
(再起動する場合)
$ docker-compose restart
(停止する場合)
$ docker-compose stop

作 戦 完 了 !

  • それでは、Rails on Dockerライフ をお楽しみください!

謝辞

  • @akirakudo さんの記事は、大変参考になりました

https://qiita.com/akirakudo/items/16a01271b0a39316e439

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

【Imagemagick対応】Dockerを利用して、Rails環境を作成

はじめに

  • 初心者の方でも簡単に再現可能
  • Imagemagickが使用可能
  • この記事は、以下の記事を参考にして作成しています
  • Heroku にもビルド可能なイメージです

    DockerComposeでコンテナベースのRailsアプリを作成してそのままHerokuにデプロイする
    https://qiita.com/akirakudo/items/16a01271b0a39316e439

  • 引用元記事での問題点

    • rmagickが使用できない
    • 引用元記事では、ベースイメージにalphineを採用
    • このベースイメージでは、Imagemagick 7 が使用されており、gem install rmagick でエラーが発生する
    • gem rmagick では、Imagemagick 6 までしか対応していないことが原因
  • 対策

    • ベースイメージを alphine から Debian へ変更

環境構築方法

  • コンテナの概要
    • RailsコンテナとPostgreSQLコンテナから構成
    • 両者は、local環境のカレントディレクトリにマウントされている
      (※カレントディレクトリ = Dockerfileが置いてあるディレクトリのこと)
    • Railsコンテナはポートフォワーディングにより、localhost:3000からアクセス可能
  • コンテナ構成.png

  • コンテナ構築方法

    • 以下記載の手順をそのまま実行してください

1. フォルダ、ファイルの準備

  • 好きな場所にフォルダを新規作成
  • フォルダ内に4つのファイルを作成(中身は空でOK)
  • 例)この記事では、フォルダ名をhoge_folderとしています

    • hoge_folder
      ├ Dockerfile
      ├ docker-compose.yml
      ├ Gemfile
      ├ Gemfile.lock

2. ファイルの編集

  • 1. で作成した4つのフォルダに以下の内容をコピペする

① Dockerfile

/hoge_folder/Dockerfile
FROM ruby:2.6

ENV RUNTIME_PACKAGES="linux-headers libxml2-dev libxslt-dev make gcc libc-dev nodejs tzdata postgresql-dev postgresql" \
    DEV_PACKAGES="build-base curl-dev" \
    HOME="/myapp"

WORKDIR $HOME

# Counter Measure to Error:"Autoprefixer doesn’t support Node v4.8.2. Update it"
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \
        && apt-get install -y nodejs

RUN apt-get update && \
    apt-get install -y mysql-client \
    postgresql-client \
    sqlite3 \
    --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

ADD Gemfile      $HOME/Gemfile
ADD Gemfile.lock $HOME/Gemfile.lock

RUN bundle install

ADD ./ $HOME
COPY ./ $HOME

CMD ["rails", "server", "-b", "0.0.0.0"]

② docker-compose.yml

/hoge_folder/docker-compose.yml
version: '3'
services:
  db:
    container_name: postgress_db
    image: postgres:latest
  web:
    container_name: rails_app
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

③ Gemfile

/hoge_folder/Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.2'

gem 'rmagick'

④ Gemfile.lock

/hoge_folder/Gemfile.lock

注)Gemfile.lock の中身は空です

  • これでコンテナを立ち上げる準備ができました。

3. コンテナの立ち上げ準備

  • CLIを起動する(bash,コマンドプロント等、ご自身の環境でお使いのものを起動してください。)
  • Dockerfileのあるディレクトリに移動する

    • この記事で言えば、 cd /home/...(以下略).../hoge_folder
  • 以下のコマンドを入力

コマンド
$ docker-compose run web rails new . --force --database=postgresql

※実際に入力する際は、$マークを消してください

  • コマンドを実行すると、作業フォルダ内(hoge_folder)に複数のフォルダが生成される

  • Databaseの設定を変更する

    • このままではpostgreSQLコンテナにアクセスできないため
    • config/database.ymlを開いて、下記内容を上書きする
/hoge_folder/config/database.yml
  default: &default
  adapter: postgresql
  encoding: unicode
  host: db           # 上書き
  username: postgres # 上書き
  password:          # 上書き
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  • docker-compose.ymlがあるフォルダ上でビルドする
コマンド
$ docker-compose build
  • データベースを作成し、マイグレーションする
コマンド
$ docker-compose run web rails db:create
$ docker-compose run web rails db:migrate
  • ここまでで、コンテナの準備は終了です。

4. railsの起動確認

  • docker-compose up でrailsは自動で立ち上がる
コマンド
$ docker-compose up
  • Webブラウザでhttp://localhost:3000にアクセスしてみる

    • Yey ! 君はrailsの上にいるze !
    • rails_top.png
  • railsの起動停止

コマンド
(再起動する場合)
$ docker-compose restart
(停止する場合)
$ docker-compose stop

作 戦 完 了 !

  • それでは、Rails on Dockerライフ をお楽しみください!

謝辞

  • @akirakudo さんの記事は、大変参考になりました

https://qiita.com/akirakudo/items/16a01271b0a39316e439

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

RailsアプリをHerokuでデプロイする際の注意点

はじめに

初投稿です。本とネット教材で半年学んだ程度のヒヨッコです。
AWS cloud9でRuby on Railsプロジェクトを作成し、GitHub経由でHerokuでデプロイしました。
その際に発生したエラー・注意点を備忘録として投稿させていただきます。

Herokuの注意点

Herokuではsqlite3が使えないようなので、本番環境用のgemを別に設定する必要があります。
以下リンクを参考にPostgreSQLを入れました。
(参考)HerokuではSQLite3が使えない?! https://qiita.com/MosamosaPoodle/items/7149aa66f1c087472777

・Rails Gemfileを修正

Gemfile
gem 'sqlite3' 

↓変更する

Gemfile
gem 'sqlite3', groups: %w(test development), require: false
gem 'pg', groups: %w(production), require: false

・Rails Gemfile.lockを削除しbundle installし直す

Terminal
$ rm Gemfile.lock
$ bundle install

これでRails上の設定は完了です。

AWS cloud9からgit push時の注意点

安全性の観点から、GitHubを二段階認証で管理すべきなのですが、
そうするとcloud9からpushする際にGitHubアカウントパスワードを入力してもエラーになります。

Terminal
Username for 'https://github.com': hoge-user
Password for 'https://hoge-user@github.com':
remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/hoge-user/github-repository-name.git/'

対策として
・GitHubでPersonal Access tokensを発行し、tokenをPassword欄に入力する(https接続)
・SSH接続するよう設定を変更する
の2つがありますが、今回は前者を選びました。
(参考)GitHub「Personal access tokens」の設定方法 https://qiita.com/kz800/items/497ec70bff3e555dacd0

ちなみに個人的にハマった罠が2つありますのでそちらも書いておきます。
1. Terminal上のPassword入力時は、画面は反応しないが文字はちゃんと入力されている。心配せずに入力する!
2. コピーした文字をTerminal上にペーストするときはcommand+v (Ctrl+v)で入力しない。右クリック→Pasteでペーストする。

成果

半年間HTML, CSS, JavaScript, Ruby, Rails, AWS cloud9などと悪戦苦闘しましたが、素人でもHerokuでデプロイできました。
飲み会のお会計をシチュエーション別で傾斜計算する簡単なツールです。
Warikan - https://warikanappbygmnori.herokuapp.com/

デザインや計算方法の拙さは本人が一番実感しています。次はもっと良いものを作れるよう頑張ります。

参考リンク

HerokuではSQLite3が使えない?! https://qiita.com/MosamosaPoodle/items/7149aa66f1c087472777
GitHub「Personal access tokens」の設定方法 https://qiita.com/kz800/items/497ec70bff3e555dacd0

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

【初学者向け】RailsアプリをHerokuでデプロイする際の注意点

はじめに

初投稿です。本とネット教材で半年学んだ程度のヒヨッコです。
AWS cloud9でRuby on Railsプロジェクトを作成し、GitHub経由でHerokuでデプロイしました。
その際に発生したエラー・注意点を備忘録として投稿させていただきます。

Herokuの注意点

Herokuではsqlite3が使えないようなので、本番環境用のgemを別に設定する必要があります。
以下リンクを参考にPostgreSQLを入れました。
(参考)HerokuではSQLite3が使えない?! https://qiita.com/MosamosaPoodle/items/7149aa66f1c087472777

・Rails Gemfileを修正

Gemfile
gem 'sqlite3' 

↓変更する

Gemfile
gem 'sqlite3', groups: %w(test development), require: false
gem 'pg', groups: %w(production), require: false

・Rails Gemfile.lockを削除しbundle installし直す

Terminal
$ rm Gemfile.lock
$ bundle install

これでRails上の設定は完了です。

AWS cloud9からgit push時の注意点

安全性の観点から、GitHubを二段階認証で管理すべきなのですが、
そうするとcloud9からpushする際にGitHubアカウントパスワードを入力してもエラーになります。

Terminal
Username for 'https://github.com': hoge-user
Password for 'https://hoge-user@github.com':
remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/hoge-user/github-repository-name.git/'

対策として
・GitHubでPersonal Access tokensを発行し、tokenをPassword欄に入力する(https接続)
・SSH接続するよう設定を変更する
の2つがありますが、今回は前者を選びました。
(参考)GitHub「Personal access tokens」の設定方法 https://qiita.com/kz800/items/497ec70bff3e555dacd0

ちなみに個人的にハマった罠が2つありますのでそちらも書いておきます。
1. Terminal上のPassword入力時は、画面は反応しないが文字はちゃんと入力されている。心配せずに入力する!
2. コピーした文字をTerminal上にペーストするときはcommand+v (Ctrl+v)で入力しない。右クリック→Pasteでペーストする。

成果

半年間HTML, CSS, JavaScript, Ruby, Rails, AWS cloud9などと悪戦苦闘しましたが、素人でもHerokuでデプロイできました。
飲み会のお会計をシチュエーション別で傾斜計算する簡単なツールです。
Warikan - https://warikanappbygmnori.herokuapp.com/

デザインや計算方法の拙さは本人が一番実感しています。次はもっと良いものを作れるよう頑張ります。

参考リンク

HerokuではSQLite3が使えない?! https://qiita.com/MosamosaPoodle/items/7149aa66f1c087472777
GitHub「Personal access tokens」の設定方法 https://qiita.com/kz800/items/497ec70bff3e555dacd0

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

Ruby on railsを学習してる者たちよ、SQLを学べ!

SQLを学ぼうと思ったきっかけ

Railsで自作アプリを作っているときにN+1問題に直面した。

includesやleft_joinとかを学習してるときに、SQLの動きをちゃんとわかってないとやばくね?って思った。

Railsはあくまでフレームワークだ。本来SQL直書きするところを簡単にしてくれている。

なのでSQLをある程度わかっていたら、もし違うプログラミング言語で組むことになったとき、フレームワークであれば関連づけて覚えやすいし、直書きなら普通に書ける。

実際にSQLを勉強してみたら意外とすんなり頭に入ってくれた。何も怖がることはなかったので勉強しておくとあとあと絶対いいはず!

この記事では、結合とグループ化について書いていく。

テーブルの結合

Usersテーブル
+----+--------+
| id | name   |
+----+--------+
|  1 | user1  |
|  2 | user2  |
+----+------- +

Articlesテーブル
+----+--------+-------+---------+
| id | title  | body  | user_id |
+----+--------+-------+---------+
|  1 | title1 | body1 |       1 |
|  2 | title2 | body2 |       1 |
|  3 | title3 | body3 |       2 |
|  4 | title4 | body4 |         |
+----+--------+-------+---------+

上記の2つのテーブルがあるとします。

INNER JOIN(内部結合)

articleのuser_idとusersのidを紐づけている。

select articles.*, users.name 
from articles inner join users 
on articles.user_id = users.id;

出力結果

+----+--------+-------+---------+-------+
| id | title  | body  | user_id | name  |
+----+--------+-------+---------+-------+
|  1 | title1 | body1 |       1 | user1 |
|  2 | title2 | body2 |       1 | user1 |
|  3 | title3 | body3 |       2 | user2 |
+----+--------+-------+---------+-------+

articles.user_idとusers.idが一致しているデータだけを出力している。

なので、Articlesテーブルのid = 4のデータはuser_idを持っていないので出力されていない。

LEFT JOIN(外部結合)

LEFT OUTER JOINLEFT JOINと同じ意味。

SQL

select articles.*, users.name 
from articles left join users 
on articles.user_id = users.id;

出力結果

+----+--------+-------+---------+-------+
| id | title  | body  | user_id | name  |
+----+--------+-------+---------+-------+
|  1 | title1 | body1 |       1 | user1 |
|  2 | title2 | body2 |       1 | user1 |
|  3 | title3 | body3 |       2 | user2 |
|  4 | title4 | body4 |         |       |
+----+--------+-------+---------+-------+

articlesテーブルとuserテーブルを並べるようなイメージ。

左のテーブル(articlesテーブル)を基準に結合している。

articlesテーブルはwhereの条件にあてはまる全件が出力され、それに紐づくusersのデータが出力されている。

ちなみにwhere句の条件なしの表現は where 1 = 1

RIGHT JOIN(外部結合)

LEFT JOINの逆なので省略

グループ化

Teamsテーブル
+----+-------+-------+
| id |  team | point |
+----+-------+-------+
|  1 | teamA |   3   |
|  2 | teamB |   2   |
|  3 | teamA |   5   |
|  4 | teamB |   4   |
|  5 | teamC |  10   |
|  6 | teamC |  20   |
+----+-------+-------+

上記のテーブルがあるとします。

Group by

SQL

select team, sum(point) from teams group by team;

出力結果

+----+-------+-------+
| id |  team | point |
+----+-------+-------+
|  1 | teamA |   8   |
|  2 | teamB |   6   |
|  3 | teamC |  30   |
+----+-------+-------+

teamカラムを基準にpointを合計している。
今回扱ったのはsum()合計やけど他にも色々あるで。

例:

  • COUNT(team) 
select team, sum(point) from teams group by team;
レコードのカウント数
+----+-------+-------+
| id |  team | COUNT |
+----+-------+-------+
|  1 | teamA |   2   |
|  2 | teamB |   2   |
|  3 | teamC |   2   |
+----+-------+-------+
  • avg(point)では平均pointが出力される。

where句の位置

SQL

select team, sum(point) from teams where team != "teamB" group by team;

出力結果

+----+-------+-------+
| id |  team | point |
+----+-------+-------+
|  1 | teamA |   8   |
|  2 | teamC |  30   |
+----+-------+-------+

Having句

group byした結果をさらに条件をつける。

SQL

select team, sum(point) from teams where team != "teamB" 
group by team having sum(point) > 10;

出力結果

+----+-------+-------+
| id |  team | point |
+----+-------+-------+
|  1 | teamC |  30   |
+----+-------+-------+

何をしているかというと、

team != "teamB"の条件でグループ化

グループ化した結果をさらにsum(point) > 10の条件で検索

Havingはグループ化した後のwhere句みたいなかんじ。

余計わかりにくいかな。

おわり

他にもいろいろあるけど、とりあえず今回は結合とグループ化までで。

これがわかっただけでもRailsで走っているSQLだいたい読めると思う。

記事書いてるときたまたま見つけたサイト。

https://www.1keydata.com/jp/sql/sql-intersect.php

じゃ。

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

Railsを学習してる者たちよ、SQLを学べ!

SQLを学ぼうと思ったきっかけ

Railsで自作アプリを作っているときにN+1問題に直面した。

includesやleft_joinとかを学習してるときに、SQLの動きをちゃんとわかってないとやばくね?って思った。

Railsはあくまでフレームワークだ。本来SQL直書きするところを簡単にしてくれている。

なのでSQLをある程度わかっていたら、もし違うプログラミング言語で組むことになったとき、フレームワークであれば関連づけて覚えやすいし、直書きなら普通に書ける。

実際にSQLを勉強してみたら意外とすんなり頭に入ってくれた。何も怖がることはなかったので勉強しておくとあとあと絶対いいはず!

この記事では、結合とグループ化について書いていく。

テーブルの結合

Usersテーブル
+----+--------+
| id | name   |
+----+--------+
|  1 | user1  |
|  2 | user2  |
+----+------- +

Articlesテーブル
+----+--------+-------+---------+
| id | title  | body  | user_id |
+----+--------+-------+---------+
|  1 | title1 | body1 |       1 |
|  2 | title2 | body2 |       1 |
|  3 | title3 | body3 |       2 |
|  4 | title4 | body4 |         |
+----+--------+-------+---------+

上記の2つのテーブルがあるとします。

INNER JOIN(内部結合)

articleのuser_idとusersのidを紐づけている。

select articles.*, users.name 
from articles inner join users 
on articles.user_id = users.id;

出力結果

+----+--------+-------+---------+-------+
| id | title  | body  | user_id | name  |
+----+--------+-------+---------+-------+
|  1 | title1 | body1 |       1 | user1 |
|  2 | title2 | body2 |       1 | user1 |
|  3 | title3 | body3 |       2 | user2 |
+----+--------+-------+---------+-------+

articles.user_idとusers.idが一致しているデータだけを出力している。

なので、Articlesテーブルのid = 4のデータはuser_idを持っていないので出力されていない。

LEFT JOIN(外部結合)

LEFT OUTER JOINLEFT JOINと同じ意味。

SQL

select articles.*, users.name 
from articles left join users 
on articles.user_id = users.id;

出力結果

+----+--------+-------+---------+-------+
| id | title  | body  | user_id | name  |
+----+--------+-------+---------+-------+
|  1 | title1 | body1 |       1 | user1 |
|  2 | title2 | body2 |       1 | user1 |
|  3 | title3 | body3 |       2 | user2 |
|  4 | title4 | body4 |         |       |
+----+--------+-------+---------+-------+

articlesテーブルとuserテーブルを並べるようなイメージ。

左のテーブル(articlesテーブル)を基準に結合している。

articlesテーブルはwhereの条件にあてはまる全件が出力され、それに紐づくusersのデータが出力されている。

ちなみにwhere句の条件なしの表現は where 1 = 1

RIGHT JOIN(外部結合)

LEFT JOINの逆なので省略

グループ化

Teamsテーブル
+----+-------+-------+
| id |  team | point |
+----+-------+-------+
|  1 | teamA |   3   |
|  2 | teamB |   2   |
|  3 | teamA |   5   |
|  4 | teamB |   4   |
|  5 | teamC |  10   |
|  6 | teamC |  20   |
+----+-------+-------+

上記のテーブルがあるとします。

Group by

SQL

select team, sum(point) from teams group by team;

出力結果

+----+-------+-------+
| id |  team | point |
+----+-------+-------+
|  1 | teamA |   8   |
|  2 | teamB |   6   |
|  3 | teamC |  30   |
+----+-------+-------+

teamカラムを基準にpointを合計している。
今回扱ったのはsum()合計やけど他にも色々あるで。

例:

  • COUNT(team) 
select team, sum(point) from teams group by team;
レコードのカウント数
+----+-------+-------+
| id |  team | COUNT |
+----+-------+-------+
|  1 | teamA |   2   |
|  2 | teamB |   2   |
|  3 | teamC |   2   |
+----+-------+-------+
  • avg(point)では平均pointが出力される。

where句の位置

SQL

select team, sum(point) from teams where team != "teamB" group by team;

出力結果

+----+-------+-------+
| id |  team | point |
+----+-------+-------+
|  1 | teamA |   8   |
|  2 | teamC |  30   |
+----+-------+-------+

Having句

group byした結果をさらに条件をつける。

SQL

select team, sum(point) from teams where team != "teamB" 
group by team having sum(point) > 10;

出力結果

+----+-------+-------+
| id |  team | point |
+----+-------+-------+
|  1 | teamC |  30   |
+----+-------+-------+

何をしているかというと、

team != "teamB"の条件でグループ化

グループ化した結果をさらにsum(point) > 10の条件で検索

Havingはグループ化した後のwhere句みたいなかんじ。

余計わかりにくいかな。

おわり

他にもいろいろあるけど、とりあえず今回は結合とグループ化までで。

これがわかっただけでもRailsで走っているSQLだいたい読めると思う。

記事書いてるときたまたま見つけたサイト。

https://www.1keydata.com/jp/sql/sql-intersect.php

じゃ。

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

Could not find generator xxx. Maybe you meant xxx

背景

Gemfile に新しい gem を追加後 bundle update して rails generate コマンドを打ってみたけど認識されなかった。

$ rails g serializer book
Running via Spring preloader in process 16248
Could not find generator 'serializer'. Maybe you meant 'helper', 'erb:mailer' or 'mailer'
Run `rails generate --help` for more options.

対応

spring stop と打ちました。

$ spring stop

成功。

$ rails g serializer book
Running via Spring preloader in process 16372
      create  app/serializers/book_serializer.rb

ちなみに Spring も元通り動いています。

$ spring status
Spring is running:

Rails Spring

spring

Spring is a Rails application preloader. It speeds up development by keeping your application running in the background so you don't need to boot it every time you run a test, rake task or migration.

普段全く意識していなかったのですが、バックグラウンドで動いてくれているアプリケーションプリローダーのようです。
Rails 4.1 で標準になったようです。

参考

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

link_to deleteメソッドがGetメソッドで送られる。

Rails 5.1 link_to deleteメソッドがGetメソッドになる。

ある日、deviseでログアウトを作って、

 <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>

ログアウトを試してみたら、ルーティングエラーが起きた。
そして何故か、ログを見てみたらGetでアクセスしている。

html見ても

<a rel="nofollow" data-method="delete" href="/users/sign_out">ログアウト</a>

しっかりdeleteになってる。

解決方法

rails-ujs

ruby on rails 5.0まではjqueryに依存していてjquery-ujsを使ってたらしいですが、5.1以降ではもうjqueryに依存していないので、rails-ujsを使うそうです。

$ yarn add rails-ujs

application.jsに追加。

app/javascript/packs/application.js(webpacker)
(省略)...

import Rails from 'rails-ujs'
Rails.start()

または、

app/assets/javascripts/application.js
//= require rails-ujs

自分の場合はこれで直りました。

参考にさせて頂きました。

Rails 5.1を使うならSprocketsは捨ててwebpackを使おう

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

Railsのポリモーフィック関連の挙動確認

はじめに

公式ドキュメントのポリモーフィック関連の項目を見た際に、挙動がイメージしにくかったので、ポリモーフィック関連を持つモデルを作成しつつ、DBのデータやコンソールで動きを確認します。

環境

  • OS : Ubuntu 17.04
  • Ruby : 2.6.3
  • Rails: 5.2.3
  • MySQL: 5.7.20

ポリモーフィズム(多様性)

ポリモーフィックと聞くと、オブジェクト指向プログラミングで出てくるポリモーフィズムを思い出す人が多いはずです。

ポリモーフィズムとは、プログラミング言語の持つ性質の一つで、ある関数やメソッドなどが、引数や返り値の数やデータ型などの異なる複数の実装を持ち、呼び出し時に使い分けるようにできること。
(略)
オブジェクト指向プログラミング言語では親クラスから派生(継承)した子クラスがメソッドの内容を上書き(オーバーライド)したり、インターフェースで定義されたメソッドを実装することによりこれを実現している。

オブジェクト指向プログラミングの、ポリモーフィズムでは、継承やインターフェース1を使って同名のメソッドを複数のクラスで定義します。それによって同名のメソッドでも、インスタンスごとに振る舞いを変える(多様性2が生まれる)というものです。

ただ、ActiveRecordのポリモーフィック関連は、継承やインターフェース1を用いるのではなく、ActiveRecordで定義されている関連付けの機能を使用します。

(用途は異なりますが継承を用いるシングルテーブル継承 (STI)というものもあります)

ポリモーフィック関連を持つモデルの作成

Railsガイドを元に、ポリモーフィック関連付けのモデルを作成します。

作成するモデルは、Picture/Employee/Productの3つです。
ここでは、モデルPictureと複数のモデルEmployee/Productとの関連を、imageableという関連1つで表現します。

$ bin/rails generate model Product name:string
Running via Spring preloader in process 21448
      invoke  active_record
      create    db/migrate/20190504040556_create_products.rb
      create    app/models/product.rb
      invoke    test_unit
      create      test/models/product_test.rb
      create      test/fixtures/products.yml

$ bin/rails generate model Employee name:string
# 省略

$ bin/rails generate model Picture name:string
# 省略

PictureのMigrationファイルを修正します。

class CreatePictures < ActiveRecord::Migration[5.2]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end

    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

作成した、それぞれのモデルに関連を記載します。

# app/models/picture.rb
class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

# app/models/employee.rb
class Employee < ApplicationRecord
  has_many :pictures, as: :imageable
end

# app/models/product.rb
class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

マイグレーションを実行します。

$ bin/rails db:migrate
== 20190504040556 CreateProducts: migrating ===================================
-- create_table(:products)
   -> 0.0588s
== 20190504040556 CreateProducts: migrated (0.0590s) ==========================

== 20190504040742 CreateEmployees: migrating ==================================
-- create_table(:employees)
   -> 0.0648s
== 20190504040742 CreateEmployees: migrated (0.0650s) =========================

== 20190504041111 CreatePictures: migrating ===================================
-- create_table(:pictures)
   -> 0.0630s
-- add_index(:pictures, [:imageable_type, :imageable_id])
   -> 0.0678s
== 20190504041111 CreatePictures: migrated (0.1311s) ==========================

MySQLのデータを確認

DBeaver をつかってデータを確認します。

Screenshot from 2019-05-04 13-34-04.png

Screenshot from 2019-05-04 13-34-24.png

Screenshot from 2019-05-04 13-34-51.png

ポリモーフィック関連を実現するために、テーブルpicturesに、imageable_id/imageable_typeというカラムが追加されてます。
imageableという単語は、モデルEmployee/Productで定義されていました。

has_many :pictures, as: :imageable

説明をすると、「複数のpictureを持ち、その関連はimageableとする」といったところでしょうか。

モデルの作成

動きを確認するために、モデルを作成します。

$ bin/rails c

> product = Product.create(name: '商品1')
=> #<Product id: 1, name: "商品1", created_at: "2019-05-04 04:41:12", updated_at: "2019-05-04 04:41:12">

> employee = Employee.create(name: '田中一郎')
=> #<Employee id: 1, name: "田中一郎", created_at: "2019-05-04 04:42:23", updated_at: "2019-05-04 04:42:23">

> product.pictures
=> #<ActiveRecord::Associations::CollectionProxy []>

> employee.pictures
=> #<ActiveRecord::Associations::CollectionProxy []>

ここはhas_many の動きです。まだPictureを作成していないのでデータがヒットしません。

product.pictures.create(name: '商品1-1.jpg')
=> #<Picture id: 1, name: "商品1-1.jpg", imageable_id: 1, imageable_type: "Product", created_at: "2019-05-04 04:54:46", updated_at: "2019-05-04 04:54:46">

employee.pictures.create(name: '田中一郎_1.jpg')
=> #<Picture id: 2, name: "田中一郎_1.jpg", imageable_id: 1, imageable_type: "Employee", created_at: "2019-05-04 04:59:24", updated_at: "2019-05-04 04:59:24">

それぞれのProduct/Employeeで、picturesを作成したところ、こちらで指定していないのにもかかわらずimageable_idとimageable_typeに値が入っています。

今度はPictureから、Product/Employeeの関連を見てみましょう。

> pictures = Picture.all
  Picture Load (0.9ms)  SELECT  `pictures`.* FROM `pictures` LIMIT 11
=> #<ActiveRecord::Relation [#<Picture id: 1, name: "商品1-1.jpg", imageable_id: 1, imageable_type: "Product", created_at: "2019-05-04 04:54:46", updated_at: "2019-05-04 04:54:46">, #<Picture id: 2, name: "田中一郎_1.jpg", imageable_id: 1, imageable_type: "Employee", created_at: "2019-05-04 04:59:24", updated_at: "2019-05-04 04:59:24">]>

> pictures.first.imageable
  Picture Load (0.8ms)  SELECT  `pictures`.* FROM `pictures` ORDER BY `pictures`.'id' ASC LIMIT 1
  Product Load (1.1ms)  SELECT  `products`.* FROM `products` WHERE `products`.'id' = 1 LIMIT 1
=> #<Product id: 1, name: "商品1", created_at: "2019-05-04 04:41:12", updated_at: "2019-05-04 04:41:12">

> pictures.last.imageable
  Picture Load (0.8ms)  SELECT  `pictures`.* FROM `pictures` ORDER BY `pictures`.'id' DESC LIMIT 1
  Employee Load (1.2ms)  SELECT  `employees`.* FROM `employees` WHERE `employees`.'id' = 1 LIMIT 1
=> #<Employee id: 1, name: "田中一郎", created_at: "2019-05-04 04:42:23", updated_at: "2019-05-04 04:42:23">

(省略していた、SQL文のログも記載してます)

Pictureでは、Product/Employeeの値を参照する際に、Product/Employeeで定義していたimageableを使用して参照することができます。

ログから察するに、1回目のSQLでテーブルpicturesのデータを取得、2回目のSQLでpicturesのimageable_id/imageable_typeの値を使用して、検索するテーブルと、id値を決めているようです。

MySQLのデータを確認

Screenshot from 2019-05-04 13-53-18.png

Screenshot from 2019-05-04 13-53-38.png

Screenshot from 2019-05-04 14-05-43.png

コンソールで確認したように、それぞれのテーブルにデータが格納されています。

注意する点としてはpicturesのimageable_id/imageable_typeはRails(アプリ側)だと、ポリモーフィック関連を提供するカラムとして認識されますが、DB側にはポリモーフィック関連を表現する機能がないため「インデックスが張られているカラム」という認識しかありません。

例えば、アプリ側でEmployeeもしくはProductのデータを削除した際にdependentを使い、Pictureのimageable_id/imageable_typeのデータに手を加え関連の整合性を保つことができます。しかし、DB側でemployeeもしくはproductsを削除しても外部キー制約などを設定できない(このカラムでは外部キーとなるテーブルが明確でない)ため、picturesとの関連の整合性が取れなくなります。

まとめ

  • ActiveRecordのポリモーフィック関連は、ポリモーフィズムと目的は同様
  • しかし、オブジェクト指向プログラミングなどと手法が異なる
  • x_id/x_typeといったカラムを追加して、対象のオブジェクト(テーブル)名と主キーのidを格納する
  • 1つの関連で、複数のオブジェクトの関連を表す事ができる
  • ポリモーフィック関連はActiveRecordの機能であり、DBはポリモーフィックの整合性を担保しない

  1. ここでいうインターフェースは、Javaの実装におけるインターフェースを想定しているような気がします。 

  2. オブジェクト指向プログラミングでよく例に挙げられるものは、厳密には「ポリモーフィズムの部分型付け」を指すようです。Wikipedia ポリモーフィズム  

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

【Ruby on Rails】混同しやすいredirect_toとrenderの動きと引数

はじめに

Qiita初投稿なので最初に少しだけ。

1か月ほど前にプログラミングの独学をし始め、現在はHTMLとCSSの基礎を終えて簡単なLPやサイトであれば模写してjQueryで軽く動きを付けられるくらいのレベルです。

恥ずかしながら現在ProgateでRuby on Railsを学習中です。
このレベルでQiitaに記事を書くのは大変申し訳なく思いますが、理解するのに少し時間がかかった点について、頭の中にあるふんわりとした概念を言語化して理解を定着させるためにも文章としてまとめたいと思います。

自身のインプットの質を高めるためのアウトプットですが、これからプログラミングを独学される方は多いと思うので、そういった方たちに向けても書いていきます。

また、まだまだプログラミング入門者ゆえに記述内容が間違っているかもしれないので、そういった場合はご指摘いただけると幸いです。

今回躓いた点

僕が今回躓いたのは、redirect_torenderの引数がどちらともURLだと間違って理解していたためにエラーを発生してしまい少し躓きました。

redirect_toは引数に"URL"を指定する*のに対して、renderは"フォルダ名/ファイル名"を指定します。

qiita.rb
redirect_to("/posts/index")  #「/posts/index」というURLを指定している
render("posts/index")  #「postsフォルダ内にあるindexファイル」を直接指定している

ちなみに、render("/posts/index")でも動作します。URLとして記述したのに動作していたことが今回の間違った理解につながっていました。

躓いたことによって、それぞれの引数が全く別のものを指していることを理解すると同時に、この二つのメソッドの動きについてもしっかりと理解できました。

今回は各メソッドの内側の動きについて書いていきます。

redirect_toとrenderは表面上の動きは似ているが内部の動きは大きく異なる

先ほども述べましたが、redirect_torenderは表面上の動きは確かに似ていますが、表示するまでの内部の動作が大きく異なります。

どういう事かというと、例えば、引数をこんな感じで取る場合を考えてみましょう。

qiita.rb
# どちらとも表面上は「edit.html.erb」というビューを表示
redirect_to("/posts/index")
render("posts/index")

両者ともに表面上は「edit.html.erb」というビューを表示させます。(URL:/posts/indexのルーティングがindexアクションになっている場合。基本的にはこうなっていると思います。)

しかし、表面上は「edit.html.erb」を表示するだけですが、その内部の動きは全く異なります。

Railsの表示の仕組み

内部の動きの違いについて説明する前に、Railsにおける表示の仕組みについて軽く押さえたいと思います。
Railsでは、ブラウザからHTMLファイルを要求された際に以下の順番でHTMLをブラウザに渡します。
①ルーティング
②コントローラのアクション
③ビュー

ルーティングは、指定されたURLからどのコントローラどのアクションで処理をするのかを決定する対応表のような役割を持っています。
そして、ルーティングによって指定されたコントローラのアクションはコントローラと同じ名前のフォルダからアクション名と同じビュー(HTMLファイル)をブラウザに返してます。

redirect_toメソッドの実際の動き

表示までの大まかな流れが分かったところで話を戻します。
redirect_to("/posts/index")render("posts/index")は両者ともに表面上は「edit.html.erb」というビューを表示させますが、その内部の動きは大きく異なります。

redirect_toは指定したURLに転送するメソッドです。

URLを指定するので、先ほど説明した「①ルーティングを通って→②アクションを決定して→③ビューを表示する」という流れでビューを表示し、ブラウザからURLを指定された時と同じ動きをします。

renderメソッドの実際の動き

一方、renderルーティングやアクションを経由せずに直接ビューを表示することができます。

ルーティングとアクションを経由せず直接ビューを表示するので、renderメソッドと同じアクション内で定義した@変数をビューで使える点がredirect_toとの大きな違いです。

まとめ

  • redirect_toの引数は「"URL"」でrenderの引数は「"フォルダ名/ファイル名"」
  • redirect_toメソッドは引数でURLを指定するのでルーティングからアクションを通してビューを表示する
  • renderメソッドは引数で直接ビューを指定して表示するため、ルーティング→アクションを経由しないのでrenderメソッドと同じアクション内で定義した@変数をビューで使うことができる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Redmine を GKE(k8s) + CloudSQL + FileStore でデプロイ

このページは 5/18(土) 14:00 - 18:00 開催予定のGCPUG Shimane #04 のハンズオンセッションの資料となります。
https://gcpug-shimane.connpass.com/event/128517/

注)当日まで随時修正を加える予定です。

アジェンダ

  • k8s, 構成の説明
  • RedmineをGKEをベースに動かしてみる(ハンズオン)
    • 事前準備
    • Cluster 作成
    • FileStore 作成
    • CloudSQL 作成
    • Redmine デプロイ
  • 運用管理について
  • 削除方法

構成の説明

構成図.png

RedmineをGKEをベースに動かしてみる(ハンズオン)

事前準備

  • プロジェクトの作成 ([gcpug-04] で作成)
  • 課金の有効化
  • APIの有効化
    • GKE
    • CloudSQL
    • Cloud SQL Admin API
    • FileStore

注)以降の手順(コマンド)はCloud Shellにアクセスして行う

gcloud components install kubectl
gcloud config set project gcpug-04
gcloud config set compute/zone asia-northeast1-a

Cluster 作成

クラスター名は [ redmine-cloudsql-filestore ] で作成する。

gcloud container clusters create redmine-cloudsql-filestore --num-nodes=3

preemptibleを利用する場合

gcloud container clusters create redmine-cloudsql-filestore --preemptible --machine-type=g1-small --num-nodes=2

# 一台はpreemptibleでないnodeを使用することによって無停止で運用
gcloud container node-pools create default-pool --cluster redmine-cloudsql-filestore --machine-type=g1-small --num-nodes=1

作成済みのclusterを利用する場合(クラスターの選択)

gcloud config set container/cluster redmine-cloudsql-filestore

Docker Image 作成

事前にRedmineを GKE 向けにDocker image化したリポジトリーを準備しています。
以下の手順で CloudShell 上でDocker image をbuildし、GCPのレジストリーサービス(Container Registry)にpushする。

# リポジトリーをダウンロード
git clone -b gcp/master https://github.com/yoshiokaCB/redmine.git ./redmine_gke && cd $_
# Docker imagesをbuild
docker-compose build
# アップロードようにtag付け
docker tag redmine_gke_app asia.gcr.io/gcpug-04/redmine_app:latest
# Buildしたイメージをpush
docker push asia.gcr.io/gcpug-04/redmine_app

FileStore 作成

gcloud filestore instances create redmine-nfs-server \
    --project=gcpug-04 \
    --location=asia-northeast1-a \
    --tier=STANDARD \
    --file-share=name="vol01",capacity=1TB \
    --network=name="default"

FileStoreのIPアドレスを確認し、メモしておく

gcloud filestore instances list

INSTANCE_NAME       ZONE               TIER      CAPACITY_GB  FILE_SHARE_NAME  IP_ADDRESS    STATE  CREATE_TIME
redmine-nfs-server  asia-northeast1-a  STANDARD  1024         vol01            10.171.14.xx  READY  2019-05-xxT03:24:36

先ほどメモしたIPアドレスを追記する

vi redmine-pv.yaml

# x.x.x.x(ip) を先ほどメモしたIPに変更する
# ...
# nfs:
#   path: /vol01
#   server: x.x.x.x(ip)

kubernetesで接続できるようにvolumeを作成

# PersistentVolume の作成(volumeを定義)
kubectl apply -f redmine-pv.yaml

# PersistentVolumeClaim(volumeとpodを紐付け)
kubectl apply -f redmine-volumeclaim.yaml

参考
https://cloud.google.com/kubernetes-engine/docs/concepts/persistent-volumes?hl=ja

CloudSQL 作成

注意) [password]は任意の文字列に変更してください。

# cloudsql instance 作成
gcloud sql instances create redmine-mysql \
    --region=asia-northeast1 \
    --database-version=MYSQL_5_6 \
    --root-password=[password] \
    --cpu=1 \
    --memory=3840MiB \
    --storage-type=SSD \
    --storage-size=10GB \
    --storage-auto-increase

# k8sへファイルを暗号化して保存しておく
kubectl create secret generic mysql --from-literal=password=[password]

# cloudsql へのアクセス用の iam 作成
gcloud iam service-accounts create cloudsql --display-name="cloudsql"

# cloudsql(iam) の権限追加
gcloud projects add-iam-policy-binding gcpug-04 \
    --member="serviceAccount:cloudsql@gcpug-04.iam.gserviceaccount.com" \
    --role="roles/cloudsql.client"

# credential ファイルのダウンロード
gcloud iam service-accounts keys create $HOME/cloudsql-credentials.json \
    --iam-account cloudsql@gcpug-04.iam.gserviceaccount.com

# kubernetes へ credential ファイルを登録
kubectl create secret generic cloudsql-instance-credentials \
    --from-file=credentials.json=$HOME/.google_auth/cloudsql-credentials.json

Redmine デプロイ

secret_key_baseを設定する

# secrete_key_baseを編集する
docker-compose run --rm app bundle exec rake secret
vi redmine.yaml
redmine.yaml
- name: SECRET_KEY_BASE
  value: [secret_key_base]

Redmineのデプロイをする

# redmine+sqlproxy のpod作成
kubectl create -f redmine.yaml

# サービスの作成
kubectl create -f redmine-service.yaml

サービス作成後、サイトの確認をする。

kubectl get svc
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
kubernetes   ClusterIP      10.47.xxx.xx   <none>          443/TCP        1d
mysql        ClusterIP      10.47.xxx.xx   <none>          3306/TCP       1d
redmine      LoadBalancer   10.47.xxx.xx   35.221.90.xx   80:30667/TCP   1d

# 35.221.90.xx にブラウザでアクセスする。

EXTERNL-IP(35.221.90.xx)にブラウザからアクセスする。
(ログインID,PASS共にadminでアクセス可能)

修正変更等を反省させる時は下記のように apply -f を使用する

kubectl apply -f redmine.yaml

kubectl (よく使うコマンド)

kubectl get xxx
# (pod, svc, deploy, pv, pvc, secrets, etc)

kubectl describe xxx [xxx_name]
# (pod, svc, deploy, pv, pvc, secrets, etc)

kubectl logs [pod_name]

# 削除系のコマンド
kubectl delete svc [service_name]
kubectl delete deployment [deployment_name]
kubectl delete pv [pv_name]
kubectl delete pvc [pvc_name]

参考サイト

gcloud リファレンス

https://cloud.google.com/sdk/gcloud/reference/

kubectl リファレンス

https://kubernetes.io/docs/reference/kubectl/overview/

kubernetes : kubectlコマンド一覧

https://qiita.com/suzukihi724/items/241f7241d297a2d4a55c

GKE

チュートリアル

https://cloud.google.com/kubernetes-engine/docs/tutorials/

Using Persistent Disks with WordPress and MySQL

https://cloud.google.com/kubernetes-engine/docs/tutorials/persistent-disk

Github(サンプルコード)

https://github.com/GoogleCloudPlatform/kubernetes-engine-samples

GKE + FileStore

https://cloud.google.com/filestore/docs/accessing-fileshares
https://cloud.google.com/kubernetes-engine/docs/concepts/persistent-volumes?hl=ja

GKE + CloudSQL

https://cloud.google.com/sql/docs/mysql/connect-kubernetes-engine?hl=ja#proxy

EC2 インスタンスで運用している GitLab を、 GKE に移行してみた。(Kubernetes Engine + Cloud SQL + Cloud Filestore)

https://qiita.com/nagase/items/0ec9bf1702b222cb2e69

GKEのインスタンスタイプを無停止で変更する

https://qiita.com/yagince/items/9d66a6cf58b2905c16db

https://cloud.google.com/kubernetes-engine/docs/concepts/node-pools?hl=ja
https://cloud.google.com/kubernetes-engine/docs/resize-cluster?hl=ja

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

Dockerではじめる『Everyday Rails - RSpecによるRailsテスト入門』

概要

Railsチュートリアルも完了し、さて次は何をしようかと思案していたところ、
『Everyday Rails - RSpecによるRailsテスト入門』がゴールデンウィークセール中でしたので、これはと思い購入。
Everyday Rails… Aaron Sumner 著 et al. [Leanpub PDF/iPad/Kindle]
Railsチュートリアルに引きづつき、Dockerで環境を構築。

環境

PC:MacBookPro
docker for mac

環境構築

RSpec用のコンテナ準備

まずは、ターミナルで適当なフォルダを作成して、カレントディレクトリに移動。

mkdir myRspec && cd $_

続いてDockerコンテナを起動。

$ docker container run -it --name myRspec -p 3000:3000 -v `pwd`:/myapp  ruby:2.4.6 /bin/bash

内容について軽く補足
* -it  : 標準入出力を繋げる。要するにターミナルで起動したコンテナを操作できるようにする。
* --name  : 名前付け。あるとわかりやすい。
* -p 3000:3000 : ホストとなるOSとDockerイメージのポートを繋げる。
* -v `pwd`:/myapp : ホストのカレントディレクトリと、Dockerのmyappディレクトリを同期させる。
  (同時にコンテナ内のルート直下にmyappディレクトリも作成される)
* ruby:2.4.6 : 引っ張ってくるDocker Image。サンプルアプリはRuby2.4なので今回はタグで2.4.6を指定。
* /bin/bash :実行するコマンド。

ターミナルの最初の部分がUSERnoMacBook-Pro:~からroot@:(12文字の英数字)に変わっていたら成功。
そのまま、次のコマンドを実行。

$ apt update
$ apt install -y nodejs

Githubからサンプルアプリケーションをクローン

サンプルアプリケーションはGitHubにあります。
https://github.com/everydayrails/everydayrails-rspec-2017
クローンの手順はやったことがなかったので、ググりながら進めます。

$ git clone https://github.com/everydayrails/everydayrails-rspec-2017.git

everydayrails-rspec-2017というディレクトが作成され、コードが取得できました。
まずはcd everydayrails-rspec-2017/でディレクトリに移動。
この状態では、Masterブランチのみ取得している状態です。
ここから2章開始の状態にするために、本文に従ってブランチを切り替えます。

$ git checkout -b my-02-setup origin/01-untested

これで、ソースコードの準備が整いました。先述の通り、ホストの myRspecディレクトリはmyappディレクトリと同期しています。
ホストPCのmyRspec下のeverydayrails-rspec-2017をVSCodeでひらけば、そのまま開発環境として利用できます。
ただし、Railsコマンドの実行などは、Dockerコンテナを動かしているターミナルからでないと使えません。

仕上げ

ターミナルで最後の仕上げです。

$ bundle install
$ bin/rails db:create:all
$ bin/rails db:migrate RAILS_ENV=development

これで準備完了のはずです。ターミナルにrails sと入力し、ブラウザからhttp://localhost:3000/に接続してみます。

スクリーンショット 2019-05-06 12.49.52.png

問題なく動いているようですね。それでは勉強開始!

参考

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

N+1の予感がしたらincludesを追加?

こんにちは!
入社したてのころ、右も左もわからずにコーディングをしていました。
そんな中で、僕もよく悩まされたN+1についての対策について簡単にまとめてみました。
N+1は簡単に防げてパフォーマンスをあげることができます。
すぐできて、効果大なのでぜひ実践してみてください!

そもそもN+1とは

SQLクエリが 「データ量N + 1回 」走ってしまい、取得するデータが多くなるにつれて(Nの回数が増えるにつれて)パフォーマンスを低下させてしまう問題です。

N+1問題 / Eager Loading とは
(引用させていただきました)

→簡単にいうとデータ取得の際、余計にSQLを発行してしまいパフォーマンスを下げてしまうことです。

テーブルの定義

例えば配列展開でBook(本)からAuthor(著者)の名前を出力したい場合(以下ER図&作成データになります)。
スクリーンショット 2019-05-05 20.13.02.png

念の為コードも

Author

class Author < ApplicationRecord
  has_many :books
end

Book

class Book < ApplicationRecord
  belongs_to :author
end

データの用意

author(諫山さん)が6冊の本(book)のリレーションを持っています。

irb(main):004:0> Author.all # 全てのAuthorレコード
  Author Load (0.5ms)  SELECT "authors".* FROM "authors"
+----+--------+-------------------------+-------------------------+
| id | name   | created_at              | updated_at              |
+----+--------+-------------------------+-------------------------+
| 1  | 諫山創 | 2019-05-05 10:44:51 UTC | 2019-05-05 10:44:51 UTC |
+----+--------+-------------------------+-------------------------+
irb(main):003:0> Book.all # 全てのBookレコード
  Book Load (1.3ms)  SELECT "books".* FROM "books"
+----+---------+-----------+-------------------------+-------------------------+
| id | title   | author_id | created_at              | updated_at              |
+----+---------+-----------+-------------------------+-------------------------+
| 1  | 進撃の1 | 1         | 2019-05-05 10:46:17 UTC | 2019-05-05 10:46:17 UTC |
| 2  | 進撃の2 | 1         | 2019-05-05 10:46:25 UTC | 2019-05-05 10:46:25 UTC |
| 3  | 進撃の3 | 1         | 2019-05-05 10:46:28 UTC | 2019-05-05 10:46:28 UTC |
| 4  | 進撃の4 | 1         | 2019-05-05 10:46:31 UTC | 2019-05-05 10:46:31 UTC |
| 5  | 進撃の5 | 1         | 2019-05-05 10:46:35 UTC | 2019-05-05 10:46:35 UTC |
| 6  | 進撃の6 | 1         | 2019-05-05 10:46:38 UTC | 2019-05-05 10:46:38 UTC |
+----+---------+-----------+-------------------------+-------------------------+
irb(main):004:0> Author.first.books # 諫山さんが6冊の本(book)のリレーションを保持
  Author Load (1.7ms)  SELECT  "authors".* FROM "authors" ORDER BY "authors"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Book Load (0.2ms)  SELECT "books".* FROM "books" WHERE "books"."author_id" = ?  [["author_id", 1]]
+----+---------+-----------+-------------------------+-------------------------+
| id | title   | author_id | created_at              | updated_at              |
+----+---------+-----------+-------------------------+-------------------------+
| 1  | 進撃の1 | 1         | 2019-05-05 10:46:17 UTC | 2019-05-05 10:46:17 UTC |
| 2  | 進撃の2 | 1         | 2019-05-05 10:46:25 UTC | 2019-05-05 10:46:25 UTC |
| 3  | 進撃の3 | 1         | 2019-05-05 10:46:28 UTC | 2019-05-05 10:46:28 UTC |
| 4  | 進撃の4 | 1         | 2019-05-05 10:46:31 UTC | 2019-05-05 10:46:31 UTC |
| 5  | 進撃の5 | 1         | 2019-05-05 10:46:35 UTC | 2019-05-05 10:46:35 UTC |
| 6  | 進撃の6 | 1         | 2019-05-05 10:46:38 UTC | 2019-05-05 10:46:38 UTC |
+----+---------+-----------+-------------------------+-------------------------+

コンソールで実行

配列展開でBookから親モデルのAuthorのnameを呼び出すとBookのデータ数分(6個)SQLを発行してしまいます。
→つまり5回もSQLが無駄に発行されてしまうのです。

books = Book.all
> Book Load (0.2ms)  SELECT "books".* FROM "books"
irb(main):037:0* books.each do |book|
irb(main):038:1*  book.author.name
irb(main):039:1> end
  Author Load (0.6ms)  SELECT  "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Author Load (0.1ms)  SELECT  "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Author Load (0.1ms)  SELECT  "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Author Load (0.1ms)  SELECT  "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Author Load (0.1ms)  SELECT  "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Author Load (0.1ms)  SELECT  "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
irb(main):040:1>

スクリーンショット 2019-05-05 20.25.32.png

この無駄なクエリを防ぐにはincludesを追加するのがもっとも簡単です

includes追加

  books = Book.all.includes(:author) #追加
  Book Load (2.1ms)  SELECT "books".* FROM "books"
  Author Load (0.2ms)  SELECT "authors".* FROM "authors" WHERE "authors"."id" = ?  [["id", 1]]

お気づきでしょうか、includes追加により関連レコード(author)も一緒に取得されています。
→つまり、Bookと一緒に紐づいたAuthorモデルのレコードも取得し変数に代入していることになります。

Before

books = Book.all
> Book Load (0.2ms)  SELECT "books".* FROM "books"

books = Bookモデルの全てのデータ

After
スクリーンショット 2019-05-05 20.33.31.png
books = Bookモデルの全てのデータとそれらにひもづくAuthorデータ

この状態でもう一度booksを展開してみましょう!

irb(main):051:0* books.each do |book|
irb(main):052:1*   book.author.name
irb(main):053:1> end
irb(main):054:0>

今度は展開のたびにAuthorを取得していません。
SQLの発行をおさえてパフォーマンス低下を防ぐことができましたね。

includesで色々なリレーションを取得する

先ほどはN:1(Book:Author)でのパターンでしたが、実際はもっと複雑な利用パターンが多いと思います。
そんな時に利用できる書き方をご紹介します。

N:1 = Book:Author

  books = Book.all.includes(:author)

こちらは先ほどのパターンでしたね

N:1:1 = Book:Author:Profile

では、Authorのプロフィール情報を保存するAuthors::Profileがあった場合

  books = Book.all.includes(author: :profile)
   Book Load (1.6ms)  SELECT  "books".* FROM "books" LIMIT ?  [["LIMIT", 11]]
  Author Load (0.4ms)  SELECT "authors".* FROM "authors" WHERE "authors"."id" = ?  [["id", 1]]
  Authors::Profile Load (0.4ms)  SELECT "authors_profiles".* FROM "authors_profiles" WHERE "authors_profiles"."author_id" = ?  [["author_id", 1]]

N:1:1:1 = Book : Author : Profile : ProfileImage

さらにProfileに1つのプロフィール写真Authors::ProfileImageひもづく場合

  books = Book.all.includes(author: [profile: :profile_image])
  Book Load (0.5ms)  SELECT  "books".* FROM "books" LIMIT ?  [["LIMIT", 11]]
  Author Load (0.1ms)  SELECT "authors".* FROM "authors" WHERE "authors"."id" = ?  [["id", 1]]
  Authors::Profile Load (0.2ms)  SELECT "authors_profiles".* FROM "authors_profiles" WHERE "authors_profiles"."author_id" = ?  [["author_id", 1]]
  Authors::ProfileImage Load (0.1ms)  SELECT "authors_profile_images".* FROM "authors_profile_images" WHERE "authors_profile_images"."id" = ?  [["id", nil]]

逆に1:N(Author:Book)なら?
これはリレーションを使い関連データを全て取得できますね。

  > Author.first.books
  Author Load (0.3ms)  SELECT  "authors".* FROM "authors" ORDER BY "authors"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Book Load (0.3ms)  SELECT "books".* FROM "books" WHERE "books"."author_id" = ?  [["author_id", 1]]

N+1の予感とタイトルにありますが、余計にクエリを投げるケースは配列展開が多いと思います。
慣れていない方はeachやmap, selectなど配列展開のメソッドを使う際にN+1が起きていないかぜひ意識してみてください!

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

APIサーバのレスポンスがスキーマ上のレスポンス定義と一致するかのテスト

committeecommittee-railsが(わからなかった|動かなかった)ので、WEB+DB PRESS Vol.108に記載されている方法でやってみた。

ざっくり言うと

oas_parserでswagger.jsonをパースして、json_schemaでバリデーションを行う

gem

gem 'oas_parser'
gem 'json_schema'

swagger.json

{
  "swagger": "2.0",
  "info": {
    "title": "API V1",
    "version": "v1"
  },
  "servers": [
    {
      "url": "http://localhost:3000/api-docs",
      "description": "development server"
    }
  ],
  "basePath": "/api/v1",
  "paths": {
    "/areas/{id}": {
      "get": {
        "summary": "Retrieves a area",
        "tags": [
          "Areas"
        ],
        "description": "Retrieves a specific area by id",
        "operationId": "getArea",
        "security": [
          {
            "apiKey": [

            ]
          }
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name":        "id",
            "in":          "path",
            "type":        "integer",
            "description": "エリアID",
            "required":    true
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "schema": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name"
              ]
            }
          }
        }
      }
    },
    "securityDefinitions": {
    "apiKey": {
      "type": "apiKey",
      "name": "Authorization",
      "in": "header"
    }
  }
}

SchemaオブジェクトとJSON Schemaライブラリを用いてJSONのバリデーションを行う

  • spec/rails_helper.rb
RSpec.configure do |config|
  config.include ControllerSpecsHelper

  config.before :example, type: :controller do
    spec        = OasParser::Definition.resolve(Rails.root.join('swagger', 'v1', 'swagger.json'))
    schema_data = spec.path_by_path(schema_path).endpoint_by_method(schema_method).response_by_code(code.to_s).raw["schema"]
    @schema     = JsonSchema.parse!(schema_data) if schema_data
  end
  • spec/support/controller_specs_helper.rb
module ControllerSpecsHelper
  def expect_to_conform_schema(response)
    expect {
      @schema.validate!(JSON.parse(response.body))
    }.not_to raise_error
  end
end

テスト

require 'rails_helper'

RSpec.describe Api::V1::AreasController, type: :controller do
  render_views

  describe 'GET #show' do
    let(:schema_path)   { '/areas/{id}' }
    let(:schema_method) { 'get' }
    let(:code)          { 200 }

    it 'conform json schema' do
      expect_to_conform_schema response
    end

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

Day004 webエンジニアへの道 - rails tutorialを始める -

こんにちは。
webエンジニアを目指すtomoです。

エンジニアの勉強をしている日々の学びを記録しています。

私自身の頭の整理と今後迷った時のリファレンスも兼ねて書き連ねていきますが、同じようにエンジニア転職を考えている方の参考にもなればと考えています。
また、「ここ間違ってる!こっちが正しい!」といったご指摘もあれば頂けると嬉しいです?


[1.3.2 rails server]のブラウザ表示でつまづく

railsチュートリアルの文章に「クラウドIDEの場合は、[Share] を開いて、開きたいアプリケーションのアドレスをクリックします(図 1.11)。]と記載があり、文章に従って進めていたのですがうまく表示されませんでした。

調べた結果、文章ではなく図1.11と図1.12に従うことでブラウザを正しく表示することができました。

正しくは「クラウドIDEの場合は、[Preview]から[Preview running application]を開いて、アドレスバーの右横にあるブラウザ展開アイコンをクリックする。」のようです。
*[Share]から開く方法があれば教えてください!

[参考]
Cloud9上でのRuby on rails サーバ起動/ページ表示方法(Ruby on rails Tutorial 1.3.2)

★メモ

*後日調べること & 気づいたこと
  • チュートリアルとWikipediaでMVCモデルの概念図が異なるので、他のサイトではどのように説明されているか調べる。

さいごに

5月5,6日は勉強外の予定があり進捗少ないかもしれませんが、毎日少しでもコードを書いて進めたいと思います。


twitterもやっているので、宜しければフォローお願いします!
@tomo_tech_

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

【10日間でポートフォリオ作成に挑戦】9日目:フロントエンドの実装〜各種機能の修正

概要

今回は、2019年のGW期間(10日間)を全て費やして取り組むポートフォリオの製作過程を取りまとめた内容を投稿させて頂きます。(投稿は毎日行う予定)

全体通した取り組みの詳細については、前回までの記事をご参照ください。

【10日間でポートフォリオ作成に挑戦】1日目:要件定義〜記事投稿のCRUD
【10日間でポートフォリオ作成に挑戦】2日目:アクセス制限〜コメントのCRUD機能
【10日間でポートフォリオ作成に挑戦】3日目:ページネーション~CKEditorの導入
【10日間でポートフォリオ作成に挑戦】4日目:テーブル分割〜CKEditorのフォームへの反映
【10日間でポートフォリオ作成に挑戦】5日目:CKEditorへ画像アップロード機能を追加
【10日間でポートフォリオ作成に挑戦】6日目:テストコードの実装
【10日間でポートフォリオ作成に挑戦】7日目:検索機能〜いいね機能の実装
【10日間でポートフォリオ作成に挑戦】8日目:記事ストック機能〜ユーザーフォロー機能の実装

今日一日の作業内容

ここからは、今日1日で取り組んだ作業内容をご説明します。

フロントエンドの実装

先日、サーバーサイドのの実装が概ね完了したので、全く未着手だったフロントエンドの実装を行って行きます。
と言っても、私自身、実務でフロントの実装は全く経験していないのと、残り二日という短い期間なので、手早く実装できる手段を採用します。

そこで利用するのが、前回紹介したCSSフレームワークのMaterializeCSSです。
本当は、主流のBootstrapを利用したかったのですが、全く触った事が無いので、今回は諦めました。

使い方としては、公式のドキュメントから、使いたいパーツやデザインを探してきて、そのデザインを適用させる為のclassを、任意の箇所に記述するだけで、実装が完了します。
なので、全くCSSを触らなくても、WEBサイトの体裁を整える事ができます。

↓公式ドキュメント

Screen Shot 2019-05-06 at 1.23.33.png

↓実装したコード

= f.submit value: t('common.button.submit'), class: 'waves-effect waves-light btn orange'

↓結果
Screen Shot 2019-05-06 at 1.24.16.png

そうして、

↓記事の一覧表示

61f4b02b0ef38083298f9dc935485986.gif

↓記事の作成ページ

6c71ee68a3ea04585e7db5ac002943d5.gif

↓検索機能

30b232fc80076504327daaadcf0f1f9f.gif

↓ログインページ

fd1a1f08010b99a2020da7db27f224f2.gif

↓マイページ

e526903fd6e94b9bb1eae5e0d3d64768.gif

即席なので、かなり粗だらけですが、ゆくゆくはCSSフレームワークは使わずに仕上げていきたいと考えています。Vue.jsも使ってみたいですし!

今日の失敗

ここからは今日の失敗をまとめていきます

ページネーションの表示数をベタ打ち

記事の一覧を表示する箇所ではページネーションを導入していたのですが、1ページの表示件数を指定する記述は、各コードにそれぞれ記述していました。

記事の一覧は、下記のコードで表した通り、「通常の一覧表示・検索結果の一覧・ストックの一覧・自身の投稿記事の一覧」の4箇所が該当します。

controllers/posts_controller.rb
def index
  @posts = Post.page(params[:page]).per(10).order(id: "DESC").includes(:user)
end

def search
  @search = Post.ransack(params[:q])
  @posts = @search.result.page(params[:page]).per(10)
  @keyword = params[:q][:title_cont]
end
controllers/users_controller.rb
def show
  @posts = Post.where(user_id: params[:id]).page(params[:page]).per(10).order(id: "DESC")
  @user = User.find(params[:id])
end

def post_stocks
  post_stocks = current_user.post_stocks.pluck(:post_id)
  @posts = Post.where(id: post_stocks).page(params[:page]).per(10).order(id: "DESC")
end

流石に4箇所全てにベタ打ちは冗長なので、変数に置き換える事にしました。
今回は、コントローラーが別れているので、application_controllerに変数を定義しています。

controllers/application_controller.rb
PER = 10

これで、もし表示件数を変更する必要が出て来ても、application_controllerの記述だけ変更すれば良いので、メンテナンス性は高まります。

画像アップロード機能の実装方法に無理がある

念のため、振り返りでER図を再掲します。

Screen Shot 2019-05-06 at 3.57.13.png

注目して頂きたいのが、記事に添付した画像を、どの様に管理しているか?です。
ER図では、PostDescriptionと関連付けさせたimageテーブルで管理する様にしています。

そして、今回の記事の編集はCKEditorを利用していますが、CKEditorは、画像アップロードの操作をした時点で、画像を保存します。

↓この時点

5c1119e716b18c51ea2becac9789f71f.gif

つまり、記事の新規作成の場合、記事の作成より先に、画像がDBに保存されます。
なので、画像保存時に、記事との関連付けを行う事が出来ません(まだ存在しないデータと関連付けは出来ない)

CKEditorは、アップロードした画像のパスも含めて、入力した情報をHTML形式に変換して保存してくれるので、関連付けしていなくとも、編集や詳細表示は問題なく出来ます。

支障をきたすのが、記事の一覧表示で、画像を表示させる場合です。
関連付けしていないので、PostからもImageからも、互いにどのデータと紐づいているか判別出来ません。

それを解消させる為に、下記の様な手段を取りました。

1:postにimage_idカラムを新たに追加し、モデルには下記のアソシエーションを記述

models/post.rb
has_one :image, dependent: :destroy

2:画像保存後に、保存されたレコードのIDをセッションで保持

controllers/images_controller.rb
def create
  image = current_user.description_images.build(
      image: params[:upload],
      image_relation: params[:image_relation]
  )
  if image.save
    render json: {
        url: image.image[:standard].url,
        uploaded: true
    }
     #画像のDBへの保存が完了したタイミングで、IDをセッションに保持
     # 複数枚画像を投稿した際は、最初の画像を登録する様にnilガードで記述
    session[:image_id] ||= image.id
  else
    render json: {
        error: {
            message: image.errors.full_messages
        },
        uploaded: false
    }
  end
end

3:記事保存時に、セッションに保持していたImageのIDも、外部キーとして一緒に保存する

controllers/posts_controller.rb
def create
  @post = current_user.posts.build(post_params)
  @post[:image_id] = session[:image_id]
  if @post.save
    session[:image_id] = nil
    redirect_to post_path(@post), notice: t('common.message.post_create')
  else
    render :new
  end
end

こうする事で、あとは下記のコードで、記事一覧の中で画像も表示させる事が可能になります。

views/posts/index.html.haml
.nav-wrapper.container
  %h5.header.orange-text
    = t('common.header.post_index')
  .row
    -# 要素の数だけ繰り返し
    = render @posts
views/posts/_post.html.haml
= link_to post_path(post) do
  .col.s6
    .post.card
      .card-image
        - if post.image_id.present?
          -# この記述で対象のpostと関連付いた画像を表示させる
          -# (:standard)は任意で変更できる様にした画像リサイズのオプション
          = image_tag post.image.image_url(:standard)
        - else
          = image_tag 'no_image.png'
      .card-title
        = post.title
      .card-user
        = "投稿者:#{post.user.name}"

もっと良い方法がRailsやCKEdtorにはあるのかもしれませんが、あまり調査に時間を掛ける余裕も無かったため、一旦この方法で実装しました。

これについては、追い追い調査して、効果的な方法を探りたいと考えています。

明日の予定

  • AWSへのデプロイ
  • HTTPS化
  • フロントの調整

AWSへのデプロイが1ヶ月ぶりの作業になるのと、HTTPS化が初の試みなので、本当に明日1日だけで完成できるのか?かなり不安が残ります。

いずれにしても、この10日間の開発で終わりにするのではなく、今後も継続して開発は続けて行き、機能を更にブラッシュアップさせて行こうと考えています。

※追記:10日目を投稿しました
【10日間でポートフォリオ作成に挑戦】10日目:AWSでのデプロイ

おまけ

最後になりますが、現在、私は下記の目標を立てて学習に取り組んでいます。

  • 3年間で「10,000時間」をプログラミングに費やす
  • その間、毎日ブログの投稿を行う

Twitterでは、その過程で学んだ事などを発信しています。
もし宜しければフォローしてみてください。

Twitter:@ryoutaku_jo
ブログ:りょうたくのWEBエンジニア日記

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