- 投稿日:2020-07-20T22:00:42+09:00
railsで簡単に複数画像をアップロードする rails+carrierwave+cloudinary
1.概要
railsで画像投稿を利用したアプリを作りたいという時に、どうせなら複数枚を一度に投稿したいという人は多いのではないでしょうか。
今回は簡単なrails投稿アプリを開発しながら、複数画像投稿ができるように実装して行きます。
※今回の画像投稿は、cloudinaryを利用して行います。2.はじめに
開発環境
環境ruby 2.6.3 Rails 6.0.3完成予定
はじめる前に:Cloudinaryに登録
このリンクから登録をはじめましょう。
https://cloudinary.com/右上のsign up for freeを押すと、登録画面に飛びます。
諸々の情報を登録し仮登録を終えてから、最後にメールアドレス宛に送信されている本登録用のメッセージを開いて、本登録の完了までお願いします。3.複数画像投稿機能の実装
3.1.事前準備
まず複数画像投稿機能を実装するために、投稿するだけのサンプルアプリを作ります。
ターミナル$ cd $ cd desktop $ rails new ImageSample3.2.投稿周りの機能
投稿周りの機能はscaffoldを利用し作成します。その際、imageカラムを複数作成します。
今回は同時にimage1,image2の2枚投稿できるようにします。3枚以上同時に投稿したい場合は、image3,image4とカラムを増やしてみてください。ターミナル$ cd ImageSample $ rails g scaffold post body:text image1:string image2:string $ rails db:migrate3.3.viewの変更(新規投稿ページ)
新規投稿ページで画像を選択できるように、_form.html.erbを編集します。
posts/_form.html.erb# 変更前 <%= form.text_field :image1 %> # 変更後 <%= form.file_field :image1 %>image2についても同様の変更を行います。
3.4.viewの変更(投稿一覧ページ)
posts/index.html.erb# 変更前 <td><%= post.image1 %></td> # 変更後 <td><%= image_tag post.image1_url ,size: '200x150' %></td>image2についても同様の変更を行います。
,size: '200x150'に関しては、記述をしなくても問題ありませんが、画面いっぱいに画像が広がるのを防ぐために記述しました。3.5.viewの変更(投稿詳細ページ)
こちらも投稿一覧ページと基本的には同じです。
posts/show.html.erb# 変更前 <%= @post.image1 %> # 変更後 <%= image_tag @post.image1_url ,size: '200x150' %>image2についても同様の変更を行います。
3.6.gemの追加
Gemfilegem 'carrierwave' gem 'cloudinary' gem 'dotenv-rails'追加したら、
ターミナル$ bundle installをして反映させます。
3.7.アップローダー
アップローダーの作成
CarrierWaveのジェネレーターでアップローダーを作成します。
ターミナル$ rails g uploader Imageモデルの修正
app/models/post.rbを以下のように修正します。
app/models/post.rbclass Post < ApplicationRecord mount_uploader :image1, ImageUploader mount_uploader :image2, ImageUploader endアップローダの設定
app/uploaders/image_uploader.rbの6~8行目を変更します。
app/uploaders/image_uploader.rb# Choose what kind of storage to use for this uploader: storage :file # storage :fog上記を下図のように変えます。
app/uploader/image_uploader.rb# Choose what kind of storage to use for this uploader: if Rails.env.production? include Cloudinary::CarrierWave CarrierWave.configure do |config| config.cache_storage = :file end else storage :file end # storage :fog3.8.cloudinaryのAPIキー
.envファイル
.envというファイルをアプリケーションディレクトリ(appやdbやGemfileがあるディレクトリ)に自分で作成します。
次に作成した.envファイルに以下を入力します。.envCLOUD_NAME=q0w9e8r7t6yu5 #←この値は人によって違います!! CLOUDINARY_API_KEY=123456789012345 #←この値は人によって違います!! CLOUDINARY_API_SECRET=1a2s3d4f5g6h7j8k9l0a1s2d4f5g6h1q #←この値は人によって違います!!取得したCloudinaryのアカウントの、「Cloud name」、「API Key」、「API Secret」を利用します。
ここで「=」の後のそれぞれの値は先ほどのCloudinaryのマイページで取得したキーに書き換えてください(数字は個々人によって変わります).gitignore
アプリケーションディレクトリにある.gitignoreに下記を追加します。
.gitignore/.env
cloudinary.yml
configフォルダにcloudinary.ymlファイルを作成してください。
config/cloudinary.ymlに以下のようにそのままコピペしてください。config/cloudinary.ymldevelopment: cloud_name: <%= ENV['CLOUD_NAME'] %> api_key: <%= ENV['CLOUDINARY_API_KEY'] %> api_secret: <%= ENV['CLOUDINARY_API_SECRET'] %> enhance_image_tag: true static_file_support: false production: cloud_name: <%= ENV['CLOUD_NAME'] %> api_key: <%= ENV['CLOUDINARY_API_KEY'] %> api_secret: <%= ENV['CLOUDINARY_API_SECRET'] %> enhance_image_tag: true static_file_support: false test: cloud_name: <%= ENV['CLOUD_NAME'] %> api_key: <%= ENV['CLOUDINARY_API_KEY'] %> api_secret: <%= ENV['CLOUDINARY_API_SECRET'] %> enhance_image_tag: true static_file_support: false4.終わりに
以上で複数画像投稿ができるようになったと思います。
説明が最小限で申し訳ないですが、もし良かったら参考にしてみてください!
最後まで読んでくださってありがとうございました!!
- 投稿日:2020-07-20T22:00:42+09:00
railsで簡単に複数画像投稿を実装する rails+carrierwave+cloudinary
1.概要
railsで画像投稿を利用したアプリを作りたいという時に、どうせなら複数枚を一度に投稿したいという人は多いのではないでしょうか。
今回は簡単なrails投稿アプリを開発しながら、複数画像投稿ができるように実装して行きます。
※今回の画像投稿は、cloudinaryを利用して行います。2.はじめに
開発環境
環境ruby 2.6.3 Rails 6.0.3完成予定
はじめる前に:Cloudinaryに登録
このリンクから登録をはじめましょう。
https://cloudinary.com/右上のsign up for freeを押すと、登録画面に飛びます。
諸々の情報を登録し仮登録を終えてから、最後にメールアドレス宛に送信されている本登録用のメッセージを開いて、本登録の完了までお願いします。3.複数画像投稿機能の実装
3.1.事前準備
まず複数画像投稿機能を実装するために、投稿するだけのサンプルアプリを作ります。
ターミナル$ cd $ cd desktop $ rails new ImageSample3.2.投稿周りの機能
投稿周りの機能はscaffoldを利用し作成します。その際、imageカラムを複数作成します。
今回は同時にimage1,image2の2枚投稿できるようにします。3枚以上同時に投稿したい場合は、image3,image4とカラムを増やしてみてください。ターミナル$ cd ImageSample $ rails g scaffold post body:text image1:string image2:string $ rails db:migrate3.3.viewの変更(新規投稿ページ)
新規投稿ページで画像を選択できるように、_form.html.erbを編集します。
posts/_form.html.erb# 変更前 <%= form.text_field :image1 %> # 変更後 <%= form.file_field :image1 %>image2についても同様の変更を行います。
3.4.viewの変更(投稿一覧ページ)
posts/index.html.erb# 変更前 <td><%= post.image1 %></td> # 変更後 <td><%= image_tag post.image1_url ,size: '200x150' %></td>image2についても同様の変更を行います。
,size: '200x150'に関しては、記述をしなくても問題ありませんが、画面いっぱいに画像が広がるのを防ぐために記述しました。3.5.viewの変更(投稿詳細ページ)
こちらも投稿一覧ページと基本的には同じです。
posts/show.html.erb# 変更前 <%= @post.image1 %> # 変更後 <%= image_tag @post.image1_url ,size: '200x150' %>image2についても同様の変更を行います。
3.6.gemの追加
Gemfilegem 'carrierwave' gem 'cloudinary' gem 'dotenv-rails'追加したら、
ターミナル$ bundle installをして反映させます。
3.7.アップローダー
アップローダーの作成
CarrierWaveのジェネレーターでアップローダーを作成します。
ターミナル$ rails g uploader Imageモデルの修正
app/models/post.rbを以下のように修正します。
app/models/post.rbclass Post < ApplicationRecord mount_uploader :image1, ImageUploader mount_uploader :image2, ImageUploader endアップローダの設定
app/uploaders/image_uploader.rbの6~8行目を変更します。
app/uploaders/image_uploader.rb# Choose what kind of storage to use for this uploader: storage :file # storage :fog上記を下図のように変えます。
app/uploader/image_uploader.rb# Choose what kind of storage to use for this uploader: if Rails.env.production? include Cloudinary::CarrierWave CarrierWave.configure do |config| config.cache_storage = :file end else storage :file end # storage :fog3.8.cloudinaryのAPIキー
.envファイル
.envというファイルをアプリケーションディレクトリ(appやdbやGemfileがあるディレクトリ)に自分で作成します。
次に作成した.envファイルに以下を入力します。.envCLOUD_NAME=q0w9e8r7t6yu5 #←この値は人によって違います!! CLOUDINARY_API_KEY=123456789012345 #←この値は人によって違います!! CLOUDINARY_API_SECRET=1a2s3d4f5g6h7j8k9l0a1s2d4f5g6h1q #←この値は人によって違います!!取得したCloudinaryのアカウントの、「Cloud name」、「API Key」、「API Secret」を利用します。
ここで「=」の後のそれぞれの値は先ほどのCloudinaryのマイページで取得したキーに書き換えてください(数字は個々人によって変わります).gitignore
アプリケーションディレクトリにある.gitignoreに下記を追加します。
.gitignore/.env
cloudinary.yml
configフォルダにcloudinary.ymlファイルを作成してください。
config/cloudinary.ymlに以下のようにそのままコピペしてください。config/cloudinary.ymldevelopment: cloud_name: <%= ENV['CLOUD_NAME'] %> api_key: <%= ENV['CLOUDINARY_API_KEY'] %> api_secret: <%= ENV['CLOUDINARY_API_SECRET'] %> enhance_image_tag: true static_file_support: false production: cloud_name: <%= ENV['CLOUD_NAME'] %> api_key: <%= ENV['CLOUDINARY_API_KEY'] %> api_secret: <%= ENV['CLOUDINARY_API_SECRET'] %> enhance_image_tag: true static_file_support: false test: cloud_name: <%= ENV['CLOUD_NAME'] %> api_key: <%= ENV['CLOUDINARY_API_KEY'] %> api_secret: <%= ENV['CLOUDINARY_API_SECRET'] %> enhance_image_tag: true static_file_support: false4.終わりに
以上で複数画像投稿ができるようになったと思います。
説明が最小限で申し訳ないですが、もし良かったら参考にしてみてください!
最後まで読んでくださってありがとうございました!!
- 投稿日:2020-07-20T20:37:23+09:00
Digdagを用いてRubyOnRails環境でバッチ実装
Digdagについて
Getting started
Architecture
Concepts
Workflow definition
Scheduling workflow
Operators
Command reference
Language API -Ruby
Digdagで環境毎に設定値を変える(RubyOnRails)
Digdagを用いてRubyOnRails環境でバッチ実装準備
Ruby on Rails チュートリアルのサンプルアプリケーションダウンロード
https://github.com/yasslab/sample_appRuby on Rails チュートリアルのサンプルアプリケーションを実行して、ユーザーとPostを登録。
ぼくはtestユーザーを作って4つの投稿を登録しました。
今回は、ユーザー名をパラメーターにして該当するユーザーの投稿数を出力する簡単なバッチを作ります。
バッチの実装・起動は二つの方法でやってみます。Railsでバッチ処理を書く際によく使われている以下の二つの方法をやってみます。
①rails runner:スクリプトとしてバッチを書く
②rake task:ビルドタスクとしてバッチを書くrails runnerで実行
パラメーター取得はOptionParserを使いました。
バッチ処理スクリプトを追加
/lib/scripts/
の配下にバッチ処理スクリプトを追加lib/scripts/post_batch.rbrequire 'optparse' module Scripts class PostBatch def initialize @option = {} OptionParser.new do |opt| opt.on('-n VALUE', 'user name') { |v| @option[:name] = v} opt.parse!(ARGV) end end def count user = User.find_by(name: @option[:name]) puts "ID: #{user.id} 名前: #{user.name}" puts "投稿数: #{Micropost.where(user_id: user.id).count}" end end endlib配下のrubyファイルを自動ロードする
config/application.rbconfig.autoload_paths += %W(#{config.root}/lib)workflowsの配下にrails_runner.digを追加して以下の内容を追加
rails_runner.dig+task: sh>: bundle exe rails runner Scripts::PostBatch.new.count -n 'test'実行
testユーザーのID,名前、投稿数が出力されています。
実行結果$ digdag run rails_runner.dig --rerun 2020-07-20 19:38:39 +0900 [INFO] (0017@[0:default]+rails_runner+task): sh>: bundle exec rails runner Scripts::PostBatch.count 'test' ID: 5 名前: test 投稿数: 4rakeで実行
rakeタスク生成
$ rails g task task_post Running via Spring preloader in process 4255 create lib/tasks/tast_post.rake
/lib/tasks/task_post.rake
ファイルが生成されるので開いて以下のソースを追加lib/tasks/task_post.rakenamespace :task_post do desc "ユーザー投稿数を取得" task :count, ['name'] => :environment do |task, args| user = User.find_by(name: args.name) puts "ID: #{user.id} 名前: #{args.name}" puts "投稿数: #{Micropost.where(user_id: user.id).count}" end endWorkflow追加
rake.dig+task: sh>: bundle exec rake task_post:count[test4]実行
testユーザーのID,名前、投稿数が出力されています。
実行結果$ digdag run rake.dig --rerun 2020-07-20 20:04:16 +0900 [INFO] (0017@[0:default]+rake+task): sh>: bundle exec rake task_post:count[test] ID: 5 名前: test 投稿数: 4
- 投稿日:2020-07-20T20:26:22+09:00
Windows10 Home にDocker をインストールする方法
Windows10 HomeにDockerを使用
Windows10 Proを買えば、Docker Desktop for Windowsをインストールするだけで済むことを。。。
- PC購入時に予算節約するため、
- Windows10 Home と Proの違いが分からないので、"ま、Homeで良いよ"
と思って、
Windows10 Homeを使用してDocker Desktop for Windowsインストールできず、困っている方、
Windows10 HomeにDockerインストールで無駄な時間を費やしている方
のために、身をもって経験したWindows10 HomeにDockerをインストールする方法をまとめましたので、共有します。環境の整理
PCスペック:
- Windowsのエディション:Windows10 Home
- バージョン:2004 (初期状態1909でしたが、Windowsアップデートしたことで、2004となりました。)
- システムの種類:64ビットオペレーティングシステム、x64ベースプロセッサ
手順
VirtualBox をダウンロードする。
VirtualBoxインストール
「Windows hosts」をクリック、VirtualBox-6.1.12-139181-Win.exeをダウンロードDockerToolbox をダウンロードする。
DockerToolboxインストール
「DockerToolbox-19.03.1.exe」をクリック、DockerToolbox-19.03.1.exeをダウンロードVirtualBoxからインストールする。
*DockerToolboxからインストールすると、DockerToolboxインストールファイルがPCにVirtualBoxがないことを自動検出し、強制チェック入った状態(=外すことができない状態)でDockerToolboxインストールファイルからVirtualBoxをインストールさせてしまう様となる。これじゃ、うまく動作しなかった。VirtualBoxから先にインストールする手順を踏もう。)
DockerToolbox をインストールする。
Docker Quickstart Terminalをダブルクリックする。
*ここでError creating machine...E_FAIL(0x80004005)エラーとなる。落着きPCを再起動する。Docker Quickstart Terminalを再度ダブルクリックする。
*ここでWaiting for an IP...で起動できそうだが、30分、1時間、半日過ぎても進展なかった。
*解決方法はPCの「Windowsの機能」を起動し「仮想マシンプラットフォーム」にチェックが入っていた。これを外す。PCを再起動する。Docker Quickstart Terminalを再度ダブルクリックする。
クジラが現れた!!!成功。
念のため、
docker version
コマンドを打ち込んでdockerコマンドが効くか確認しましょう。
- 投稿日:2020-07-20T18:26:30+09:00
Tailwind on Rails
なぜTailwind on Rails?
- クラス名を決める必要がなくなる
- クラス名の衝突がなくなり、BEMやCSS設計から開放される
- デザインの修正により不要になったCSSが残ってしまうことがなくなる
- どの要素にどんなスタイルが当たっているかがすぐにわかる
- カラーコードやフォントサイズ、ブレイクポイント等の統一性を保ちやすい
- ネット上に転がっているサンプルコードを気軽に取り入れやすい(他の人が書いたコードでもカスタマイズが楽)
- スタイルの修正のたびに
app/assets/stylesheets/任意のフォルダ/任意のファイル
を開く必要がなくなる環境
Rails 6.0.3
導入
$ yarn add tailwindcss $ yarn tailwindcss init $ mkdir app/javascript/css $ touch app/javascript/css/tailwind.cssapp/javascript/css/tailwind.css@import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities";app/javascript/packs/application.jsimport '../css/tailwind.css';postcss.config.jsmodule.exports = { plugins: [ //... require("tailwindcss"), //追加 require("autoprefixer"), //追加 require("postcss-preset-env")({ autoprefixer: { flexbox: "no-2009", }, stage: 3, }), ], };動作確認
$ rails g controller test indexconfig/routes.rbroot to: 'tests#index'app/views/tests/index.html.erb<div class="max-w-sm mx-auto bg-white shadow-lg rounded-lg overflow-hidden"> <div class="sm:flex sm:items-center px-6 py-4"> <img class="block mx-auto sm:mx-0 sm:flex-shrink-0 h-16 sm:h-24 rounded-full" src="https://randomuser.me/api/portraits/women/17.jpg" alt="Woman's Face"> <div class="mt-4 sm:mt-0 sm:ml-4 text-center sm:text-left"> <p class="text-xl leading-tight">Erin Lindford</p> <p class="text-sm leading-tight text-gray-600">Customer Support Specialist</p> <div class="mt-4"> <button class="text-purple-500 hover:text-white hover:bg-purple-500 border border-purple-500 text-xs font-semibold rounded-full px-4 py-1 leading-normal">Message</button> </div> </div> </div> </div>applyを使う
Tailwind CSSにはapplyという機能があり、複数のクラスをまとめて適用することができます。例えば同じボタンがあらゆる箇所に出現する場合、毎回
font-bold py-2 px-4 rounded bg-red-500 text-white hover:bg-red-700
などと書くのは大変なので、btn
というクラスを指定するだけで上記のクラスを適用するためにapplyを使います。TailwindをRailsで利用する場合applyを使用するのが難しいので、helper関数で対応することにします。
(こちらの記事ではRailsでapplyを使っているようですが、この通り設定するとapplyを適用した箇所以外のスタイルが効かなくなってしまいました)
$ rails g helper tailwind
app/helpers/tailwind_helper.rbmodule TailwindHelper def btn 'fosnt-bold py-2 px-4 rounded bg-red-500 text-white hover:bg-red-700' end endapp/controllers/application_controller.rbclass ApplicationController < ActionController::Base helper TailwindHelper endapp/views/tests/index.html.erb<a href='#' class='<%= btn %>'>ボタン</btn>懸念点
TailwindCSSでは、想定されうるあらゆるユーティリティークラスが用意されているので、他のCSSフレームワークよりファイルサイズが大きいです。
この問題を、PurgeCSSという機能を使いビルド時に実際に使われているクラスに関するスタイルだけを抽出する方法で解決しています。しかしRails上でTailwindを使う場合、PurgeCSSが使えません。(正確には設定方法がわかりません。分かる方がいたら教えてください。)
そのため通常よりファイルサイズが大きくなってしまいます。当初この点を懸念して、Tailwind on Railsは無理ではないかと考えていました。
Tailwindの公式サイトを確認したところ
Using the default configuration, the development build of Tailwind CSS is 1996kb uncompressed, 144.6kb minified and compressed with Gzip, and 37.kb when compressed with Brotli.
とあり、要はminify&gzip済で
144.6kb
とのこと。Bootstrapが22.1kb
ってことを考えるとまあ重いですが、許容範囲なんじゃないかと思っています。gzipの設定、ブラウザにキャッシュさせる期間の設定、CDNの活用とかをちゃんとやっていればクリティカルではないでしょう。
参考
- 投稿日:2020-07-20T16:13:05+09:00
ビューファイルにbinding.pryを使う方法
- 投稿日:2020-07-20T14:31:54+09:00
AWSでデプロイして放置してたらアクセス拒否された件
初デプロイから2週間、スプリントレビューでメンターさんに動作確認してもらおうとしたら・・・
そんな不思議なトラブルのお話です。
TECH CAMP最終課題カリキュラム中、私はデプロイ担当をしております。
そして迎える初スプリントレビュー、本番環境見せようとしたら・・・
アクセスが拒否されました(確かこんな文面)
スクショ撮っておけばよかった。???
おそらく私含め、チームメンバーもそんな顔してたような気がします。
ハナからできてなかったんならまだしも、一度は成功してたんですから。よくわからないのでインスタンスの再起動やら一度停止、起動を経てもういいやと再デプロイ、すると・・・
ググったところ、NGINXのホーム画面のようで、これが出るということは、
Elastic IP設定するファイルにElastic IPがちゃんと書けてない(誤字ってる)
という記述を発見、ならイージーモード。秒で直せばいい。いやElastic IP合ってるんですけど?
デプロイの勉強会用のQiitaをいくら見ても何が間違ってるのかわからない。
ググってもわからん。チームメンバーもあれこれ助言してくれますが、解決に至らず...
メンターさんへの相談も視野に入れつつ、3度目の正直の再デプロイ。作りかけのほぼ白紙のwebページが見事表示できましたとさ。
ここで仮説。
デプロイ後、一定時間放置してたら、AWS側で「これいらないのかな?」と判断してアクセス弾く説。
大穴の考えとしてはアクセス集中で弾かれるということもありますが、Elastic IPなんざそんな誰も彼も知ってるわけじゃないんだから。この線は薄いと思います。
仮に総当たり攻撃的な感じで打って入ったんだとしても、Basic認証もあるので、一定の硬さはあるはず。体感で思っただけなので、むしろその辺り知ってる人いたら教えてください。
お願いします。
- 投稿日:2020-07-20T14:25:20+09:00
RailsでPostgreSQLのRow Level Security Policyを使ったマルチテナント
Clipkit(クリップキット) というSaaSを作っています。マルチスキーマ方式のマルチテナントシステムなのですが、テナント数が1,000近くなってきて辛さも出てきているので他の方式を検証中です。
LD;TR
PostgreSQLのRow Level Security Policyを利用したマルチテナントの実装を試してみました。
問題なく実装できてうまく動きそうでした。が、結局今回は採用を見送りました。RDBのマルチテナントの手法は一長一短で難しい。
個人的には最初はRLSではなくマルチスキーマ方式で始めれば良いのではないかと思いました。
はじめに
SaaS型のWebサービスでは、顧客ごとの独立したアプリケーションを、1つのシステムに同居させる方式があります。これをマルチテナントといいます。
RDBのマルチテナント方式
まず普通に考えると、テーブルに複数のテナントのデータを混在させる設計を思いつきます。しかしそれだとプログラムにバグがあった場合、他のテナントのデータが見えてしまうなど非常に大きなセキュリティ上の問題を起こしてしまう可能性があります。
なので、絶対に混線が起こらないようにテナントごとにデータをしっかり分離させる方法を考える必要があります。
RDBでマルチテナントを実現するには、ざっくり以下の3つの方法があります。
マルチインスタンス(サイロ)
テナントごとに独立したDBインスタンス(仮想マシンなど)を使用する。独立性が高いがコストや保守性のメリットが小さい。
シングルインスタンス・マルチスキーマ(ブリッジ)
単一のDBインスタンス内にテナントごとのスキーマを用意する。テナントごとに独立したテーブルを持つのでテーブル定義の管理が煩雑。
シングルスキーマ(プール)
単一のスキーマ内のテーブルにすべてのテナントのデータを混在させる。最もリソースの効率が良いがプログラムにバグが入ると他のテナントのデータが混線するなどの大きなリスクがある。
簡単に実現できるのはマルチスキーマ方式
RailsだとApartmentというgemがある。これで全テナントへの一斉マイグレーションなども勝手にやってくれます。
マルチスキーマ方式の欠点
テナントごとにスキーマを分けるということで、テーブルの構造を変更する際には、すべてのスキーマに対して同じようにマイグレーションを実行する必要があります。2〜3秒のマイグレーション処理だったとしても数千以上のテナント数になるとそれなりに厳しくなってくるでしょう。すべてのテナントでマイグレーションが確実に完了できるようにする管理コストも大きくなります。
なのでアクセス制御さえ確実にできれば、シングルスキーマ方式が理想のような気がしてきます。
Row Level Security Policyを利用したシングルスキーマ方式
概要
PostgeeSQL 9.5以降には「行セキュリティポリシー」(Row Level Security Policy :RLS)という機能があります。これはユーザーのロールや実行時パラメータに応じてあらかじめ指定された条件の行以外にはアクセスできないようにする機能です。
設定方法
具体的には次のように設定します。
例)usersテーブルのtenant_idカラムが特定の値のレコード以外は見えないようにしたい。
RLSを設定。(これは実行時パラメータに応じて制御する設定)
ALTER TABLE users ENABLE ROW LEVEL SECURITY; CREATE POLICY user_isolation_policy ON users FOR ALL USING (tenant_id = current_setting('tenant.id')::BIGINT);あとは実行時パラメータを次のように設定すると、以降、tenant_id=999のレコード以外にはアクセスできなくなります。
SET tenant.id = 999;Railsでの実装(案)
テナントを管理するtenantsテーブル(Tenantモデル)を作っておきます。(※ 説明用の例なのでテーブルの定義とかは省略します)
Tenant#switch
メソッドでテナントを切り替えられるように実装します。さらにTenant.current
で現在のテナントを取得できるようにしておくと便利です。class Tenant < ApplicationRecord def switch ActiveRecord::Base.connection.execute("SET tenant.id = #{id}") end def self.current find(ActiveRecord::Base.connection.execute('SHOW tenant.id').getvalue(0, 0)) end end
ApplicationController
のbefore_action
で、リクエストのドメインに応じてテナントが切り替わるようにします。class ApplicationController < ActionController::API before_action :switch_tenant def switch_tenant Tenant.find_by(domain: request.host).switch end end以上で自分のテナントのデータだけに触れるようになります。
ただしデータを追加するときはtenant_idを自分で入れなくてはいけません。これが面倒なので自動的に入るようにModelの基底クラス(
ApplicationRecord
)に実装します。class ApplicationRecord < ActiveRecord::Base self.abstract_class = true after_initialize :set_tenant_id def set_tenant_id if new_record? if has_attribute?(:tenant_id) self.tenant_id = Tenant.current.id end end end endこれでほぼテナントを意識せず透過的にデータアクセスできるようになりました。
注意点
RLSは一般ユーザーにしか効かない
CREATE TABLEしたユーザーやSUPERUSERに対してはRLSの制限は無効となります。なので、migrationはSUPERUSERで実行、アプリは一般ユーザーで起動。などとする必要があります。
一般ユーザーには次のように必要な権限を与えておきましょう。
GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA public TO PUBLIC; GRANT SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO PUBLIC;UNIQUE制約に注意
UNIQUE制約はtenant_idとの複合インデックスにする必要があります。(アプリケーションからは見えないのでバリデーションについては複合条件にする必要はない)
INSERT時の注意
SELECTは透過的に制約されたレコードしか見えませんが、INSERTするときはtenant_idを自分でセットする必要があります。(前述の実装案ではModelの基底クラス
ApplicationRecord
を使って自動的に入るようにした)マイグレーションの注意
テーブルを追加するときに必要になるCREATE POLICYは、マイグレーションでやりたくなりますが、その場合schema.rbに反映されないので、
db:reset
/db:setup
は使えません。(db:migrate:reset
はok)デメリット
今回は実際にこの実装で運用したわけではないのですが、考えられるデメリットを上げてみます。
テーブルが肥大化する
RDBはレコード数が膨大になると取り扱いが大変になります。インデックスを設定していてもメモリに乗らず急激に重くなったり。
そこでパーティショニング(テーブル分割)機能の利用を検討することになります。カラムの値に応じて分割するリストパーティションという方法があるので、それを使うことになるでしょう。
tenant_idごとにテーブル分割する。という戦略を最初に思いつきますが、一般的にパーティショニングで100を超えるような子テーブルを作るのは想定されていないようで、パフォーマンスに問題がでるという報告も見られます(実際に試してはいませんが)。このアプローチはあまり現実的ではなさそうです。
データが増えたら臨機応変に手動で分割していく、といった戦略になりそう。めんどくさいですね。
テナントの削除が面倒
すべてのテーブルのレコードを消して回らないといけないので大変そう。マルチスキーマ方式の場合はスキーマを削除するだけなので簡単でした。
他の環境からのデータ移行が難しい
SaaS型のサービスだけどオンプレミスでも提供する。といった場合、オンプレミスからSaaSへのデータ移行が必要になったときに大変そう。各テーブルのidが変わってしまうためです。マルチスキーマ方式の場合はダンプ&リストアするだけで済みました。
RLSは見送ることにした
やはりテーブルの肥大化がつらそう。という懸念が払拭できませんでした。
マルチスキーマ方式でマイグレーションがつらい、というのはデプロイのときだけの問題であり、日常的にパフォーマンスを気にするよりずっとマシな気がします。
そのマイグレーションも数百程度のテナント数ならほとんど問題はないので、テナント数の想定にもよりますがスタートアップ段階ではマルチスキーマ方式でも良いのではないか? と思いました。
Apartmentはテナントに応じてDBサーバを変えられる機能などもあり、性能に関してはこちらのほうが安心感が大きいです。
他のソリューション
Citus
https://www.citusdata.com/マルチテナントをいい感じに実現してくれるPostgreSQLの拡張機能。
OSSなのでEC2にはインストールできますが、RDSでは使えないですね……
2016年〜 にAWS上でマネージドサービスを提供するCitus Cloudというサービスがあったようです。
ところが、2019年にMicrosoftがCitusを買収。Citus Cloudは終了。代わりにAzureで利用できるようなったようです。あ"〜
AWSはマストなので厳しい……
Apartmentの開発が停滞してる
よし、やっぱりこれからもApartmentで行くぞ! と思って新しいプロジェクト(Rails 6)で使おうとしたら動かなくてあれれとなりました。
結構メジャーなGemだと思うのですが、なんと今時点(2020年7月)でまだRails 6に対応していないのでした。
活発にメンテされているFork版があったのでとりあえずこちらを使えば大丈夫そうですが。
- 投稿日:2020-07-20T14:11:27+09:00
Deviseでログイン前のルーティングを設定する
やりたいこと
Devise導入した状態で、
ログイン前後のルーティングをカスタマイズする。前提
Deviseをインストールすると、デフォルトではルートパスが/users/sign_inとなっている。
そのため、ログインしていない状態でtopページへ行こうとするとdevise側で勝手にサインインページに飛んでしまう。方法
ログインしていない状態で特定のページへ遷移させたい場合は
before_action :authenticate_user!
を該当コントローラーに記述する必要があります。
全てのアクションについてログイン認証を必要とする場合はapplication_controllerに記述することで
各コントローラーに記述する手間を省けます。before_action :authenticate_user!とは
before_action :authenticate_user!
はdeviseのヘルパーメソッドです。
これを記述することで認証ユーザーのみ各アクションを実行するようになります。特定のアクションのみ、ログインしていない状態で実行したい場合
例えば「topページとaboutページだけはログインしていなくても表示させたい…」という時は
下記のように、該当アクションを除外する形で指定します。
before_action :authenticate_user!, except: [:top, about]
こうすることでtopアクションとaboutアクションのみログアウト状態でも表示することができます。
その他のアクションはログインしていないとURL直打ちしても表示されなくなります。▼参考Devise authentication_user!
https://skillhub.jp/courses/137/lessons/978又、今回私はapplication_controllerに記述すると記載しましたが、
userコントローラー以外の他のコントローラーにログイン認証設定を加えたくない場合は
ディレクトリに階層を作り、そのディレクトリのみ適応されるコントローラーを作成する方法もあります。下記を参考にしてください。
▼参考
https://qiita.com/ryuuuuuuuuuu/items/bf7e2ea18ef29254b3dd
- 投稿日:2020-07-20T12:48:45+09:00
Rubocop Airbnb
はじめに
作成したポートフォリオにRubocop Airbnbを導入したのでメモします。
Rubocopとは
Rubocopは書かれたコードがRubyのコーディング規約に沿った書かれ方をしているかを自動的に確認してくれるgemです。
Rubocopではなく、Rubocop Airbnbを導入する理由
Rubocopはデフォルト設定だと自分で設定を変更する必要があるとのことなので、airbnbの開発で使用されているrubocopの設定をインストールできるRubocop Airbnbを導入することにしました。
Rubocop Airbnbの導入手順
基本的にRubocop Airbnbの手順に従いました。
Gemfileにgemを追加
gemを追加したら、bundle installします。
Gemfilegroup :development do gem 'rubocop-airbnb' endRubocop-airbnbを適用させるアプリファイルに、rubocop.ymlとrubocop-airbnb.ymlを作成し、それぞれ内容を記載
rubocop.ymlの内容は必要に応じて適宜変更してください。
rubocop.ymlinherit_from: - .rubocop_airbnb.yml # Rails用に最適化 Rails: Enabled: true # 文字数の上限を80文字から変更 LineLength: Max: 130 #rubocopで検証したくないフォルダを指定 AllCops: Exclude: - 以下省略rubocop-airbnb.ymlrequire: - rubocop-airbnb実行
設定が完了したら、
bundle exec rubocop --require rubocop-airbnb
以上。
- 投稿日:2020-07-20T11:03:35+09:00
【Rails】docker-composeの速度を上げる方法
開発環境
・Docker: 19.03.8
・Docker-Compose: 1.25.5
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
下記実装済み。
原因
docker-compose実行時に、
temp
、log
、vendor
、.git
等の余計なファイルをマウントしてしまっている。解決方法
マウントする必要のないディレクトリのマウントを、別のvolumeで上書きします。
docker-compose.yml
を編集docker-compose.yml# 変更前 version: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: password web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - db # 変更後 version: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data environment: - POSTGRES_HOST_AUTH_METHOD=trust web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/app:cached - /app/vendor - /app/tmp - /app/log - /app/.git ports: - '3000:3000' depends_on: - db
- 投稿日:2020-07-20T09:50:25+09:00
[herokuデプロイ手順②]新しいアプリの作成からindexアクション表示まで
はじめに
今回は前回の続きでアプリを作成し、indexアクションをローカルで表示するとこまでのアウトプットを行います。
前回の[herokuデプロイ手順①]heroku CLIのインストールからログインまでをまだ読まれていない方はそちらを先に確認して下さい。(下記URLから)
https://qiita.com/nkekisasa222/items/52c872957b30f4e8de1e環境
Rails 5系
macOS catalina 10.15Railsアプリの新規作成
今回はRails5系で新規作成を行って下さい。そちらの方がスムーズに実装できます。
バージョンの確認方法はターミナルにてrails -v
を入力して下さい。必要に応じて次のコマンドで新しいRailsのバージョンを入手して下さい。
ターミナル.$ gem install rails --no-document Successfully installed rails-5.2.2 1 gem installed次に新しいアプリを作成し、作成したアプリのディレクトリに移動します。
ターミナル.$ rails _バージョン指定_ new アプリ名 --database=postgresqlターミナル.$ cd アプリ名続いてローカルにデータベースを作成します。
ターミナル.$ rails db:create Database 'アプリ名_development' already exists Database 'アプリ名_test' already existsgemを追加する
gemファイルを次のように変更して下さい。
ターミナル.gem 'sqlite3' #このgemを gem 'pg' #このgemに変更gemを変更しないで開発を進めるとデプロイ実行の際にエラーが発生します。
開発時にはPostgreSQLを使用することを強くお勧めします。開発環境とデプロイメント環境の間の同等性を維持することで、環境の違いによる微妙なバグの発生を防ぐことができます。システムにPostgresがまだインストールされていない場合は、下記URLからローカルにインストールします。
https://devcenter.heroku.com/articles/heroku-postgresql#local-setupgemを保存するので下記コマンドを入力して下さい。
ターミナル.$ bundle installPostgreSQLアダプターの確認
config/database.yml
ファイルがpostgresqlアダプターを使用していることを確認してください。ターミナル.$ cat config/database.yml # PostgreSQL. Versions 9.1 and up are supported. # # Install the pg driver: # gem install pg # On OS X with Homebrew: # gem install pg -- --with-pg-config=/usr/local/bin/pg_config # On OS X with MacPorts: # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config # On Windows: # gem install pg # Choose the win32 build. # Install PostgreSQL and put its /bin directory on your path. # # Configure Using Gemfile # gem 'pg' # default: &default adapter: postgresql encoding: unicode # For details on connection pooling, see Rails configuration guide # http://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> development: <<: *default database: myapp_development # The specified database role being used to connect to postgres. # To create additional roles in postgres see `$ createuser --help`. # When left blank, postgres will use the default role. This is # the same name as the operating system user that initialized the database. #username: myapp # The password associated with the postgres role (username). #password: # Connect on a TCP socket. Omitted by default since the client uses a # domain socket that doesn't need configuration. Windows does not have # domain sockets, so uncomment these lines. #host: localhost # The TCP port the server listens on. Defaults to 5432. # If your server runs on a different port number, change accordingly. #port: 5432 # Schema search path. The server defaults to $user,public #schema_search_path: myapp,sharedapp,public # Minimum log levels, in increasing order: # debug5, debug4, debug3, debug2, debug1, # log, notice, warning, error, fatal, and panic # Defaults to warning. #min_messages: notice # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default database: myapp_test # As with config/secrets.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is # ever seen by anyone, they now have access to your database. # # Instead, provide the password as a unix environment variable when you boot # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database # for a full rundown on how to provide these environment variables in a # production deployment. # # On Heroku and other platform providers, you may have a full connection URL # available as an environment variable. For example: # # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" # # You can use this database configuration with: # # production: # url: <%= ENV['DATABASE_URL'] %> # production: <<: *default database: myapp_production username: myapp password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>indexアクションの作成
indexアクションを作成していない場合ローカル環境ででエラーが発生するので作成して下さい。
まずコントローラーを作成します。
ターミナル.$ rails g controller コントローラー名次にindexアクションを設定します。
app/views/コントローラー名/index.html.erb
を編集。index.html.erb<h2>Hello World</h2> <p> The time is now: <%= Time.now %> </p>続いてルーティングの設定で
config/routes.rb
2行目に、以下を追加します。routes.rbroot 'コントローラー名#index'サーバーを立ち上げてページが存在するか確認します。
ターミナル.$ rails s
app/views/コントローラー名/index.html.erb
の内容が表示されれば成功です。最後に
次回はアプリをgitに連携からherokuデプロイまでをアウトプットします。
参考にして下さい!