- 投稿日:2019-05-06T23:38:07+09:00
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.rbdef 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.rbreturning: %w[ id name ]と指定すると、
id
とname
が返ってきます。1-2.
unique_by
オプション
unique_by
は、PostgreSQLとSQLiteにのみ有効なオプションです。
デフォルトでは、id
(主キー)のみが重複しているとスキップされます。unique_by
オプションをつけると、重複でスキップするカラムを配列によって追加で指定できます。rails/activerecord/lib/active_record/persistence.rbunique_by: { columns: %w[ isbn ] }以下のように
columns:
とwhere:
を組み合わせることで、部分インデックスを指定することもできます。rails/activerecord/lib/active_record/persistence.rbunique_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.rbdef 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.rbdef 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
オプションと同じです。
- 投稿日:2019-05-06T23:15:03+09:00
【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からアクセス可能
コンテナ構築方法
- 以下記載の手順をそのまま実行してください
1. フォルダ、ファイルの準備
- 好きな場所にフォルダを新規作成
- フォルダ内に4つのファイルを作成(中身は空でOK)
例)この記事では、フォルダ名を
hoge_folder
としています
- hoge_folder
├ Dockerfile
├ docker-compose.yml
├ Gemfile
├ Gemfile.lock
2. ファイルの編集
- 1. で作成した4つのフォルダに以下の内容をコピペする
① Dockerfile
/hoge_folder/DockerfileFROM 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.ymlversion: '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/Gemfilesource '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.ymldefault: &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
コマンド(再起動する場合) $ docker-compose restart (停止する場合) $ docker-compose stop作 戦 完 了 !
- それでは、
Rails on Docker
ライフ をお楽しみください!謝辞
- @akirakudo さんの記事は、大変参考になりました
- 投稿日:2019-05-06T23:15:03+09:00
【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からアクセス可能
コンテナ構築方法
- 以下記載の手順をそのまま実行してください
1. フォルダ、ファイルの準備
- 好きな場所にフォルダを新規作成
- フォルダ内に4つのファイルを作成(中身は空でOK)
例)この記事では、フォルダ名を
hoge_folder
としています
- hoge_folder
├ Dockerfile
├ docker-compose.yml
├ Gemfile
├ Gemfile.lock
2. ファイルの編集
- 1. で作成した4つのフォルダに以下の内容をコピペする
① Dockerfile
/hoge_folder/DockerfileFROM 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.ymlversion: '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/Gemfilesource '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.ymldefault: &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
コマンド(再起動する場合) $ docker-compose restart (停止する場合) $ docker-compose stop作 戦 完 了 !
- それでは、
Rails on Docker
ライフ をお楽しみください!謝辞
- @akirakudo さんの記事は、大変参考になりました
- 投稿日:2019-05-06T22:33:05+09:00
RailsアプリをHerokuでデプロイする際の注意点
はじめに
初投稿です。本とネット教材で半年学んだ程度のヒヨッコです。
AWS cloud9でRuby on Railsプロジェクトを作成し、GitHub経由でHerokuでデプロイしました。
その際に発生したエラー・注意点を備忘録として投稿させていただきます。Herokuの注意点
Herokuではsqlite3が使えないようなので、本番環境用のgemを別に設定する必要があります。
以下リンクを参考にPostgreSQLを入れました。
(参考)HerokuではSQLite3が使えない?! https://qiita.com/MosamosaPoodle/items/7149aa66f1c087472777
・Rails Gemfileを修正
Gemfilegem 'sqlite3'↓変更する
Gemfilegem '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アカウントパスワードを入力してもエラーになります。TerminalUsername 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
- 投稿日:2019-05-06T22:33:05+09:00
【初学者向け】RailsアプリをHerokuでデプロイする際の注意点
はじめに
初投稿です。本とネット教材で半年学んだ程度のヒヨッコです。
AWS cloud9でRuby on Railsプロジェクトを作成し、GitHub経由でHerokuでデプロイしました。
その際に発生したエラー・注意点を備忘録として投稿させていただきます。Herokuの注意点
Herokuではsqlite3が使えないようなので、本番環境用のgemを別に設定する必要があります。
以下リンクを参考にPostgreSQLを入れました。
(参考)HerokuではSQLite3が使えない?! https://qiita.com/MosamosaPoodle/items/7149aa66f1c087472777
・Rails Gemfileを修正
Gemfilegem 'sqlite3'↓変更する
Gemfilegem '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アカウントパスワードを入力してもエラーになります。TerminalUsername 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
- 投稿日:2019-05-06T19:39:23+09:00
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 JOIN
はLEFT 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
じゃ。
- 投稿日:2019-05-06T19:39:23+09:00
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 JOIN
はLEFT 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
じゃ。
- 投稿日:2019-05-06T17:04:12+09:00
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 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 で標準になったようです。参考
- 投稿日:2019-05-06T16:55:52+09:00
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-ujsapplication.jsに追加。
app/javascript/packs/application.js(webpacker)(省略)... import Rails from 'rails-ujs' Rails.start()または、
app/assets/javascripts/application.js//= require rails-ujs
自分の場合はこれで直りました。
参考にさせて頂きました。
- 投稿日:2019-05-06T16:54:34+09:00
Railsのポリモーフィック関連の挙動確認
はじめに
公式ドキュメントのポリモーフィック関連の項目を見た際に、挙動がイメージしにくかったので、ポリモーフィック関連を持つモデルを作成しつつ、DBのデータやコンソールで動きを確認します。
環境
- OS : Ubuntu 17.04
- Ruby : 2.6.3
- Rails: 5.2.3
- MySQL: 5.7.20
ポリモーフィズム(多様性)
ポリモーフィックと聞くと、オブジェクト指向プログラミングで出てくるポリモーフィズムを思い出す人が多いはずです。
ポリモーフィズムとは、プログラミング言語の持つ性質の一つで、ある関数やメソッドなどが、引数や返り値の数やデータ型などの異なる複数の実装を持ち、呼び出し時に使い分けるようにできること。
(略)
オブジェクト指向プログラミング言語では親クラスから派生(継承)した子クラスがメソッドの内容を上書き(オーバーライド)したり、インターフェースで定義されたメソッドを実装することによりこれを実現している。オブジェクト指向プログラミングの、ポリモーフィズムでは、継承やインターフェース1を使って同名のメソッドを複数のクラスで定義します。それによって同名のメソッドでも、インスタンスごとに振る舞いを変える(多様性2が生まれる)というものです。
ただ、ActiveRecordのポリモーフィック関連は、継承やインターフェース1を用いるのではなく、ActiveRecordで定義されている関連付けの機能を使用します。
(用途は異なりますが継承を用いるシングルテーブル継承 (STI)というものもあります)
ポリモーフィック関連を持つモデルの作成
- ActiveRecordマイグレーション - Railsガイド 2.2 モデルを生成する
- ActiveRecordの関連付け(アソシエーション) - Railsガイド 2.9 ポリモーフィック関連付け
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 をつかってデータを確認します。
ポリモーフィック関連を実現するために、テーブル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のデータを確認
コンソールで確認したように、それぞれのテーブルにデータが格納されています。
注意する点としては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はポリモーフィックの整合性を担保しない
ここでいうインターフェースは、Javaの実装におけるインターフェースを想定しているような気がします。 ↩
オブジェクト指向プログラミングでよく例に挙げられるものは、厳密には「ポリモーフィズムの部分型付け」を指すようです。Wikipedia ポリモーフィズム ↩
- 投稿日:2019-05-06T15:05:08+09:00
【Ruby on Rails】混同しやすいredirect_toとrenderの動きと引数
はじめに
Qiita初投稿なので最初に少しだけ。
1か月ほど前にプログラミングの独学をし始め、現在はHTMLとCSSの基礎を終えて簡単なLPやサイトであれば模写してjQueryで軽く動きを付けられるくらいのレベルです。
恥ずかしながら現在ProgateでRuby on Railsを学習中です。
このレベルでQiitaに記事を書くのは大変申し訳なく思いますが、理解するのに少し時間がかかった点について、頭の中にあるふんわりとした概念を言語化して理解を定着させるためにも文章としてまとめたいと思います。自身のインプットの質を高めるためのアウトプットですが、これからプログラミングを独学される方は多いと思うので、そういった方たちに向けても書いていきます。
また、まだまだプログラミング入門者ゆえに記述内容が間違っているかもしれないので、そういった場合はご指摘いただけると幸いです。
今回躓いた点
僕が今回躓いたのは、
redirect_to
とrender
の引数がどちらともURLだと間違って理解していたためにエラーを発生してしまい少し躓きました。
redirect_to
は引数に"URL"を指定する*のに対して、renderは"フォルダ名/ファイル名"を指定します。qiita.rbredirect_to("/posts/index") #「/posts/index」というURLを指定している render("posts/index") #「postsフォルダ内にあるindexファイル」を直接指定しているちなみに、
render("/posts/index")
でも動作します。URLとして記述したのに動作していたことが今回の間違った理解につながっていました。躓いたことによって、それぞれの引数が全く別のものを指していることを理解すると同時に、この二つのメソッドの動きについてもしっかりと理解できました。
今回は各メソッドの内側の動きについて書いていきます。
redirect_toとrenderは表面上の動きは似ているが内部の動きは大きく異なる
先ほども述べましたが、
redirect_to
とrender
は表面上の動きは確かに似ていますが、表示するまでの内部の動作が大きく異なります。どういう事かというと、例えば、引数をこんな感じで取る場合を考えてみましょう。
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
メソッドと同じアクション内で定義した@変数をビューで使うことができる
- 投稿日:2019-05-06T14:58:46+09:00
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 デプロイ
- 運用管理について
- 削除方法
構成の説明
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-aCluster 作成
クラスター名は [ redmine-cloudsql-filestore ] で作成する。
gcloud container clusters create redmine-cloudsql-filestore --num-nodes=3preemptibleを利用する場合
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_appFileStore 作成
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=jaCloudSQL 作成
注意) [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.jsonRedmine デプロイ
secret_key_baseを設定する
# secrete_key_baseを編集する docker-compose run --rm app bundle exec rake secret vi redmine.yamlredmine.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.yamlkubectl (よく使うコマンド)
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=jaGKE + 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
- 投稿日:2019-05-06T13:01:24+09:00
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 nodejsGithubからサンプルアプリケーションをクローン
サンプルアプリケーションは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-06T12:50:19+09:00
N+1の予感がしたらincludesを追加?
こんにちは!
入社したてのころ、右も左もわからずにコーディングをしていました。
そんな中で、僕もよく悩まされたN+1についての対策について簡単にまとめてみました。
N+1は簡単に防げてパフォーマンスをあげることができます。
すぐできて、効果大なのでぜひ実践してみてください!そもそもN+1とは
SQLクエリが 「データ量N + 1回 」走ってしまい、取得するデータが多くなるにつれて(Nの回数が増えるにつれて)パフォーマンスを低下させてしまう問題です。
N+1問題 / Eager Loading とは
(引用させていただきました)→簡単にいうとデータ取得の際、余計にSQLを発行してしまいパフォーマンスを下げてしまうことです。
テーブルの定義
例えば配列展開でBook(本)からAuthor(著者)の名前を出力したい場合(以下ER図&作成データになります)。
念の為コードも
Author
class Author < ApplicationRecord has_many :books endBook
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>この無駄なクエリを防ぐには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
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が起きていないかぜひ意識してみてください!
- 投稿日:2019-05-06T10:56:19+09:00
APIサーバのレスポンスがスキーマ上のレスポンス定義と一致するかのテスト
committeeやcommittee-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
- 投稿日:2019-05-06T07:21:52+09:00
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_
- 投稿日:2019-05-06T05:38:44+09:00
【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サイトの体裁を整える事ができます。↓公式ドキュメント
↓実装したコード
= f.submit value: t('common.button.submit'), class: 'waves-effect waves-light btn orange'そうして、
↓記事の一覧表示
↓記事の作成ページ
↓検索機能
↓ログインページ
↓マイページ
即席なので、かなり粗だらけですが、ゆくゆくはCSSフレームワークは使わずに仕上げていきたいと考えています。Vue.jsも使ってみたいですし!
今日の失敗
ここからは今日の失敗をまとめていきます
ページネーションの表示数をベタ打ち
記事の一覧を表示する箇所ではページネーションを導入していたのですが、1ページの表示件数を指定する記述は、各コードにそれぞれ記述していました。
記事の一覧は、下記のコードで表した通り、「通常の一覧表示・検索結果の一覧・ストックの一覧・自身の投稿記事の一覧」の4箇所が該当します。
controllers/posts_controller.rbdef 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] endcontrollers/users_controller.rbdef 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.rbPER = 10これで、もし表示件数を変更する必要が出て来ても、
application_controller
の記述だけ変更すれば良いので、メンテナンス性は高まります。画像アップロード機能の実装方法に無理がある
念のため、振り返りでER図を再掲します。
注目して頂きたいのが、記事に添付した画像を、どの様に管理しているか?です。
ER図では、PostDescription
と関連付けさせたimage
テーブルで管理する様にしています。そして、今回の記事の編集は
CKEditor
を利用していますが、CKEditorは、画像アップロードの操作をした時点で、画像を保存します。↓この時点
つまり、記事の新規作成の場合、記事の作成より先に、画像がDBに保存されます。
なので、画像保存時に、記事との関連付けを行う事が出来ません(まだ存在しないデータと関連付けは出来ない)CKEditorは、アップロードした画像のパスも含めて、入力した情報をHTML形式に変換して保存してくれるので、関連付けしていなくとも、編集や詳細表示は問題なく出来ます。
支障をきたすのが、記事の一覧表示で、画像を表示させる場合です。
関連付けしていないので、PostからもImageからも、互いにどのデータと紐づいているか判別出来ません。それを解消させる為に、下記の様な手段を取りました。
1:postにimage_idカラムを新たに追加し、モデルには下記のアソシエーションを記述
models/post.rbhas_one :image, dependent: :destroy2:画像保存後に、保存されたレコードのIDをセッションで保持
controllers/images_controller.rbdef 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 end3:記事保存時に、セッションに保持していたImageのIDも、外部キーとして一緒に保存する
controllers/posts_controller.rbdef 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 @postsviews/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では、その過程で学んだ事などを発信しています。
もし宜しければフォローしてみてください。