- 投稿日:2019-12-23T22:25:30+09:00
【Rails 6】(初心者向け)Ajax版最小構成CRUDアプリ(ページ移動をゼロに!)
Railsの学習で最初に学ぶのが,メッセージの作成・表示(読み取り)・更新・削除のできるCRUDアプリであると思います。私も最初にCRUDアプリを作成したときは,正直理解が追いつきませんでした
ところが,実際に作成してみるといろいろと不満が出てくることでしょう。一番は「見た目」だと思いますが,
「ボタンをクリックするたびにページを移動するのは嫌だなあ……」
と思いませんでしたか?特に本番環境では読み込みに時間がかかってしまいます。そこで,この記事では,ページ遷移ゼロの最小構成CRUDアプリを作成していきたいと思います
通常のCRUDアプリを理解していたならば,本記事も理解できるようなるべく丁寧に解説していきます。
この記事では,Rails標準の Ajax の使い方を学ぶことだけに焦点を当てます。そのため,見た目・バリデーション・例外処理など細かいことは全て削ります。なお, jQuery の使用は避けて, Javascript を使用することとします。
完成後のアプリ
開発環境
- macOS Catalina 10.15.2
- Ruby 2.6.4
- Rails 6.0.2
手順
0. 準備
- まずはアプリを作成し,最低限の準備をしていきます。
Heroku
へのデプロイまで挑戦されたい場合は,rails new
のところで-d postgresql
オプションを付け,$ rails db:create
も実行して下さい。ターミナル$ rails new ajax_crud_sample $ cd ajax_crud_sample $ rails g controller messages index $ rails g model Message content:string $ rails db:migrate
念のため,
$ rails s
でサーバーを起動し,http://localhost:3000
にアクセスし,「Yay! You’re on Rails!」を確認しておいて下さい。CRUDアプリを作成するための,ルーティングを設定しておきます。
config/routes.rbRails.application.routes.draw do - get 'messages/index' + root 'messages#index' + resources :messages end
- 一覧ページに投稿メッセージが表示されるようにします。
app/controllers/messages_controller.rbclass MessagesController < ApplicationController def index @messages = Message.all end endapp/views/messages/index.html.erb<!-- 各メッセージに対し,部分テンプレート _message.html.erb を呼び出す --> <%= render @messages %>app/views/messages/_message.html.erb<p> <%= message.content %> </p>
- 確認用として,適当なメッセージをデータベースに投入します
db/seeds.rbmessages = %w[おはよう こんにちは こんばんは] messages.each do |message| Message.create!(content: message) end puts '初期データの保存に成功しました!'ターミナル$ rails db:seed
- トップページ(
http://localhost:3000
)にアクセスし,次のように表示されたならばOKです!1.メッセージの作成
準備ができましたので,まずはメッセージを投稿し,ページ遷移無しでそのメッセージを追加表示できるようにしましょう!
- 最初にフォームを作成します。
app/views/messages/index.html.erb<!-- 部分テンプレート _form.html.erb を呼び出す --> <%= render 'form' %> <hr> <!-- 各メッセージに対し, _message.html.erb を呼び出す --> <%= render @messages %>app/views/messages/_form.html.erb<!-- 新規メッセージ投稿用のフォーム --> <%= form_with model: Message.new do |f| %> <%= f.text_field :content %> <%= f.submit "投稿" %> <% end %>
- ここでトップページを確認してみて下さい。次のように表示されていればOKです!
もちろん,このままでは「投稿ボタン」を押しても,何も起こりません。対応する
create
アクションでメッセージの保存を指示します。app/controllers/messages_controller.rb# (略) # ********** 以下を追加 ********** def create Message.create!(message_params) end private # Strong Parameters はサボらずに使っておくこととします def message_params params.require(:message).permit(:content) end # ********** 以上を追加 ********** endこの時点で,フォームに入力して投稿してみて下さい。ページ内には変化は起きませんが,SQLが発行されていることをターミナルから確認できます。ページを更新してみて下さい。先ほど投稿したメッセージが反映されているはずです!
ここで,通常のCRUDアプリならば,例えば次のように書くでしょう。
app/controllers/messages_controller.rbdef create Message.create!(message_params) # 実際に次の一行を追加して確認してもよいですが,その後削除して下さい! redirect_to root_path endこのようにすれば,新規メッセージを投稿 するだけでなく ページ更新 まで行われますので,一応,新規投稿機能ができたことになります。
ところが,今回は ページ遷移無し で投稿メッセージを追加表示させたいので,これではダメですここで,トップページのソースを確認してみます。
<form action="/messages" accept-charset="UTF-8" data-remote="true" method="post"> <!-- (略) --> </form>フォームの箇所に, data-remote="true" が入っているはずです。
form_with
でフォームを作成した場合は,local: true
オプションを付けない限り,自動的にこのデータ属性が追加されます。この data-remote="true" が 含まれていない 場合は,コントローラの
create
メソッドで指示をしない限り, create.html.erb が呼び出されます。create.html.erb を自動で呼び出すにはlocal: true
オプションを付けなければなりません。
(なお,form_for
やform_tag
の使用は 現在推奨されておりません)では, data-remote="true" が 含まれている 場合はどうなるのでしょうか。実は,自動的に Ajax を利用して非同期通信(ページ遷移無しに通信)が行われ,
create
メソッドが実行されます。更に,指示がない場合は create.js.erb が呼び出されるようになるのです。つまり,ページ遷移は起こらず,この create.js.erb に書いた処理が実行されるようになるのです!Javascript を使うことで,ページの一部分を変更することができます。そこで,
- 投稿したメッセージを,メッセージ一覧の一番下に追加する
というプログラムを書くことで, ページ遷移無し に新規メッセージを追加表示できるのです!具体的には次のように書きます。
app/views/messages/index.html.erb<%= render 'form' %> <hr> <!-- メッセージ一覧を div タグで囲み,idを付けておく --> <div id="messages"> <%= render @messages %> </div>app/controllers/messages_controller.rb# 以下を変更(新規メッセージを create.js.erb で使えるようにインスタンス変数に入れておく) def create @message = Message.create!(message_params) end
- 次の
create.js.erb
を作成します。app/views/messages/create.js.erb// メッセージ一覧の一番下に新規メッセージを追加する document.getElementById('messages').insertAdjacentHTML('beforeend', '<%= j(render @message) %>')Javascriptに慣れていない方もいらっしゃると思いますので,解説を入れます。
document.getElementById('messages')
は,messages
というidが付いている要素を取得する操作です。今回のケースならば,次が取得されます。document.getElementById('messages')<div id="messages"> <p> おはよう </p> <p> こんにちは </p> <p> こんばんは </p> </div>ここの
</div>
の前に新規メッセージを追加したいので,続けて次を書くことになります。.insertAdjacentHTML('beforeend', //新規メッセージ// )「新規メッセージ」の箇所は,例えば
'<%= @message.content %>'
としてもメッセージが追加されるのですが,これではダメです!
'<%= @message.content %>'
は ただの文字列 です。<p>
タグで囲まれていませんので,2回投稿すると前のメッセージと繋がってしまいます!そもそも,単に文字列を追加したいのではなく,
_message.html.erb
に書いたテンプレートで新規メッセージを追加したいわけです。そこで,j(render @message)
と書くことになります。「 j とはなんぞや?」と思われたかもしれません。これは, escape_javascript メソッドです。
_message.html.erb
内の改行\n
をエスケープしないとJavascriptの構文エラーが発生します。さて,メッセージを投稿すれば,ただデータベースに保存されるだけでなく,新規メッセージが一番下に追加表示されるようになりましたが,もう一つ問題があります。投稿したのに, フォームの文字が残ったまま になっています!
ページを更新していないので,指示をしなければ当然フォームの文字も残ったままになります。そこで,
create.js.erb
に次を追加して下さい。app/views/messages/create.js.erb// (略) // フォームの文字を空にする document.getElementById('message_content').value = ''実は,フォームのテキストフィールドに
id="message_content"
が自動で付いています。これを取得し,値を空にする指示を出せばOKです。ここの id はハイフン
ではなくアンダーバー
ですのでご注意下さい。2. メッセージの削除
次にメッセージの削除機能を付けます
- まずは削除用のリンクを作成します。
app/views/messages/_message.html.erb<!-- idを追加 --> <p id="message-<%= message.id %>"> <%= message.content %> <!-- 削除のリンクを追加 --> <%= link_to '削除', message_path(message), method: :delete, data: { confirm: '削除しますか?', remote: true } %> </p>
- コントローラに次を追加します
app/controllers/messages_controller.rb# idからメッセージを取り出す操作は他でも必要となるので最初からまとめておきます before_action :set_message, only: %i[destroy] # (略) def destroy @message.destroy! end private # (略) def set_message @message = Message.find(params[:id]) end普通のCRUDアプリとの違いは,まず,
data
属性にremote: true
を入れていることです。これで,コントローラのdestroy
メソッドの後にページ遷移が起こらず, destroy.html.erb ではなく destroy.js.erb が動作するようになります。また,削除するメッセージを特定できるようにするため,各メッセージに id を付けておきます。
この時点でデータベースからメッセージの削除はできます。ところが,何も指示しなければページを更新しない限り,ページ内からメッセージが消えません。そこで,
destroy.js.erb
にメッセージを削除するプログラムを書きます。app/views/messages/destroy.js.erbdocument.getElementById('message-<%= @message.id %>').outerHTML = ''ここも解説を入れておきます。削除する
@message.id
が2
であるとすると,document.getElementById('message-<%= @message.id %>')により,次が取り出されます。
<p id="message-2"> こんにちは <a data-confirm="削除しますか?" data-remote="true" rel="nofollow" data-method="delete" href="/messages/2">削除</a> </p>全てを消去したいので,
outerHTML
を空にするように指示します。これでページ遷移無しにメッセージが消えるようになりました!3. メッセージの更新
メッセージの更新は少々大変です。普通のCRUDアプリならば,更新ボタンを押した後「更新用のページ」に移動させます。今回はページ遷移無しに更新部分を実装したいので,まずは更新ボタンを押した時に「更新用のフォーム」を表示できるようにしなければなりません。
なるべく簡単にしたいと思いますので,この記事では「新規メッセージの投稿フォーム」を「更新用のフォーム」に置き換えることにします。
- まずは,更新フォームを呼び出すリンクを作成します。
app/views/messages/_message.html.erb<p id="message-<%= message.id %>"> <%= message.content %> <!-- ********** 以下を追加 ********** --> <%= link_to '更新', edit_message_path(message), data: { remote: true } %> <!-- ********** 以上を追加 ********** --> <%= link_to '削除', message_path(message), method: :delete, data: { confirm: '削除しますか?', remote: true } %> </p>
- フォームを更新用としても使えるように,フォームの部分テンプレートを修正しておきます。
app/views/messages/_form.html.erb<!-- Message.new を 変数 message に変更 --> <%= form_with model: message do |f| %> <%= f.text_field :content %> <!-- ボタンの文字を 変数 value に変更 --> <%= f.submit value %> <% end %>app/views/messages/index.html.erb<!-- 新規投稿メッセージなので,ボタンの文字は「投稿」とする --> <%= render 'form', message: Message.new, value: '投稿' %> <hr> <div id="messages"> <%= render @messages %> </div>この時点で次のような表示になります。
- コントローラに
edit
,update
を追加します。app/controllers/messages_controller.rb# edit, update でも呼び出すようにする before_action :message_set, only: %i[destroy edit update] # 更新用のフォームに置き換えることだけに使用する def edit end def update @message.update!(message_params) end更新のリンクをクリックしたときに動作するのは edit.js.erb です。ここに,「更新用のフォーム」に置き換える操作を書きましょう。
app/views/messages/edit.js.erb// フォームは一つしか無いので, id を使わず,タグ名から要素を取得 // ここの変数宣言に let や const を使用すると2回目にエラーが発生します var form = document.getElementsByTagName('form')[0] // フォームを更新用フォームに置き換えます。ボタンの文字は「更新」にします。 form.outerHTML = '<%= j(render 'form', message: @message, value: '更新') %>'例えば,2番目のメッセージにある更新ボタンをクリックすると,次のような表示に変わります。
コントローラにデータベースを更新する操作はすでに入れています。あとは更新用フォームの「更新」ボタンを押したときに,ページ内のメッセージを更新するようにします。
app/views/messages/update.js.erb// 対応するメッセージを更新 document.getElementById('message-<%= @message.id %>').innerHTML = '<%= j(render @message) %>' // フォームを新規投稿フォームに戻す var form = document.getElementsByTagName('form')[0] form.outerHTML = '<%= j(render 'form', message: Message.new, value: '投稿') %>'これで,メッセージの更新も ページ遷移無し で実現できるようになりました
4. おまけ
最後に,フォームを「更新用」に変更したあと,「新規投稿用」に戻すリンクを作っておきます。見た目がいまいちですが,本筋から外れますので許して下さい
- 更新用フォームにだけ「取消」のリンクを追加
app/views/messages/_form.html.erb<%= form_with model: message do |f| %> <%= f.text_field :content %> <%= f.submit value %> <!-- ********** 以下を追加 ********** --> <% if value == '更新' %> <%= link_to '取消', new_message_path, data: { remote: true } %> <% end %> <!-- ********** 以上を追加 ********** --> <% end %>
- コントローラに追加
app/controllers/messages_controller.rb# 新規投稿用のフォームに置き換えることだけに使用する def new end
- 「取消」ボタンを押したとき,「新規投稿用フォーム」に戻すようにする
app/views/messages/new.js.erbvar form = document.getElementsByTagName('form')[0] form.outerHTML = '<%= j(render 'form', message: Message.new, value: '投稿') %>'これで,「新規投稿用フォーム」に戻すこともできるようになりました。
今回の記事は Ajax を利用して ページ遷移無し でCRUDアプリを作成することのみに重点をおいて解説をしました。少しでも理解を進めるお手伝いができたならば幸いです。お疲れ様でした
最終的なコード
- GitHubに完成後のコードをアップしております
$ git clone https://github.com/T-Tsujii/ajax_crud_sample.git
- 投稿日:2019-12-23T21:09:47+09:00
.orderで並び替えたデータ取得時の注意点
映画レビューアプリを作成中に気づいたことです!
レビュー投稿画面に移行時に
①Movieテーブル最新データ1つ取得→ fleshmovie = Movie.order(updated_at: :desc).limit(1)
②最新データidカラムの値をインスタンス変数に定義→ @movie = fleshmovie.idreviews_controller.rbdef new fleshmovie = Movie.order(updated_at: :desc).limit(1) @movie = fleshmovie.id enderror_code[2] pry(#<ReviewsController>)> @movie Movie Load (0.6ms) SELECT `movies`.* FROM `movies` ORDER BY `movies`.`updated_at` DESC LIMIT 1 ↳ app/controllers/reviews_controller.rb:10 => [#<Movie:0x00007fb5d5e845d0 id: 37, mtdb_id: "10315", created_at: Mon, 23 Dec 2019 11:08:37 UTC +00:00, updated_at: Mon, 23 Dec 2019 11:08:37 UTC +00:00>] [3] pry(#<ReviewsController>)> @movie.id NoMethodError: undefined method `id' for #<Movie::ActiveRecord_Relation:0x00007fb5d6b6bd98> Did you mean? ids@movieで最新データは取得できているのに、idカラムが参照できない!
rails documentを確認
.orderは並び替えるメソッドなので、@movieは配列を取得している気がする
まず配列の1つめを取得する必要がありそう!fleshmovie.id → fleshmovie[0].idで解決しました!
reviews_controller.rbdef new fleshmovie = Movie.order(updated_at: :desc).limit(1) @movie = fleshmovie[0].id end
- 投稿日:2019-12-23T20:59:14+09:00
CircleCiで、mysqlのDBに接続してテストを通す。ruby on rails
ruby on railsのCircleCiで結構はまったのでメモ
データベースの接続設定
appli/config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: host: localhost test: <<: *default username: 'root' password: 'rootpass' port: 3306 host: '127.0.0.1' database: ci_testcircleci/config.yml# Ruby CircleCI 2.0 configuration file # # Check https://circleci.com/docs/2.0/language-ruby/ for more details # version: 2 jobs: build: docker: # specify the version you desire here - image: circleci/ruby:2.6.5-node-browsers environment: BUNDLER_VERSION: 2.0.1 RAILS_ENV: test DB_HOST: 127.0.0.1 DB_USERNAME: 'root' DB_PASSWORD: 'rootpass' - image: circleci/mysql:5.7.22 environment: MYSQL_DATABASE: 'ci_test' MYSQL_USER: 'root' MYSQL_ROOT_PASSWORD: 'rootpass' # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ # - image: circleci/postgres:9.4 working_directory: ~/repo steps: - checkout - run: name: yarn install command: yarn install - run: name: setup bundler command: | sudo gem update --system sudo gem uninstall bundler sudo rm /usr/local/bin/bundle sudo rm /usr/local/bin/bundler sudo gem install bundler # Download and cache dependencies - restore_cache: keys: - v1-dependencies-{{ checksum "Gemfile.lock" }} # fallback to using the latest cache if no exact match is found - v1-dependencies- - run: name: install dependencies command: | yarn install --check-files bundle install --jobs=4 --retry=3 --path vendor/bundle - save_cache: paths: - ./vendor/bundle key: v1-dependencies-{{ checksum "Gemfile.lock" }} # Database setup - run: name: bundle exec command: | dockerize -wait tcp://127.0.0.1:3306 -timeout 120s bundle exec rake db:create bundle exec rake db:schema:load # run tests! - run: name: run tests command: | mkdir /tmp/test-results TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ circleci tests split --split-by=timings)" sudo gem install bundler sudo gem install rspec sudo gem install rspec-core bundle exec rspec \ --format progress \ --format RspecJunitFormatter \ --out /tmp/test-results/rspec.xml \ --format progress \ $TEST_FILES # collect reports - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-results
- 投稿日:2019-12-23T20:15:15+09:00
【環境構築】Docker + Rails6 + Vue.js + Vuetifyの環境構築手順
はじめに
Docker + Rails6 + Vue.js + Vuetifyの開発環境構築手順をまとめました。
以下の記事を参考にさせて頂きました!ありがとうございます
- webpackerを使ってRuby on Rails 6.0とVue.jsを連携する方法(フロントエンド編)
- Rails+Vue.js+Vuetify環境の構築手順 - Qiita
- 【Rails6】10分でRails + Vue + Vuetifyの環境を構築する - Qiita
環境
OS: macOS Catalina 10.15.1 zsh: 5.7.1 Ruby: 2.6.5 Rails: 6.0.2 Docker: 19.03.5 docker-compose: 1.24.1 Vue: 2.6.10 vue-router: 2.6.10 vuex: 3.1.2 vuetify: 2.1.01.準備
作成するアプリケーション名はhogeappとします。
まずは以下ファイルを作成して下さい。Dockerfile
yarnが必要になるので、Dockerfileに反映しています。
hogeapp/DockerfileFROM ruby:2.6.5 RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs \ vim RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN mkdir /app_name ENV APP_ROOT /app_name WORKDIR $APP_ROOT COPY ./Gemfile $APP_ROOT/Gemfile COPY ./Gemfile.lock $APP_ROOT/Gemfile.lock RUN bundle install COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT [ "entrypoint.sh" ] ADD . $APP_ROOTdocker-compose.yml
hogeapp/docker-compose.ymlversion: "3" services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: root volumes: - db-data:/var/lib/mysql ports: - "3306:3306" web: build: . command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/app_name ports: - "3000:3000" links: - db tty: true stdin_open: true depends_on: - db data: image: busybox volumes: - db-data:/var/lib/mysql tty: true volumes: db-data: driver: localGemfile
hogeapp/Gemfilesource 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem 'rails', '~> 6.0.2', '>= 6.0.2.1'Gemfile.lock
中身は空で作成します。
hogeapp/Gemfile.lockentrypoint.sh
hogeapp/entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /app_name/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"これで必要なファイルが揃ったので、次はRailsアプリの作成です。
2.Railsアプリの作成
rails new
$ docker-compose run web rails new . --force --database=mysql --skip-bundle※このエラーが出たら
Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "exec: \"rails\": executable file not found in $PATH": unknown↓
$ docker-compose build
※何らかのgemが不足しているようなエラーが出たら
自分は以下のようなエラーが発生しました。
Could not find public_suffix-4.0.1 in any of the sources
↓
$ docker-compose run web bundle install一度
bundle install
を試してみて下さい。database.ymlの変更
config/database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password #ここを変更 host: db #ここを変更コンテナが立ち上がるか確認
docker-compose up -d
だとログが見えないので、無事に環境構築が完了するまでは-d
なしが良いと思います。$ docker-compose up
db:create
無事にコンテナが立ち上がったら、DBを作成しましょう。
$ docker-compose exec web rails db:createYay! You’re on Rails!
localhost:3000
にアクセスして確認してみましょう。これでRailsはOKなので、次はVue.jsです!
3.Vue.jsの導入
webpackerのインストール
$ docker-compose exec web rails webpacker:installVue.jsのインストール
$ docker-compose exec web rails webpacker:install:vueVue.jsとの連携を確認
Railsでコントローラーを作ってみて、Vue.jsと連携出来るかを確認してみます。
$ docker-compose exec web rails g controller home indexapp/views/home/index.html.erb<%= javascript_pack_tag 'hello_vue' %> <%= stylesheet_pack_tag 'hello_vue' %>このように変更し、
hello_vue.js
を読み込みます。※Vue.jsのインストール時に
hello_vue.js
はデフォルトで作成されています。Railsのルーティングを設定
config/routes.rbroot to: 'home#index'ブラウザで確認
localhost:3000にアクセスし、下記画面が出力されているか確認してみて下さい。
これでVue.jsの導入はOKですが、他の単一ファイルコンポーネントも作成し、読み込めるかどうかを確認しておきます。
4.他の単一ファイルコンポーネントが読み込めるかどうか確認する
Top.vueの作成
app/javascript/components/
ディレクトリを作成し、その中にTop.vue
を作成します。
Top.vue
の中身は以下のようにしました。app/javascript/components/Top.vue<template> <section id="top"> <h1>This is Top.vue!</h1> </section> </template> <script> export default { name: 'Top' } </script> <style> h1 { text-align: center; } </style>
app.vue
を変更app/javascript/app.vue<template> <div id="app"> <p>{{ message }}</p> <Top/> //追記 </div> </template> <script> import Top from "./components/Top"; //追記 export default { data: function () { return { message: "Hello Vue!" } }, components: { Top, //追記 } } </script> ...以下略ブラウザで確認
再度読み込みすると、以下のような画面になっているはずです。
これできちんと単一ファイルコンポーネントが読み込まれていることが確認できたので、Vue.jsはOKです。
次はラスト!Vuetifyの導入です。
5.Vuetifyの導入
Vuetifyのインストール
$ docker-compose exec web yarn add vuetify -D
hello_vue.js
に追記hello_vue.jsimport Vue from 'vue' import Vuetify from 'vuetify' //追加 import "vuetify/dist/vuetify.min.css" //追加 import App from '../app.vue' Vue.use(Vuetify) //追加 const vuetify = new Vuetify(); //追加 document.addEventListener('DOMContentLoaded', () => { const app = new Vue({ vuetify, //追加 render: h => h(App) }).$mount() document.body.appendChild(app.$el) console.log(app) })application.html.erbの変更
application.html.erb
でVuetify用にフォントとアイコンの読み込みと不要な箇所の削除を行います。app/views/layouts/application.html.erb<head> ...略 <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet"> ...略 </head>下記タグは不要なので削除しておきます。
app/views/layouts/application.html.erb<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>これで準備が整ったので、あとはVuetifyのコンポーネントが反映されるかどうか、作成して確認してみましょう。
Header.vueの作成
新規にHeader.vueを作成し、
<v-app-bar>
を組み込んでみます。app/javascript/components/Header.vue<template> <header id="header"> <v-app-bar> <v-app-bar-nav-icon></v-app-bar-nav-icon> <v-toolbar-title>This is Header.vue</v-toolbar-title> </v-app-bar> </header> </template> <script> export default { name: 'Header', } </script>
app.vue
を変更
Header.vue
を読み込みつつ、全体をまとめている<div>
を<v-app>
タグに変更しておきます。app/javascript/app.vue<template> <v-app id="app"> //divから変更 <Header/> //追記 <h1>This is app.vue</h1> //一応追記。どっちでもいいです <p>{{ message }}</p> <Top/> </v-app> </template> <script> import Header from "./components/Header"; //追記 import Top from "./components/Top"; export default { data: function () { return { message: "Hello Vue!" } }, components: { Header, //追記 Top, } } </script>これで再度localhost:3000にアクセスし、問題ないかを確認してみます。
無事に画像のようなヘッダーが表示されていれば完了です!
以上です!お疲れ様でした!
おわりに
最後まで読んで頂きありがとうございました
どなたかのお役に立てれば幸いです
参考にさせて頂いたサイト(いつもありがとうございます)
- 投稿日:2019-12-23T19:23:27+09:00
欅坂46の歌詞を形態素解析してみた(MeCabをRubyで使う)
はじめに
12月14日に平成Ruby会議01に参加してきました。
「階層的クラスタリングをRubyで表現する」のセッションに影響されて、
普段、自然言語処理や統計などやっていない素人がやってみました。環境
macOS Cataline 10.15.2
Ruby 2.5.3
Homebrew 2.2.1インストール
MecabとMecabで使用する辞書をインストールします。
$ brew install mecab $ brew install mecab-ipadic$ mecab -v mecab of 0.996続いて、
MecabをRubyで扱うことのできるnatto
をインストールします。$ gem install natto$ gem list natto *** LOCAL GEMS *** natto (1.1.2)実装
今回歌詞を取得するため
歌ネット からnokogiri
を使ってスクレイピングします。
今回nokogiri
に関する説明は省略します。欅坂46の歌詞の人気順のTOP10に絞って行いました。
https://www.uta-net.com/artist/19868/4/まず人気一覧から各曲の歌詞ページの飛んで歌詞を取得し、
run_natto
に渡します。require 'nokogiri' require 'open-uri' def run doc = Nokogiri::HTML.parse(open('https://www.uta-net.com/artist/19868/4/'), nil, 'utf-8') words = [] doc.css('.td1').each_with_index do |f, i| link = f.children[0][:href] url = "https://www.uta-net.com#{link}" song = Nokogiri::HTML.parse(open(url), nil, 'utf-8') title = song.css('.title > h2').text run_natto(song, title, words) break if i == 9 end end
Natto::MeCab.new
にノードフォーマットオプションを渡すことで
抽出する属性をカスタマイズすることができます。今回は
「助詞」、「助動詞」、「記号」は除き、
形態素: ノードの品詞
の形で抽出しています。人: 名詞 溢れ: 動詞 交差点: 名詞 どこ: 名詞 行く: 動詞require 'natto' def run_natto(song, title, words) natto = Natto::MeCab.new('-F%m:\s%f[0]') natto.enum_parse(song.css('#kashi_area').text).each do |n| array = n.feature.split(': ') if array[1] == '助詞' || array[1] == '助動詞' || array[1] == '記号' next else words << array[0] end end end
run_natto
で生成した配列から
形態素をkeyとして個数をvalueとするハッシュを生成し
CSVで出力しました。require 'csv' def run ... hash = words.group_by(&:itself).map{ |key, value| [key, value.count] }.to_h create_csv(hash) end def create_csv(hash) CSV.open('file.csv', 'w') do |csv| csv << %w[ワード カウント] hash.each do |key, value| csv << [key, value] end end end結果
TOP10は以下のようになりました。
ワード カウント ( 68 し 50 僕 50 ) 45 ん 39 れ 37 ない 35 君 31 人 29 何 29 「僕」「君」は多いだろうなと思っていたので予想通りでしたが
「()」や「ん」「ない」などあまり参考にならないものが上位に来てしまい
改良の余地があるなと思いました…ちなみに
ワード カウント セゾン 24 Blah 24 二人セゾンとアンビバレントですね
(セゾンこんな言ってるんだ…)最後に
実際の発表ではさらに階層的クラスタリングを行っているのですが、
とりあえずは形態素解析まで行ってみました。今回は秋元康でしたが別の作詞家でやってみると面白いかと思います!
参考
https://github.com/buruzaemon/natto
https://speakerdeck.com/ayumitamai97/implement-hierarchical-clustering-analysis-using-ruby
https://www.uta-net.com/
http://brainvalley.jp/blog/32
http://brainvalley.jp/blog/33
- 投稿日:2019-12-23T19:08:18+09:00
ライブラリの定期アップデートのPRをどこまでレビューするか
前提
あくまで私がどこまで見てるか、です。会社・チーム・文化によって違うと思うので参考程度に見てください。
今まで Ruby の開発がメインだったのでその経験をもとにしています。思い出しながら書いてるので、えいっと書き出したものなので、書き漏れがあるかもしれないのですが、それはご了承ください。
また、表現が適切じゃない箇所もあるかもしれないのですが、そこもいい感じに読み取っていただけると助かります ><しない話
- 定期アップデートをなぜやるのか
- 定期アップデートをどうやるのか
レビュー時にみるポイント
- どれくらいバージョンが上がっているのか
- メジャーなのかマイナーなのかパッチなのか
- 現在のバージョンはいくつか
- production 用か devleopment 用か test 用か
- 言語系か小さなツールか
- 作者・チームは信頼できるか・きちんとメンテされているか
- changelogはきちんと書かれてるか
- どういう変更か・breaking changesがあるか
- feature/bugfix/breaking changes/docs etc
- リリースされてからどれくらい経っているか
- PR/commit/issue/diffをみる
- テストでカバーできてる範囲の変更か
- 仮にバグっていたときどこに影響が出るのか
どれくらいバージョンが上がっているのか
- APIの変更に互換性のない場合はメジャーバージョンを、
- 後方互換性があり機能性を追加した場合はマイナーバージョンを、
- 後方互換性を伴うバグ修正をした場合はパッチバージョンを上げます。
多くのアプリはこの semantic versioning を採用しているはずなので、
メジャー・マイナー・パッチのどれがどれくらい上がったか、をみるば影響範囲が概ね想定できます。※ ただ、アプリによっては、 semantic versioning っぽいバージョンの付け方をしていても、 semantic versioning に則ってないこともあるので注意です。
また、
0.y.z
のようなメジャーバージョンが 0 系のアプリに関しては、破壊的変更が入りやすいので注意深く見ます。production 用か devleopment 用か test 用か
https://bundler.io/v2.0/man/gemfile.5.html#GROUPS
Ruby の Gem (ライブラリ) 管理ツールである bundler では、
以下のように、gem をどの環境で使うのかを設定できます。gem 'rails' group :development, :test do gem 'byebug' end group :production do gem 'pg' end
production
で使う gem であれば、もちろん慎重にいきますが、
development
やtest
でつかう gem であれば、多少雑にレビューをしてバグったとしてもバグってから直せば良い、という判断をすることもあります。ただ、
development
やtest
でも、『通っちゃいけないテストが通るようになって本番にバグが紛れこんでしまう』ということも否定はできないので、
development
test
でも十分にレビューが必要なケースはもちろんあります。言語系か小さなツールか
たとえば
rails
のような影響範囲・依存度が高い gem はとても慎重にレビュー・テストを実行した上でのマージをするケースが多いですが、
おそらくテストのダミーでターでしか使われないようなfaker
であれば、テストが通ってれば十分だろう、という判断してさくっとマージしてしまうことが多いです。作者・チームは信頼できるか・きちんとメンテされているか
rails
やrack
などのような、大きなコミュニティによってメンテされている gem であれば、changelog をざっと眺めてBugFix
しかないのであればさくっとマージしてしまいます。
逆に、あまりきちんとメンテされてなさそうなそうでない gem の場合、きちんと changelog だけでなく commit/diff を丁寧に確認します。※ もちろん、OSSとして公開してもらえていて、それを利用しているのでありがたやという気持ちでいっぱいです。
基本的には、外部のものは自分たちの作業の範囲外なので信用しない・しすぎない、という体でいるのが正しいと思っています。
なので、基本丁寧にレビューして、信用できる gem であれば、少し手を抜いても良いかもという立ち位置です。changelogはきちんと書かれてるか
https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md#530-alpha39-november-5-2019
のように、Breaking Changes
,Features
,BugFix
のようにグループ分けされて書かれてるととても安心感が高くなります。
きちんと書かれてる場合、たとえばBreaking Changes
とあればそこは重点的に見ないといけないでしょうし、BugFix
だけであればざっとchangelogを眺めて気になるのがなければ changelog だけ眺めてマージしています。※ 自分のアプリがバグを踏んでいないか・バグ前提になってないなどはもちろんきちんと確認する必要はあります。
でも、changelog が書かれてない・あまりきちんとメンテされてないケースもたまにあるので、そのような場合は、changelog だけを参考にはせずに commit 履歴や diff を確認したほうが良いでしょう。
また、
Features
などであれば、自身のチームのアプリに導入できるものがないかを見て、導入できそうなものがあれば導入をします。
rubocop
のようなlint系のツールの場合、新しいルールをどうするかをチームで検討するなどもします。PR/commit/issue/diffをみる
changelog だけでなく、基本的には PR/commit/issue/diff もみます。
全部見てると時間が足りなくなってしまうので絞ったほうが良いと思いますが、
Breaking changes, BugFix, Feature
などは、どういう経緯・意図なのか、影響範囲はどれくらいか、などを把握するために見ます。
また、他人のコード・PR・issueをみることで学べることはたくさんあるので、そういう観点でも時間があったり興味があるのは見たりします。(『見てもわからない >< 』、っていう方ほど理解するために頑張ってみるのをおすすめします。)
人・チーム・文化によっては、diff全部見るべき、という意見の方もいると思うのですが、
その意見は十分に理解できますが、現実的には時間が足りないので、うまい落とし所を見つけていく必要があるなと思っています。リリースされてからどれくらい経っているか
定期的にupdateしている場合、新しいバージョンがリリースされてから1日もたってない gem のアップデートということもたくさんあります。
OSSとの向き合い方として、そういうのを積極的に取り入れてバグを発見したらバグ報告したり修正のPRをあげたりコントリビュートしたほうが好ましいと思っています。
ですが、現実的にはそうはいかないケースもあります。
内容次第では、1週間ぐらいアップデートをスキップして1週間後にバグ報告などが増えてなければ gem をアップデートするという選択もたまにします。テストでカバーできてる範囲の変更か
普段のレビューの観点と同様です。
例えば、
faker
gem であればテストが通っていれば十分だと思います。
でも、たとえばpuma
やwhenever
のような、単体テストではテストしづらい gem もあると思います。
それらの場合、実際動かしてみたりして挙動を確かめます。仮にバグっていたときどこに影響が出るのか
普段のレビューの観点と同様です。
これをイメージするには、gem のことを知っていて、自身のアプリの全体像を把握してる必要があります。
わからない場合は、チームメンバーに相談します。バグが起きたときにどう検知・対応できるか、どのタイミングで起こり得そうか、みたいなことをイメージし、
「問題が起きたら直す!」って責任が持てそうであればエイヤッとマージしてしまうこともあります。
マージ後に挙動確認して、ダメだったら即revertします。とくに、とりあえずマージしてデプロイが無事完了してアプリが起動すればOKだろう、っていうものの場合はとりあえずマージという判断をすることそこそこあります。
(動かしてみないとわからないなら動かしてみればいいじゃん、です。)CI通らない・アプリが動かなくなったときにどうするか
- Changelog, それに関連する PR/issue/commit/diff を読む
- 同様の問題報告などがないか、 issue/PR を探す
- issue/PR を上げる
- アップデートを見送るのか・パッチをあてるのか
Changelog, それに関連する PR/issue/commit/diff を読む
まずはきちんとどういうアップデートが行われたのか確認します。
Breaking changes
やBugfix
にある内容を踏んでるケースが多いので、注意深く確認します。同様の問題報告などがないか、 issue/PR を探す
自身のアプリが、その gem にモンキーパッチをあてたり・特殊な使い方をしてない限り、
同様の問題は他の人の環境でも起きてる可能性が高いです。
なので、すでに別の方が issue/PR を上げてくれている可能性があるので、それを探します。issue/PR を上げる
だれも issue/PR を上げてないのであれば、issue/PRを上げます。
アップデートを見送るのか・パッチをあてるのか
たとえば、バグを発見してすでに報告済みで数日待てば修正されたバージョンが上る見込みがあるのであれば、
それを待つという選択を取ることは多いです。しかし、アクティブにメンテされてる gem ばかりではないので、すぐに直る見込みがないケースも十分にあります。
その場合は、アプリにモンキーパッチをあてたり、gem をフォークして修正してそのバージョンを使うなどもあります。どちらにするかは、状況によるのでチームメンバーと相談しながら決めていきます。
最後に
慣れるまでは時間がかかるしハードルが高いと思います。
ですが、根気よく続けていけば、技術力も上がるはずですし、
良い意味で手を抜いてレビューできる部分がわかってきてそこまで負荷なくレビュー・マージできるようになると思います。あくまで、私自身の経験としてライブラリアップデートのPRをレビューしてる経験から書いてみました。
会社・チーム・文化によって重視する点が変わると思うので、あくまで一つのやり方として参考にしてもらえればと思います。
- 投稿日:2019-12-23T17:57:37+09:00
[Ruby] より細かくMetricsを取得したい [Heroku]
2016年に[Ruby] かしゆかの誕生日!という潔すぎる Advent Calendar を書いてから 3年。またこの地に降り立ちました、しょっさんです。もちろん、本日もかしゆかの誕生日です、おめでとうございます!!!
今日はRuby Advent Calendar 2019の 23日目、今年もかしゆかの誕生日です!! 昨日はfursichさんのネタ待ち、明日はooooooo_qさんです!!
Heroku Ruby Language Metrics(Public Beta) がでたぞ!
public beta なので、大きな声で推奨はできませんが、NewRecli や Scoutを入れるほどでも、標準よりももう少し詳しく Heroku がアプリの Metrics とってくれないかな...。
とれるようになりそうです
それが、この Ruby Language Metrics(Public Beta) です。設定すればよいだけではないのと、動いたり動かなかったりなのは「β」だからということで、事情を知りうまく行かなかったときは、heroku-metrics-feedback@salesforce.com へフィードバックを入れていただけると捗ると思います、どうぞよろしく。(英語でね)
何ができるようになったの?
次の3つの項目が、追加で取れるようになりました。
- Puma Pool Usage … Puma のキューイング状況が見られます。80%超え始めたら Dyno 追加しようかなみたいな。
- Free Memory Slots … 単位時間あたりのメモリスロットの状況を可視化します。
- Heap Objects Count … 割当と開放されたヒープ状況を可視化します。メモリちゃんと開放されてる?
どうやって使うの?
制約条件として
- Puma >= 3.12.0
- 純粋な Ruby (JRubyは JVM Runtime Metrics)
使い方は Rails なら次のでいけます。
- Metric を Enableする
- 'heroku/metrics' buildpack を「頭に」追加する
- "barnes" Gem をインスコ
1. Metric を Enableする
Heroku Dashboard から、Heroku アプリの Metricsへ行って、「歯車」から "Enhanced Language Metrics" to ”Ruby Language metrics" を ON にしてあげます。
コマンドなら。
$ heroku labs:enable "runtime-heroku-metrics" -a "my-app-name" $ heroku labs:enable "ruby-language-metrics" -a "my-app-name"2. 'heroku/metrics' buildpack を「頭に」追加する
同じく Dashboard の
Settings
から、"Add buildpack" で 「heroku/metrics」を追加します。一番目になるように、順序を入れ替えてあげます。コマンドなら。
$ heroku buildpacks:add -i 1 heroku/metrics3. "barnes" Gem をインスコ
Gemfile
にbarnes
を追加して、bundle install
して、再deployしましょう。gem "barnes"※ Railsじゃないとき
例えば puma.rb とかに次のものが必要です。
require 'barnes' before_fork do # worker specific setup Barnes.start # Must have enabled worker mode for this to block to be called end※ workerない場合は動かないので、最低限一つのworkerが必要みたいだけど。
- 投稿日:2019-12-23T17:35:43+09:00
Ruby on Rails 初歩の初歩
備忘録
こんにちは!HLDの井出です。(https://t.co/FiYK5Qty3J?amp=1)
今回は、Railsについての備忘録を残していきます。
インストールからscaffoldまでです。環境
- Windows10
- viniciusfs/centos7
- rails 5.1.3
準備
Rubyのインストール
参考:https://github.com/rbenv/rbenv
流れは、まず管理ツールのrbenvをインストール。
↓
インストールできるバージョンの確認
$ rbenv install --list
欲しいバージョンをインストールしたら、
$ rbenv grobal バージョン
$ rbenv rehash
そして、
$ ruby version
で、バージョンが表示されたらOK!!railsのインストール
$ gem install rails -v x.x.x
(Xは欲しいバージョン)その前に
下拵えで、nodejsとsqlite3をインストールしておきます。しておかないまま、railsのインストールを進めるとjavascriptが分らんけど?って怒られます。。。起動!!
インストールができたら、任意のディレクトリで、
rails new app
そうすると、勝手にいろいろと作ってくれる。アドレス確認
$ ip a
でアドレスを確認して、ブラウザに表示してみます。
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 08:00:27:c1:f7:7a brd ff:ff:ff:ff:ff:ff
inet 192.168.33.10/24 brd 192.168.33.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fec1:f77a/64 scope link
valid_lft forever preferred_lft forever
192.168.33.10なので、
$ rails server -b 192.168.33.10 -d
として、サーバー立ち上げます。
Runと出たらOK!!
アドレスをコピペしてブラウザに表示させてみます。scaffoldで簡単なアプリを作ってみる
無事に、鯖が立ち上がったら、sccaffoldで簡単なアプリを作ってみます。
rails g scaffold Memo title:string body:text
とすると、メモアプリをサクッとrailsが用意してくれます。
できたら、
$ rails db:migrate
とします。
ブラウザで192.168.33.10/memos
として表示されたらOK!!殺してみる
$ ps aux | grep puma
でプロセス番号がわかります。
vagrant のあとに表示されている数字です。
$ kill -9 プロセス番号
とすると、今立ち上げていたアプリを終わらせることができます。後記
サクッとでしたが、railsのインストールからアプリの立ち上げまでの流れを見てみました。
あとは、コントローラーとか色々作り込んでみてください。
参考になれば嬉しいです。
- 投稿日:2019-12-23T14:55:28+09:00
多目的遺伝的アルゴリズムで恋愛シミュレーションゲームでのひとりぼっちを回避する
アマガミの季節になりましたね。
この時期になると、ときめきなメモリアルを作りたいなぁと考える方がいるかも知れません。そこで、今回は恋愛シミュレーションゲームを人工知能(AI)の力でヒロインを攻略して、ひとりぼっちを回避したいと思います。
……ただ、ヒロインを1人攻略するだけでは面白くありません。複数のヒロインを同時に攻略していきたいと思います。
ソースコードはGithubで公開しておりますので、詳細はそちらをご確認ください。
https://github.com/Adacchi3/MultiObjectiveGA多目的遺伝的アルゴリズム
今回は多目的遺伝的アルゴリズムを使用します。
多目的遺伝的アルゴリズムは、遺伝的アルゴリズムを多目的最適化に適用したものであり、近似解を探索するメタヒューリスティックアルゴリズムです。
基本的なプロセスは遺伝的アルゴリズムと同様で以下の通りです。
- 初期集団の生成
- 遺伝子の評価
- 交叉
- 突然変異
- 終了条件になるまで2.-4.を繰り返す
多目的遺伝的アルゴリズムの特徴は、遺伝子の評価に複数の評価関数を使うことです。
複数の評価関数を用いることにより、しばしばトレードオフの関係が生じる場合があります。
ここでは、遺伝子同士を比較し、支配関係である遺伝子を淘汰することによって、パレートの近似解を求めていきます。恋愛シミュレーションゲーム
今回シミュレーションするゲームはリアルのゲーム……ではなく、多目的遺伝的アルゴリズムに適用できるシミュレーションを作成しました。
プレイヤーは、文系、理系、芸術、スポーツ、休みの5つの行動のいずれかを1日で1回行動することができます。行動したものに応じて、プレイヤーのパラメータが上昇します。
ヒロインは、プレイヤーのパラメータを参照して、好感度を評価します。ヒロインは複数人おり、それぞれ異なる評価関数で好感度を計算します。
プレイヤーが行動することができる期間は30日(1ヶ月)であり、プレイヤーはヒロインの好感度をできる限り上げるように行動を決定していきます。ただし、実際にあるゲームでは、特別なイベント(デート・病気・修学旅行等)があるかもしれませんが、今回のシミュレーションでは想定しておりません。
実行結果
上記の内容を踏まえ、今回の多目的遺伝的アルゴリズムを実行する上での条件を以下のようにしました。
※ ハイパーパラメータのチューニング等は行っておりません。ご了承ください。。。
- 個体数:100
- 遺伝子の長さ:30
- 世代数:40
- 交叉:二点交叉
- 突然変異:10%
- 評価関数(ヒロインの数):3
- Aさん:理系パラメータを評価
- Rさん:芸術パラメータを評価
- Yさん:各パラメータを評価
世代数における評価値の推移は以下の通りになります。
ここでの評価値は各ヒロインの好感度の平均を算出しています。横ばいか少しの下降傾向にあるように見受けられます。評価値の平均は0.5で収束しているのでしょうか。評価関数の計算式を見直す必要があるかもしれません。
続いて、各個体の評価値を一部抜粋して確認していきます。
name Aさん Rさん Yさん NQESC 0.508 0.508 0.234 RQEEZ 0.516 0.508 0.235 TSTWL 0.508 0.508 0.264 AさんとRさんはほぼ同じ好感度になっています。YさんはAさんとRさんの半分くらいになっていますね(生徒会長を攻略することは難しいのでしょうか。。。)
これらの結果から、各評価関数がパレート最適になっているだろう近似解を取得できているように見受けられます。つまり、クリスマスパーティーが開催されるのであれば、声がかかる可能性があるわけですね!
まとめ
今回は多目的遺伝的アルゴリズムで恋愛シミュレーションゲームのヒロインを同時攻略してみました。多目的最適化によって、複数のヒロインの好感度をパレート解に近い形で得られる可能性があることを確認することができました。
評価関数やハイパーパラメータのチューニングを改良していくことによって、プレイヤーがひとりぼっちになることを回避していけたらな、と思います。
P.S.
私事ではありますが、有志で技術書典8にサークル参加することになりました。
マイナーなプログラミング言語を中心に解説する本を頒布する予定です。
よろしくお願いいたしますm(_ _)m
参考
- 投稿日:2019-12-23T12:47:08+09:00
Rails6 のちょい足しな新機能を試す113(MySQL enum set 編)
はじめに
Rails 6 に追加された新機能を試す第113段。 今回は、
MySQL enum set
編です。
Rails 6 では、MySQL の enum や set のカラムの schema dump が正しく出力されるようになりました。Ruby 2.6.5, Rails 6.0.2.1, MySQL 8.0.16 で確認しました。 (Rails 6.0.0 でこの修正が入っています。)
$ rails --version Rails 6.0.2.1今回は、users テーブル (User モデル)に
enum
とset
のカラムを追加して試してみます。Rails プロジェクトを作成する
$ rails new rails_sandbox $ cd rails_sandboxUser モデルを作成する
name
カラムだけもつUser
モデルを作成します。
enum
とset
のカラムは、後で、migration ファイルを直接編集して、追加します。bin/rails g model User namemigration ファイルを編集する
migration ファイルを編集して、
enum
カラムのgeneration
とset
カラムのlearning
を追加します。db/migrate/20191221002717_create_users.rbclass CreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| t.string :name t.column :generation, "enum('baby', 'toddler', 'preschool', 'gradeschool', 'teen', 'young_adult')" t.column :learning, "set('piano', 'english', 'swimming', 'ballet', 'calligraphy')" t.timestamps end end endseed データを作成する
機能を試すためには、作る必要は、無いのですが、一応、 seed データを1件作成します。
db/seeds.rbUser.create(name: 'Dave', generation: 'gradeschool', learning: 'english,swimming')マイグレーションを実行する
マイグレーションを実行します。
shell
$ bin/rails db:create db:migrate
スキーマファイルを確認する
できたスキーマファイルを確認します。
db/schema.rbActiveRecord::Schema.define(version: 2019_12_21_002717) do create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "name" t.column "generation", "enum('baby','toddler','preschool','gradeschool','teen','young_adult')" t.column "learning", "set('piano','english','swimming','ballet','calligraphy')" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end endRails 5 では
Rails 5.2.4 では、スキーマファイルは以下のようになってしまいます。
generation
とlearning
のカラムが string になってしまい、ENUM と SET であることがわからなくなってしまいます。db/schema.rbActiveRecord::Schema.define(version: 2019_12_21_005503) do create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| t.string "name" t.string "generation", limit: 11 t.string "learning", limit: 41 t.datetime "created_at", null: false t.datetime "updated_at", null: false end endMySQL で直接確認する
なお、MySQL で直接確認した場合は、Rails 6.0.2.1 も、Rails 5.2.4 も同じです。
mysql> show columns from users; +------------+-----------------------------------------------------------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+-----------------------------------------------------------------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | generation | enum('baby','toddler','preschool','gradeschool','teen','young_adult') | YES | | NULL | | | learning | set('piano','english','swimming','ballet','calligraphy') | YES | | NULL | | | created_at | datetime(6) | NO | | NULL | | | updated_at | datetime(6) | NO | | NULL | | +------------+-----------------------------------------------------------------------+------+-----+---------+----------------+ 6 rows in set (0.00 sec)bin/rails db:reset を試す
bin/rails db:reset
を実行してから、MySQL で直接確認するとRails 6.0.2.1 は、
mysql> show columns from users; +------------+-----------------------------------------------------------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+-----------------------------------------------------------------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | generation | enum('baby','toddler','preschool','gradeschool','teen','young_adult') | YES | | NULL | | | learning | set('piano','english','swimming','ballet','calligraphy') | YES | | NULL | | | created_at | datetime(6) | NO | | NULL | | | updated_at | datetime(6) | NO | | NULL | | +------------+-----------------------------------------------------------------------+------+-----+---------+----------------+ 6 rows in set (0.01 sec)と変わりないですが、Rails 5.2.4 では
mysql> show columns from users; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | generation | varchar(11) | YES | | NULL | | | learning | varchar(41) | YES | | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+ 6 rows in set (0.00 sec)と変わってしまいました。
rails console からデータを検索する
(ここからはおまけです。)
bin/rails db:reset
で seed データが登録されたので、検索してみます。irb(main):006:0> User.where(generation: 'gradeschool') User Load (1.2ms) SELECT `users`.* FROM `users` WHERE `users`.`generation` = 'gradeschool' LIMIT 11 => #<ActiveRecord::Relation [#<User id: 1, name: "Dave", generation: "gradeschool", learning: "english,swimming", created_at: "2019-12-21 02:04:41", updated_at: "2019-12-21 02:04:41">]> irb(main):007:0> User.where("learning like '%swimming%'") User Load (0.9ms) SELECT `users`.* FROM `users` WHERE (learning like '%swimming%') LIMIT 11 => #<ActiveRecord::Relation [#<User id: 1, name: "Dave", generation: "gradeschool", learning: "english,swimming", created_at: "2019-12-21 02:04:41", updated_at: "2019-12-21 02:04:41">]>正しくないデータを投入する
generation や、learning に不正なデータを指定して登録すると
ActiveRecord::StatementInvalid
が発生します。generation も learning も不正なデータの場合を試してみます。
最後のエラーメッセージが
Data truncated for column 'generation' at row 1
であることからgeneration
に問題があることがわかります。
(が、どうして truncate されたか、ちょっとわかりにくいです...。)irb(main):008:0> User.create(name: 'NG', generation: 'NG', learning: 'NG') (0.5ms) BEGIN User Create (1.6ms) INSERT INTO `users` (`name`, `generation`, `learning`, `created_at`, `updated_at`) VALUES ('NG', 'NG', 'NG', '2019-12-21 02:15:29.767037', '2019-12-21 02:15:29.767037') (0.4ms) ROLLBACK Traceback (most recent call last): 1: from (irb):8 ActiveRecord::StatementInvalid (Mysql2::Error: Data truncated for column 'generation' at row 1)generation は正しいが、 learning が不正なデータの場合です。
最後のエラーメッセージが
Data truncated for column 'learning' at row 1
であることからlearning
に問題があることがわかります。irb(main):009:0> User.create(name: 'NG', generation: 'gradeschool', learning: 'NG') (0.4ms) BEGIN User Create (1.2ms) INSERT INTO `users` (`name`, `generation`, `learning`, `created_at`, `updated_at`) VALUES ('NG', 'gradeschool', 'NG', '2019-12-21 02:15:50.398770', '2019-12-21 02:15:50.398770') (0.2ms) ROLLBACK Traceback (most recent call last): 2: from (irb):9 1: from (irb):9:in `rescue in irb_binding' ActiveRecord::StatementInvalid (Mysql2::Error: Data truncated for column 'learning' at row 1)Rails 5 では
bin/rails db:migrate
で作成した場合は、Rails 6 と同じ動作ですが、bin/rails db:reset
すると Type がただの string になってしまうため、不正なデータでも登録できてしまいます。ActiveRecord の enum との関係
ActiveRecord の
enum
と MySQL のenum
は全く関係がなく、以下のように、 User モデルにenum
を定義しても、MySQL の generation カラムの enum とは連動せず、期待通りの動作はしません。app/models/user.rbclass User < ApplicationRecord enum generation: %i[baby toddler preschool gradeschool teen young_adult] end試したソース
https://github.com/suketa/rails_sandbox/tree/try113_mysql_enum
参考情報
- 投稿日:2019-12-23T12:15:26+09:00
Cable master とドント方式と中央値の中央値
はじめに
競技プログラミングで見つけたとある問題が見かけの違う別の問題と本質的に同じで、その最小計算量オーダが意外と小さかったというお話です。
Cable master 問題
その問題は「プログラミングコンテストチャレンジブック」 (通称「蟻本」。表紙に蟻の絵が描いてあるから) の中級編の問題
「Cable master」
(英語版の元問題はこちら)長さがそれぞれ $ L_i $ であるような $ N $本の紐があります。これらの紐を切って、同じ長さの紐を $ K $本作るときの最長の長さを求めなさい。
答えは小数以下2桁までを切り捨てて出力しなさい。蟻本では二分探索を使って解の近似値を求めていますが、こういう問題を見たら近似値ではなく厳密な値を求めたくなるのが人間の性(さが)ですよね。
厳密な解はN本の紐のどれかをある本数で割った長さになるはずです。
なぜならちょうど割り切れた長さでなければまだ伸びしろがあるわけですから解が最長の長さであることに矛盾します。
つまり解は\{ \frac{L_i}{j} \mid 1 \leqq i \leqq N,\, 1 \leqq j \leqq K \}の中にあります。
この集合を長さ順にソートして順番にその長さで N本取れるか試していけば解が見つかります。
ナイーブにこの方法を行うと計算量オーダは大きいですが、工夫すると O(N * log(N)) の計算量で求められることがわかりました。(K に影響されない!)Cable master 問題とドント方式
これが計算量の限界かなと思い答えがネット上に載っていないか探したところ、まさにこの問題を取り上げている
情報学会の会誌「情報処理」連載の「プログラム・プロムナード」の「ケーブルマスタ」
を見つけました。
そこには Cable master問題がドント方式で解けると書いてあります。ドント方式とは Wikipedia の記事によると
政党名簿比例代表において、議席を配分するための最高平均方式(highest averages method)のひとつである。
この方式はベルギーの数学者ヴィクトル・ドント(Victor Joseph Auguste D'Hondt)から名づけられた。(日本でも比例代表の選挙で使っています)
ドント方式の手順は
(上記 wikipedia の記事の「配分」の「1議席ずつ配分するのではなく次のように考えても同様である。」のところに書いてある方法)
(1) 各政党の得票数を1,2,3・・・の整数で割る
(2) 一人当たりの得票数が多い順(割り算の答えの大きい順)に、1議席ずつ各政党に議席を配分する
です。例えば 9議席を A党、B党、C党の3党で争っていて各政党の獲得得票数がそれぞれ 120票、90票、60票だったとします。
まず、各政党の得票数を1,2,3・・・の整数で割ると
党 / 1 / 2 / 3 / 4 ... A党 120 / 1 120 / 2 120 / 3 120 / 4 ... B党 90 / 1 90 / 2 90 / 3 90 / 4 ... C党 60 / 1 60 / 2 60 / 3 60 / 4 ... これらをまとめて値の順に並べて上位9個を取ると(どの党のものかわかるように後ろに党名をつけておきます)
\{ 120(A), 90(B), 60(C), 60(A), 45(B), 40(A), 30(C), 30(B), 30(A)\}結果、A党は4議席、B党は3議席、C党は2議席獲得することになります。
ドント方式が Cable master とどう対応するかというと
Cable master ドント方式 $ K $ 総議席数 $ N $ 党の数 $ L_i $ 各党の得票数 各紐から取れる紐の本数 各党の獲得議席数 答えである紐の長さ 最後に議席獲得したときの割り算の答え
(上記の例では 30)という対応になります。
先ほどの Cable master の解法では
\{ \frac{L_i}{j} \mid 1 \leqq i \leqq N,\, 1 \leqq j \leqq K \}を長さ順にソートして順番に N本取れるか試していけばよいと書きましたが、実は単に長さ順でN番目の値を取ってくればよいのでした。
Cable master問題がドント方式となぜ本質的に同じ問題なのかについては上記の「プログラム・プロムナード」で見ていただくとして次に進みましょう。
ドント方式を計算量 O(N) で解く
Cable master問題がドント方式と同じだとわかったので今度は
「ドント方式 計算量」
で検索すると、なんと最悪ケースでも計算量 O(N) で解けると書いてある論文を見つけました。「議席配分法に対する線形時間アルゴリズム」
(ドント方式の計算量 O(N) のアルゴリズムが書いてあるのは、定理 4.2 です。)なぜ O(N) で計算できるかは論文を見ていただく(ちょっと難しい)ことにして次に進みましょう。
計算量 O(N) で解くプログラム
上記の論文に書いてあるアルゴリズムにしたがってプログラムを書いてみましょう。Ruby で書いてます。
# s_total: 総議席数。論文では S # vs: 各党の獲得投票数。論文では {v_i} def dhondt(s_total, vs) # 計算しやすいように実数にしておく vs = vs.map(&:to_f) n = vs.length # 論文でも n vsum = vs.sum # 論文では Σ v_i # step1 # 理想配分数 ss = vs.map do |v| s_total * v / vsum end # 初期配分数 s0s = ss.map(&:to_i) # step2 # 追加候補集合 cs = vs.map.with_index do |v_i, i| s_i = ss[i] s0_i = s0s[i] # 論文 # a < 1 + n/S, a = s / s_i なので s < (S + n) * s_i / S (s0_i + 1 .. ((s_total + n) * s_i / s_total - 1).ceil).map {|s| s / s_i} end # step3 # 不足配分数 d = s_total - s0s.sum # 論文では α_dth a_dth = d == 0 ? 1.0: select_kth(cs.flatten, d-1) # step4 # 追加配分数 dss = cs.map do |as| as.take_while {|a| a <= a_dth}.count end # 各党の獲得議席数 ss = s0s.zip(dss).map do |is, ds| is + ds end # Cable master問題では答えの紐の長さ z = vsum / s_total / a_dth [z, ss] end # ソートされていない配列からk番目に大きな数を求める # k は 0オリジン def select_kth(xs, k) # どう実装するか endただし最後のメソッド select_kth は複雑なので少し解説してから実装することにします。
というのも上記の論文では、ソートされていない N個の数の集合の中から k番目に大きな数を最悪ケースでも計算量 O(N) で得ることを求めているのです。
すぐに思いつく方法は配列をソートして上から k番目の数を求める方法ですが、この方法ではソートするのに O(N*log(N)) かかってしまいます。最悪ケースでも O(N) の計算量で k番目の数を求めるアルゴリズは見つかっています。(
introselect
という名前のようです)パーティションベースの汎用選択アルゴリズムの中の「中央値の中央値」を用いたクイックセレクトにアルゴリズムが書かれています。
クイックソートと似たクイックセレクトという手法を使っていますがクイックソートと同様にピボットの選び方に失敗すると最悪ケースの計算量オーダが大きくなるのでさらに中央値の中央値という手法を使って計算量が大きくならないように工夫しています。
このアルゴリズムを使って select_kth を実装しましょう。
def select_kth(xs, k) return xs[0] if xs.length == 1 pivot = select_pivot(xs) lows = xs.find_all do |x| x < pivot end lows_len = lows.length if k < lows_len select_kth(lows, k) elsif k == lows_len pivot else highs = xs.find_all do |x| x > pivot end select_kth(highs, k - (lows_len + 1)) end end def select_pivot(xs) # 中央値の中央値を選ぶ median_of_medians(xs) end def median_of_medians(xs, i = xs.length/2) return median5(xs, i) if xs.length <= 5 medians = xs.each_slice(5).map do |s| median5(s) end pivot = median_of_medians(medians) lows = xs.find_all do |x| x < pivot end lows_len = lows.length if i < lows_len median_of_medians(lows, i) elsif i == lows_len pivot else highs = xs.find_all do |x| x > pivot end median_of_medians(highs, i - (lows_len + 1)) end end def median5(xs, i = xs.length/2) return xs.sort[i] endこれでめでたしと思ったら
と、ここまで書いておいてなんですが、「「中央値の中央値」を用いたクイックセレクト」に
ただし、この方法では最悪時間は確かに線形になるが、平均時間は、実際にはピボット値を無作為に選ぶなどの素朴な方式の方が優れている。
と書いてあるとおり実際にはピボット選択に「中央値の中央値」を使わずに
def select_pivot(xs) # 要素をランダムに選ぶ xs.sample endとするのがよいでしょう。
さらに言えば現実の問題では O(N) も O(N * log(N)) も似たようなものなので Wikipedia のドント方式の記事の「配分」の「次のようにして1議席ずつ議席を配分する」に書いてある方法(最初に紹介した「情報処理」連載の「プログラム・プロムナード」にプログラムが載っています)を使うのが一番楽ではないでしょうか。
最後はちょっと身も蓋もない結論になってしまいましたがドント方式の理論上の最小計算量にふれて面白かったのではないでしょうか。
これで(多分)本当にめでたしめでたし。
- 投稿日:2019-12-23T10:22:15+09:00
新卒エンジニアの成長tips(6個)
新卒エンジニアとして入社して8~9ヶ月経過して、
ruby
なら少しは慣れたような気がしたので、今の気持ち(ポエム)を書こうかなと思い立ちました?以下のような新卒エンジニア向けの記事です!
・本格的な中〜大規模のrails
は初めてで
・エンジニアインターンでGitの使い方を知っていて
・railsを少し触った事があって
・国籍がコロンビアである??以下に限ったことではないのですが、僕の中で実践して良かったなと思った事だけなので?
これは
ruby
に関するtipsがメインですメソッド名をググらない
method名すぐ忘れますよね
methods.grep()
を使うと良いと思いました!> Date.today.methods.grep /begin.*day/ => [:beginning_of_day, :at_beginning_of_day]rspecを書く
特に大事なのはrspecを一番書ける人と仲良くなって、rspecの書き方を教えてもらった事です!
初めてのrpsecは書き方のイメージがつかないので、とりあえず書いてレビューしてもらおう?
結果的に仕様を再確認したり、自分のコードのバグを見つけられます!Better Specs は初めて読むと何とも思わないけど、色々と自分で書いたら、意味がわかったし、学びもあった!
次のステップとして、
rspecのレビューをする
があると思います。
スラスラ書けても、スラスラレビューできないのがrspecなんだなと思いました!
(書き方が色々あったり、裏技があったり、ややこしいんですよね?)同じ実装を個人開発でも作ってみる
会社の技術をコピペしろということではなく、同じ機能を自分で実装するのです。
結果と手順は知っているので、調べながら、どうしてもわからない場合は出勤した時にコードを少し読んで、どんなライブラリを使っているのか確認しましょう?
僕はこれでrescue
の仕組みや、DeviseTokenAuth
の仕組みを勉強しました!
0→1
にフェーズでしか実装しない(その後、改修されない)仕組みは自分から学ぼうとしないと、わからないので、大事だと思いました!gemのソースコードを読む
rubyのgemは素晴らしいです。
とにかく読みやすい!!?
また、普段何気なく使っているが意図がわからなかったdo~endブロック
の挙動とかもわかります!
PR → 【ruby】簡単ッ! do~endブロックって?
何より、機能を見に行けばqiitaには書いてないmethodを見つけることもできます。とにかく
source_location
を使おう!> User.method(:take).source_location => ["/Users/****/Desktop/****/vendor/bundle/ruby/2.5.0/gems/activerecord-5.1.6.1/lib/active_record/querying.rb", 3]端から読むのでなく、興味のある所だけ摘むだけでokだと思います。
将来的には全部知りたいですね。Object(オブジェクト)を意識する
hoge.class
でクラス名を知るだけで検索効率が全然違います。
何より、わからなければソースコードを読めば良いのです?> Array.new.each.class => Enumeratorとにかくレビューする
これは
ruby
に関わらないことなんですが、、
やっぱり身近にいる優秀なエンジニアのコードを読むのが一番良いです☺️
もちろん自分で書いてレビューしてもらうのも大事ですが、レビューしないと視野が広がらないと思いました。
とにかく、レビューして自分では思いつかない箇所をその都度勉強すべし!所感
レビューの話と似たようなことなのですが、とにかく優秀な先輩の仕事は全てストーキングしてます?️♀️
先輩のslackは全てスレッドフォローして普段からも疑問は質問して、勝手に学ぶのが一番近道だなと思います。以上です!
来年は、もっと成長してたら良いな〜?
- 投稿日:2019-12-23T08:51:38+09:00
codeanywhereでgitにハロワをプッシュしよう!
前置き
codeanywhereを使って、スマホのみでgitにRubyのハロワをプッシュする流れについて解説します。
手順
まずはハロワの実行ファイルを作成します。
手順は↓を参照して下さい。
Codeanywhereを使ってスマホでプログラミングをしようgithubにて、リポジトリを作成します。
Repositories>Desktop versionをタップ。
SSH画面にて、下記のリンクの通り操作します。
- 投稿日:2019-12-23T01:44:16+09:00
Sidekiq 6の新機能・変更点
はじめに
Ruby on Rails (以下Rails) でバックグラウンドジョブを実行する際によく使用されるGemである、Sidekiqのバージョン6.0が今年(2019年)9月にリリースされました。このバージョンでは、Active Jobへのオプション指定などの機能強化と共に、パフォーマンス向上が行われ、
sidekiqctl
コマンドが廃止されるなどの大きな変更が行われています。本記事では実際に使用した経験を元に、Sidekiq 6の新機能や変更点について説明を行います。注意点
本記事で取り扱うエディション
本記事ではGitHubで公開しているOSS版のSidekiqについて記述しています。Pro / Enterprise版を使用している場合は、公式サイト(英語)を参考にしてください。
サポートされるRails / Ruby / Redisのバージョン
サポートされるバージョンは以下の通りになります。
- Ruby on Rails 5以上
- Ruby 2.5以上
- Redis 4以上
上記より古いバージョンを使用している場合は、アップグレードを事前に行う必要があります。
新機能
Active JobでSidekiq独自のオプションを指定可能に
ActiveJob::Base
を継承したジョブで使用する際に、sidekiq_options
を指定することで、Sidekiq独自のオプションを一部使用できるようになりました。以下はリトライの回数と、Web UIで表示するバックトレースの行数を
sidekiq_options
で指定しています。class MyJob < ActiveJob::Base queue_as :myqueue sidekiq_options retry: 10, backtrace: 20 def perform(...) end end以下が使用できるオプションの一覧となります。
オプション 値 内容 retry
整数 リトライ回数を指定する backtrace
true, false, 整数 SidekiqのWeb UIにバックトレースを表示するかどうか
整数の場合は指定した行数で表示する参考
ログの出力形式を選択可能に
ログの出力機能がプラグイン化され、出力形式を選択できるようになりました。例えば、JSON形式の出力を行う場合は、以下のように
Sidekiq::Logger::Formatters::JSON
を指定します。Sidekiq.configure_server do |config| config.log_formatter = Sidekiq::Logger::Formatters::JSON.new end以下が使用できるログの出力形式の一覧となります。
フォーマッタ 説明 Sidekiq::Logger::Formatters::Pretty
通常の出力 Sidekiq::Logger::Formatters::WithoutTimestamp
タイムスタンプなしで出力 Sidekiq::Logger::Formatters::JSON
JSON形式で出力 ログ出力の例
Sidekiq::Logger::Formatters::Pretty
2019-08-31T15:36:07.569Z pid=82859 tid=11cy9br class=HardWorker jid=528f1b0ddc4a9d0690464fe4 INFO: start
Sidekiq::Logger::Formatters::WithoutTimestamp
pid=82859 tid=119pz7z class=HardWorker jid=b7f805c545c78770d30dc1fd elapsed=0.089 INFO: done`
Sidekiq::Logger::Formatters::JSON
{"ts":"2019-09-01T22:34:59.778Z","pid":90069,"tid":"104v8ph","lvl":"INFO","msg":"Running in ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]"}参考
- Pluggable Log Formatter by Tensho · Pull Request #4093 · mperham/sidekiq
- Logging · mperham/sidekiq Wiki
ジョブ毎のログレベル指定が可能に
ワーカーの
.set
メソッドの引数でlog_level
を指定することで、特定のジョブのログの出力レベルを指定できるようになりました。MyWorker.set(log_level: :debug).perform_async(...)SidekiqはデフォルトでRubyの標準ライブラリのLoggerを使用しており、
log_level
で指定できるのは、:unknown
、:fatal
、:warn
、:info
、:debug
になります。参考
- Support job-specific log levels by fatkodima · Pull Request #4287 · mperham/sidekiq
- Job-specific log levels · Issue #4286 · mperham/sidekiq
- Logging · mperham/sidekiq Wiki
ジョブのタグ付けが可能に
ワーカーのクラスの
sidekiq_options
にtags
を指定することで、ジョブのタグ付けができるようになりました。class MyWorker include Sidekiq::Worker sidekiq_options tags: ['bank-ops', 'alpha'] ... end参考
- Support job tags by fatkodima · Pull Request #4280 · mperham/sidekiq
- Support for ad-hoc job tags? · Issue #4073 · mperham/sidekiq
Web UIでのダークモードのサポート
ダークモードがサポートされたブラウザ(IEやEdge、Operaは非サポート)でモードが有効な状態でWeb UI(Sidekiqの管理画面)にアクセスした場合は、黒ベースの画面で表示するようになりました。
参考
変更点
sidekiqctl
コマンドの削除Sidekiqプロセスを停止、終了するために用いられてきた、
sidekiqctl
コマンドが廃止されました。Sidekiq 6では、このコマンドの代わりにkill
コマンドでシグナルを送ることで、プロセスの停止、終了を行うことができます。
廃止されたコマンド 代替となるコマンド 説明 sidekiqctl quiet
kill -TSTP <pid>
ワーカーの各スレッドを停止 sidekiqctl stop
kill -TERM <pid>
(上記コマンドの実行後に)プロセスを終了 デーモン化を廃止し、サービスとして実行するように
サービスの管理をOS側(Systemd、Upstartなど)で行うようにするために、デーモン化を廃止しました。このため、本番環境などサーバーで動かす場合は、Sidekiqのサービスの設定ファイルを新たに作成し、サービスを有効にする必要があります。
デーモン化の廃止に至った背景に関しては、Sidekiq作者による説明が以下のページにあります。
上記の記事では、以前から使用されてきた長時間動作するデーモンプロセスが、現在ではSystemdを使用したサービスに置き換えられていて、このことによって、ロギングやクラッシュ時の再実行などの処理を自前で実装することがなく、より堅牢な実行が可能になった、ということが書かれています。
Sidekiqのサービスの設定、起動や停止コマンドに関しては、この記事の次の節のアップグレードの部分を参考にしてください。
logfile
、pidfile
コマンド引数の廃止上記のデーモン化の廃止に伴って、ログの出力や、プロセスIDの保持をSystemdなどのサービス側で行うようになったので、
sidekiq
コマンドからlogfile
、pidfile
コマンド引数が削除されました。デフォルトのシャットダウン時間が8秒から25秒に変更に
HerokuのDynoとAmazon ECS コンテナがアプリケーションのシャットダウンで30秒のタイムアウトを使用するようになったため、Sidekiqも8秒から25秒にタイムアウト時間を伸ばしました。以前の挙動に戻すには、オプション
-t 8
をsidekiq
コマンドに対して指定します。
REDIS_PROVIDER
環境変数の検証を行うように
REDIS_PROVIDER
環境変数は、RedisサーバーのURLを保持する他の環境変数を指定するのに使用します。もし、REDIS_PROVIDER
変数に直接URLなどの不適切な文字列な文字列が指定されている場合は、以下のような警告を出すようになりました。REDIS_PROVIDER should be set to the name of the variable which contains the Redis URL, not a URL itself. Platforms like Heroku will sell addons that publish a *_URL variable. You need to tell Sidekiq with REDIS_PROVIDER, e.g.:参考
パフォーマンスの向上
生成するオブジェクトを減らすなど、細かいコードのチューニングを行うことによって、Sidekiq 5系列と比較して、10-15%実行速度が向上しているそうです。
参考
- Optimize cloning of job payload by fatkodima · Pull Request #4303 · mperham/sidekiq
- Speedup UnitOfWork#queue_name by fatkodima · Pull Request #4299 · mperham/sidekiq
- Reduce allocated objects by fatkodima · Pull Request #4269 · mperham/sidekiq
アップグレード方法
Gemのアップグレード
最初に、Gemfileに以下のように指定して、
bundle update
を実行して、Sidekiq 5系列の最新版にアップグレードします。gem 'sidekiq', '< 6'Sidekiqを動かしてみて、非推奨の警告(Deprecation warnings)が出ないようにコードの修正を行います。
修正が完了したら、Gemfileに以下のように指定して、再度
bundle update
を実行して、Sidekiq 6.xへアップグレードします。gem 'sidekiq', '< 7'サービスの設定ファイルの作成
変更点で述べたように、本番環境では、Sidekiqプロセスはデーモンでなくサービスとして起動するようになったため、設定ファイルを作成する必要があります。
開発環境では、これまで通りユーザーが
sidekiq
コマンドを実行してフォアグラウンドで起動し、ログは標準出力および標準エラー出力に送信されるため、サービスとして起動させる必要はありません。SystemdでSidekiqをサービスとして実行するには、以下のファイルを元に設定ファイルを生成し、サーバーの適切な場所(CentOSの場合は/usr/lib/systemd/system、Ubuntuの場合は/lib/systemd/system)に配置する必要があります。
https://raw.githubusercontent.com/mperham/sidekiq/master/examples/systemd/sidekiq.service
ファイルの配置を行ったら、以下のコマンドでサービスを有効にします。
$ sudo systemctl enable sidekiqサービスの起動と停止
設定ファイルを配置後は、以下のコマンドでサービスの起動と停止を行います。
サーバーの起動
$ sudo systemctl start sidekiqサーバーの停止
$ sudo systemctl stop sidekiq参考URL