cocoonで動的に要素を増やす場合のテスト
結論
cocoonで動的に要素を増やす場合はクラスで対応する。
# view slim&simple_form .nested-fields = f.input :upload_file, as: :file, accept: 'image/jpeg,image/png', input_html: { class: 'upload-images' }
# test click_link '画像を追加' # link_to_add_associationに書いた文字列 attach_file(page.all('.upload-images')[0][:id], Rails.root.join('test', 'fixtures', 'files', 'valid_image.jpg'))
capybara間が空くと書き方忘れてしまう...
pumaのproduction用設定
config/puma.rb
# Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } threads min_threads_count, max_threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # # port ENV.fetch("PORT") { 3000 } # ここをコメントアウトなり消すなり bind "unix://#{Rails.root}/tmp/sockets/puma.sock" # 追記 daemonize # 追記 stdout_redirect "#{Rails.root}/log/stdout", "#{Rails.root}/log/stderr" # 追記 以下、省略 ...
実行時
rails s -e production
nodejsのアップデートでハマった
概要
nodejsのアップデートとエラーの対応の方法について
nodejsのアップデート
1.指定のバージョンのダウンロード
distributions/README.md at master · nodesource/distributions · GitHub
curl -sL https://rpm.nodesource.com/setup_12.x | sudo bash -
2.古いバージョンの削除
rm /etc/yum.repos.d/nodesource-el.repo
3.yumのセットアップ
yum clean all
※これをやらないと、インストール時に旧バージョンを見に行って失敗する
4.nodejsの削除
yum remove nodejs
5.nodejsの再インストール
yum install nodejs
yarnの更新
1.yarnの削除
yum remove yarn
2.yarnのインストール
yum install yarn
webpackerの更新
1.webpackerのアップデート(gem)
bundle update webpacker
2.webpackerのアップデート(node_modules)
yarn upgrade @rails/webpacker --latest
3.webpack-dev-serverのアップデート
yarn upgrade webpack-dev-server --latest
トラブルシューティング
1.nodejsのアップデート
yum clean all
これを知らずに、いつまでもnodejsが更新できないという事に...
2.nodejsのアップデートやると、凄まじい数のエラーが出ることがある
yarn install v1.22.5 warning package.json: No license field warning No license field [1/4] Resolving packages... [2/4] Fetching packages... info fsevents@1.2.4: The platform "linux" is incompatible with this module. info "fsevents@1.2.4" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... warning "@rails/webpacker > postcss-cssnext@3.1.0" has unmet peer dependency "caniuse-lite@^1.0.30000697". warning " > webpack-dev-server@2.11.2" has unmet peer dependency "webpack@^2.2.0 || ^3.0.0". warning "webpack-dev-server > webpack-dev-middleware@1.12.2" has unmet peer dependency "webpack@^1.0.0 || ^2.0.0 || ^3.0.0". [4/4] Building fresh packages... [-/2] ⢀ waiting... error /to/app/path/node_modules/node-sass: Command failed. Exit code: 1 Command: node scripts/build.js ... ... ... In file included from ../src/binding.cpp:1:0: ../../nan/nan.h: In instantiation of ‘void Nan::imp::SetMethodAux(T, v8::Local<v8::String>, v8::Local<v8::FunctionTemplate>, ...) [with T = v8::Local<v8::Object>]’: ../../nan/nan.h:2353:57: required from ‘void Nan::SetMethod(HandleType<T>, const char*, Nan::FunctionCallback) [with T = v8::Object; HandleType = v8::Local; Nan::FunctionCallback = void (*)(const Nan::FunctionCallbackInfo<v8::Value>&)]’ ../src/binding.cpp:351:42: required from here ../../nan/nan.h:2337:3: warning: ‘bool v8::Object::Set(v8::Local<v8::Value>, v8::Local<v8::Value>)’ is deprecated (declared at /home/user_name/.node-gyp/12.18.4/include/node/v8.h:3498): Use maybe version [-Wdeprecated-declarations] recv->Set(name, GetFunction(tpl).ToLocalChecked()); ^ make: *** [Release/obj.target/binding/src/binding.o] エラー 1 make: ディレクトリ `/to/app/path/node_modules/node-sass/build' から出ます gyp ERR! build error gyp ERR! stack Error: `make` failed with exit code: 2 gyp ERR! stack at ChildProcess.onExit (/to/app/path/node_modules/node-gyp/lib/build.js:262:23) gyp ERR! stack at ChildProcess.emit (events.js:315:20) gyp ERR! stack at Process.ChildProcess._handle.onexit (internal/child_process.js:275:12) gyp ERR! System Linux 4.14.33-51.37.amzn1.x86_64 gyp ERR! command "/usr/bin/node" "/to/app/path/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library=" gyp ERR! cwd /to/app/path/node_modules/node-sass gyp ERR! node -v v12.18.4 gyp ERR! node-gyp -v v3.7.0 gyp ERR! not ok Build failed with error code: 1
こういうときは
yarn why node-sass
を使うと、何に依存してるかとかが分かるので、これを元にアタリをつけると良い。
yarn why v1.22.5 warning package.json: No license field [1/4] Why do we have the module "node-sass"...? [2/4] Initialising dependency graph... warning No license field [3/4] Finding dependency... [4/4] Calculating file sizes... => Found "node-sass@4.9.2" info Reasons this module exists - "@rails#webpacker" depends on it - Hoisted from "@rails#webpacker#node-sass" info Disk size without dependencies: "11.64MB" info Disk size with unique dependencies: "16.41MB" info Disk size with transitive dependencies: "11.64MB" info Number of shared dependencies: 115 Done in 1.67s.
github actionsにredisを追加する
概要
actionsにredisを追加してActiveJobのCIに対応する。
結論
docs.github.com ここに書いてありますね。
で、Rails用のactionsに置き換えると下記のような感じかなと。
name: Build on: push: branches: - master - development pull_request: branches: - master - development jobs: run_test: name: Run test runs-on: ubuntu-latest services: postgres: image: postgres:11 ports: - 5432:5432 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 redis: image: redis ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 env: REDIS_HOST: redis REDIS_PORT: 6379 steps: - uses: actions/checkout@v1 - name: Set up Ruby 2.6 uses: ruby/setup-ruby@v1 with: ruby-version: 2.6.6 - name: install bundler2 run: gem install bundler - name: Install dependent libralies run: sudo apt-get install libpq-dev graphviz - name: Bundle install run: bundle install --jobs 4 --retry 3 - name: Yarn install run: yarn install --check-files - name: Setup Database run: | cp config/database.yml.ci config/database.yml bundle exec rake db:create bundle exec rake db:schema:load env: RAILS_ENV: test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - name: Run test run: | COVERAGE=true bundle exec rails test COVERAGE=true bundle exec rails test:system env: LANG: ja_JP.UTF-8 RAILS_ENV: test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - name: Upload uses: actions/upload-artifact@v2 if: failure() with: name: my-uploads path: tmp/screenshots/
ec2にredisを入れる
概要
ActiveJobを使うために必要だったのでとりあえず雑に書き留め。
手順
1.リポジトリの追加
sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
2.インストール
sudo yum --enablerepo=remi install redis
3.起動
sudo service redis start
postgresqlに依存関係が増えた関係でlinux2にインストールのにハマった
postgresql 11.8をインストールしようとすると llvm-toolset-7-clang >= 4.0.1 の依存でこけるようになりました。
調べてみるとllvm-toolset-7-clangはcentos-sclリポジトリにあるとのことでした。
https://centos.pkgs.org/7/centos-sclo-rh-x86_64/llvm-toolset-7-clang-5.0.1-4.el7.x86_64.rpm.html
yum install centos-release-scl-rh
で追加できるって言うんだけどできないんですよね...。amazon linux2 が原因か。
なので手動でこれをリポジトリに追加していく必要があったんだけど、久しぶりにこの手の作業をしたのでめちゃくちゃハマった。
まず、yum-config-manager でリポジトリを追加
sudo yum-config-manager --add-repo http://mirror.centos.org/centos/7/sclo/x86_64/rh/
これでいけるやろと思って
sudo yum install -y postgresql11 postgresql11-server postgresql11-contrib postgresql11-devel
を実行すると
llvm-toolset-7-clang-libs-5.0.1-4.el7.x86_64.rpm の公開鍵がインストールされていません
と出て途中で終了します。
なので設定を修正
sudo vi /etc/yum.repos.d/リポジトリのファイル名(デフォルトだとadd-repoしたURLだと思われる)
で内容をいかに書き換える
[centos-sclo-rh] name=centos-sclo-rh baseurl=http://mirror.centos.org/centos/7/sclo/x86_64/rh/ enabled=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo
gpgkeyの設定が重要でした。
ファイルがない場合は以下の内容をコピーしてtouchコマンド等でファイルを作ったら良いかと思います。 github.com
sudo yum install -y postgresql11 postgresql11-server postgresql11-contrib postgresql11-devel
これでインストールが通るようになりました。
【Rails】PostgreSQLのバイナリ型で画像を扱う
経緯
- 画像のアップロード機能を追加したい
- アップロードしたデータはアップロードしたユーザー以外にみられたくない
- S3などの外部ストレージに保存したくない
これらの条件を考えた時に、バイナリ型でDBに突っ込むのが一番手取り早いと思いこの実装にいたる。
また、アップローダー系のGemは外部ストレージに送る場合でない限りさほどメリットがなさそうだったので、バリデーションも自前で作る。
環境
- Rails 6.0.2.2
- Ruby 2.6.6
- PostgreSQL 9.6.15
概要
実装
1.マイグレーションファイルの作成
rails g model Image user:references data:binary content_type:string uploaded_at:datetime
# db/migrate/xxxxxxxxxxxxxxxx_create_images.rb class CreateImages < ActiveRecord::Migration[6.0] def change create_table :images do |t| t.references :user, null: false, foreign_key: true, type: :uuid t.binary :data, null: false, limit: 10.megabyte, comment: 'データ' t.string :content_type, null: false, comment: 'ContentType' t.datetime :uploaded_at, default: -> { 'CURRENT_TIMESTAMP' }, null: false, comment: 'アップロード日' t.timestamps end end end
※binaryのデフォルトのファイルサイズが64kbなので、limitでファイルサイズの設定をする必要あり。
2.マイグレーション実行
rails db:migrate
3.テストファイルの追加
# test/models/image_test.rb require 'test_helper' class ImageTest < ActiveSupport::TestCase include ActionDispatch::TestProcess test 'when upload_file size is over 10MB, it is invalid' do file = fixture_file_upload('files/invalid_image_1.jpg', 'image/jpeg') image = Image.new(upload_file: file) image.valid? assert_includes image.errors[:data], 'のファイルサイズは10MB以下にしてください' end test 'when upload_file content_type is not image/png or image/jpeg, it is invalid' do file = fixture_file_upload('files/invalid_image_2.pdf', 'application/pdf') image = Image.new(upload_file: file) image.valid? assert_includes image.errors[:content_type], 'はファイルの種類に問題があります' end end
ActionDispatch::TestProcessをincludeすることでfixture_file_uploadを使えるようになる。
これでActionDispatch::Http::UploadedFileを(フォームからファイルアップロードした際に作られるオブジェクト)を擬似的に扱う。
4.バリデーション追加
# app/validators/content_type_validator.rb class ContentTypeValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) return if value.nil? return if options[:allow_content_type].include?(value) record.errors.add(attribute, 'はファイルの種類に問題があります') end end
# app/validators/size_limit_validator.rb class SizeLimitValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) return if value.nil? return if options[:limit] >= value.size record.errors.add(attribute, "のファイルサイズは#{convert_size(options[:limit], value)}以下にしてください") end def convert_size(limit_size, value) return convert_kb(limit_size) if 100.kilobyte >= value.size return convert_mb(limit_size) if 100.megabyte >= value.size convert_gb(limit_size) end def convert_kb(limit_size) "#{(limit_size / (2 ** 10).to_f).round}KB" end def convert_mb(limit_size) "#{(limit_size / (2 ** 20).to_f).round}MB" end def convert_gb(limit_size) "#{(byte_size / (2 ** 30).to_f).round}GB" end end
ContentTypeとファイルサイズのカスタムバリデータを作成する。
モデルに書くよりもカスタムバリデータとして個別に実装した方が見やすいと思うので、こちらに実装。
5.モデルの実装
# app/models/Image.rb class Image < ApplicationRecord ALLOW_CONTENT_TYPE = ['image/jpeg', 'image/png'] belongs_to :user attr_accessor :upload_file before_validation -> { self.data = upload_file.read }, if: -> { upload_file } before_validation -> { self.content_type = upload_file.content_type }, if: -> { upload_file } validates :data, presence: true, size_limit: { limit: 10.megabyte } validates :content_type, presence: true, content_type: { allow_content_type: ALLOW_CONTENT_TYPE } end
6.テスト実行
$ rails test test/model/image_test.rb Running via Spring preloader in process 57608 Run options: --seed 3934 # Running: .. Finished in 0.689962s, 2.8987 runs/s, 5.7974 assertions/s. 2 runs, 4 assertions, 0 failures, 0 errors, 0 skips
7.コントローラーの作成
class ImagesController < ApplicationController def new @image = Image.new end def create @image = Image.new(image_params) if @image.save # 省略 else # 省略 end end private def image_params params.require(:image).permit(:upload_file) end end
8.ビューの作成(フォーム)
<%= form_with model: @image do |f| %> <%= f.label :upload_file %> <%= f.file_field :upload_file %> <%= f.submit %> <% end %>
9.画像表示用のメソッド追加
# app/models/Image.rb class Image < ApplicationRecord ALLOW_CONTENT_TYPE = ['image/jpeg', 'image/png'] belongs_to :user attr_accessor :upload_file before_validation -> { self.data = upload_file.read }, if: -> { upload_file } before_validation -> { self.content_type = upload_file.content_type }, if: -> { upload_file } validates :data, presence: true, size_limit: { limit: 10.megabyte } validates :content_type, presence: true, content_type: { allow_content_type: ALLOW_CONTENT_TYPE } # 追加 def image_url "data:#{content_type};base64,#{Base64.encode64(image)}" end end
画像のインライン用のフォーマットを作成する。
バイナリデータをBase64エンコードすることで作成可能。
10.ビューの作成(画像表示)
<%= image_tag @image.image_url %>
おまけ fixtures
# test/fixtures/images.yml image_1: user: user_1 image: !binary <%= Base64.strict_encode64(File.binread(Rails.root.join('test', 'fixtures', 'files', 'valid_image_1.jpg').to_s)) %> content_type: image/jpeg uploaded_at: 2020-05-25 15:07:23
バイナリデータ突っ込むの地味に苦戦した...
後書き
DBにバイナリデータを保存する形での画像アップロードの実装方法についてでした。
send_dataで画像のエンドポイントを作るパターンもありましたが、手間だったのでdata URIを直接メソッドで実装してしみました。
インライン画像にする場合のメリットとしては
- httpリクエストが減る
デメリットとしては
- データ容量が増える(約40%増)
- キャッシュされない
あたりがあります。
今回はデータへのアクセス制限をRails内で完結したいということでパフォーマンス目的での実装じゃないのでその辺りはあまり考慮していません。
ただ、ライブラリ等無しでも案外簡単にやれたので、さすがRailsという感じ。