前人未踏の領域へ WEB・インフラ・プログラミング全般編

フロントエンド、バックエンド、インフラ、言語など、アプリ開発、IOT以外の記録

RubyMineのRSpec実行時にテストが固まる

課題

Mac上のRubyMineからRSpecを実行しているが、config内のファイルやModelクラスのvalidationなどを編集すると テストを実行しても途中で止まってしまい、RubyMineを再起動しないとテストが実行できなくなった。

原因

springのリロードがうまく機能していない。 プロセスにはspringがいるが、statusを見ると起動していないと言われる。

$ ps -x|grep spring
28063 ??         0:00.35 spring server | hoge | started 1 min ago  
28083 ??         0:00.43 spring app    | hoge | started 0 secs ago | test mode      
28105 ttys001    0:00.00 grep spring
$ spring status
Spring is not running.

RubyMineでは SPRING_TMP_PATH という環境変数で定義されたパスに個別のspringインスタンスが生成される。 この変数は {temp_dir}/RMSpring と等しい値になる。 なので以下のようなコマンドを叩くと

$ SPRING_TMP_PATH=${TMPDIR}/RMSpring bin/spring status
Spring is running:

41298 spring server | hoge | started 5 secs ago

springが実行されていることがわかる.

対応

springを停止する

SPRING_TMP_PATH=${TMPDIR}/RMSpring bin/spring stop

でもなんか駄目なことが多い気がする。そんなときは kill してしまおう。

$ kill 28063

参考

www.jetbrains.com

RSpecでvalidates_dateの日付テストがうまくいかない

課題

当日の日付との比較を行う処理を validates_timelinesson_or_before を使って記述しているが、RSpecにテストを記述する際に日付の固定がうまくできず( Timecoptravel_to もNG )、現在日時との比較になってしまう。

  # 未来日の入力はエラーとする
  validates_date :date, on_or_before: Time.zone.now.to_date

原因

日付固定の外で対象のクラスが読み込まれてしまっている。

RSpec.describe Hoge, type: :model do
  before(:each) do
    # Timecop.freeze(Time.zone.local(2019, 10, 10))
    travel_to Time.zone.local(2019, 10, 10)
  end

ファイルの前後に日付をセットしていたので内部で実行されるクラスには固定した日時が採用されるかと思っていたが、RSpec.describe Hoge の行が評価される時点で Hogeの読み込みが行われ、validatesに定義している処理は時間を固定する前の値で評価されてしまっていたようだ。

対応(旧)

クラスを引数にするから評価されてしまうからそれを回避しよう、ということで文字列にしてみた。

RSpec.describe 'Hoge', type: :model do

この書き方だと何か別な問題があるのかもしれないがテストは通るようになった。

対応(新)

validates のところで直接時刻評価を記述しているから評価されてしまうので、メソッドに退避してみたら上手くいった。

RSpec.describe  Hoge, type: :model do

validate :day_after_tomorrow
private

def day_after_tomorrow
  unless errors.include? :date
    validates_date :date, on_or_before: Time.zone.now.to_date, allow_blank: true
  end
end

ちなみにメソッド名と中身は一致してない。

特定のタグをすべて含むアイテムを検索するSQL

全文検索エンジンとかを使わない普通のRDBの従来のテーブル構成の場合。
気づくまで時間がかかったのでメモっておく。ER図もないので分かりにくいけど。。

課題

あるアイテムに複数のタグが関連テーブル経由の外部テーブルとして紐づいていいる。 選択されたタグをすべて含むアイテムのみを抽出したい。どうすればよいか。

対応

1 まず対象となるタグIDを持つタグを得る

select id from tags where name in ('tag1', 'tag2')

2 そのタグIDを持つアイテムタグ関連テーブルを得る

select item_id from items_tags
   where tag_id in ( select id from tags where name in ('tag1', 'tag2') )

3 全てのタグを両方含む必要があるので、item_idでグルーピングしてカウントがタグの数と一致したものみ抽出する

select item_id from items_tags
where tag_id in ( select id from tags where name in ('tag1', 'tag2') )
group by item_id having count(item_id) = 2)

4 最後に得られたitem_idでitemテーブルにクエリーを投げればOK

select * from items
where id in 
 ( select item_id from items_tags
   where tag_id in ( select id from tags where name in ('tag1', 'tag2') )
   group by item_id having count(item_id) = 2)

LinuxベースのDockerからMySQL 8.0に接続するための記述

課題

MySQL8にDocker上に構築したRailsからアクセスしようとしたら以下のようなエラーが発生して接続できなかった。

Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/mariadb19/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory

注意点

本記事は caching_sha2_password 問題でハマった人向けであるが、Amazon RDS for MySQL 8.0は mysql_native_password なのでAWSで運用してしようとしている人は異なる対応が必要なので注意(参考記事を参照のこと)

原因

8.0.4からMySQLのデフォルトの認証プラグインcaching_sha2_password に変更になった。
LinuxではMySQLクライアントとしてMySQL互換のMariaDBが使用されるため、mysql_native_password で接続しようとしてエラーとなっている

対応

記事での解説範囲。とりあえずこんな感じで接続できるようになった。

FROM ruby:2.6.5

WORKDIR /tmp
RUN apt update && apt install -y lsb-release \ 
    && apt remove -y libmariadb-dev-compat libmariadb-dev

RUN wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-common_8.0.18-1debian10_amd64.deb \
    https://dev.mysql.com/get/Downloads/MySQL-8.0/libmysqlclient21_8.0.18-1debian10_amd64.deb \
    https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-community-client-core_8.0.18-1debian10_amd64.deb \
    https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-community-client_8.0.18-1debian10_amd64.deb \
    https://dev.mysql.com/get/Downloads/MySQL-8.0/libmysqlclient-dev_8.0.18-1debian10_amd64.deb

RUN dpkg -i mysql-common_8.0.18-1debian10_amd64.deb \
    libmysqlclient21_8.0.18-1debian10_amd64.deb \
    mysql-community-client-core_8.0.18-1debian10_amd64.deb \
    mysql-community-client_8.0.18-1debian10_amd64.deb \
    libmysqlclient-dev_8.0.18-1debian10_amd64.deb

解説

対策は2案

  1. MySQL8対応のディストリビューションを使ってビルドする
  2. Linux上にMySQL8系のライブラリをインストールする

1はUbuntuのeoanがMySQL8に対応していたのでそれをビルドイメージにすればいけそうな気がする。 今回はDockerHubにあるRubyイメージからRails用イメージを構築していきたかったので2で考えてみる。

Dockerファイルを作る

1. ベースイメージ

Rubyのベースのディストリビューションもいろいろあるのだけど標準のにしてみた。このimageはDevian 10 busterをベースにしている。

FROM ruby:2.6.5

2. 依存ライブラリの追加と競合ライブラリの削除

lsb-release がないと怒られるので追加する。 同時に、mariadb系のライブラリ系が残っているとmysql8系のライブラリがコンフリクトして入れられないので削除する

libmariadb3mariadb-common は削るとRails(mysql2)から接続できなくなるので注意。ただしmysqlコマンドはなくても通る。

RUN apt update && apt install -y lsb-release \ 
    && apt remove -y libmariadb-dev-compat libmariadb-dev

MySQL 8のライブラリをダウンロード

最新バージョンのパッケージとなるとaptでは配布されていないため、 MySQLのホームページからクライアント系のパッケージをダウンロードしてインストールする。

dev.mysql.com

必要なものは以下の通り。クライアント系は全部入れておく。もしかしたら不要なものも混ざっているかもしれない。

RUN wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-common_8.0.18-1debian10_amd64.deb \
    https://dev.mysql.com/get/Downloads/MySQL-8.0/libmysqlclient21_8.0.18-1debian10_amd64.deb \
    https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-community-client-core_8.0.18-1debian10_amd64.deb \
    https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-community-client_8.0.18-1debian10_amd64.deb \
    https://dev.mysql.com/get/Downloads/MySQL-8.0/libmysqlclient-dev_8.0.18-1debian10_amd64.deb

dpkgコマンドで入れる。

RUN dpkg -i mysql-common_8.0.18-1debian10_amd64.deb \
    libmysqlclient21_8.0.18-1debian10_amd64.deb \
    mysql-community-client-core_8.0.18-1debian10_amd64.deb \
    mysql-community-client_8.0.18-1debian10_amd64.deb \
    libmysqlclient-dev_8.0.18-1debian10_amd64.deb

以上。 あとは普通にRaisのDockerfileを作ればよい(省略)。
安易に mysql_native_password に逃げたくなかったので時間をかけて調べたものの、caching_sha2_passwordAWSで使えないということが判明して以降はただの意地であった。

参考

IdeaVimプラグインを使いつつTabキーによるインデントを可能にする

課題

JetBrailsのIDEにIdeaVimプラグインをインストールしているとエディタで複数行選択してからTabキーでインデントさせる処理が動作しない

対応

IdeaVimはVimでいうところの vimrc に相当する ideavimrc に対応しているらしい。.ideavimrc を起動時に読み込んでくれるのでそちらを編集して機能を追加する

$ vim ~/.ideavimrc
nnoremap <Tab> >>_
nnoremap <S-Tab> <<_
inoremap <S-Tab> <C-D>
vnoremap <Tab> >gv
vnoremap <S-Tab> <gv
$ source ~/.ideavimrc

上記は調べてないけどVimの設定周りの常識テクなのかもしれない。 またVim使いならVimで行選択してインデントする方法(V>>)をご存知だろうからそれでも問題ないが、
自分のような既存のショートカットも含めて使用する、いいとこ取りのVimユーザーからすると連続インデントなどのときにTabキー使えた方が便利。

参考

stackoverflow.com

schema.rbが実行エラーになる

課題

MySQLの生成カラムを使ったテーブル定義を行ったところ、構文内に含まれる文字列が schema.rbでエスケープされ、SQL実行時にエラーとなってしまう。

やりたかったこと(例)

年と月のカラムがあるのでdate型の生成カラムを作ってみた。

ALTER TABLE t1  ADD COLUMN c1 INT 
   GENERATED ALWAYS AS (str_to_date(concat(`year`, _utf8mb3'-', `month`, _utf8mb3'-', 1), _utf8mb3'%Y-%m-%d'))

すると以下のような感じに変換される。シングルクウォートがエスケープされているのがわかる。

create_table t1 |t|
  t.virtual "c1", type: :date, as: "str_to_date(concat(`year`,_utf8mb3\\'-\\',`month`,_utf8mb3\\'-\\',1),_utf8mb3\\'%Y-%m-%d\\')"
end

まあこんな変なことしようとしているのが悪いというのは別な話であるとして。

対応

schema.rbを編集したり、生成されるクエリーをエスケープさせない対応は難しそうなので、データベーススキーマをファイルに書き出す際のフォーマットをデフォルトのRubyからSQLに変更することで対応。

#config/application.rb

class Application < Rails::Application
  config.active_record.schema_format = :sql
end

スキーマの書き出しは以下の通り。schema ではなく structure になっているのがポイント。 db/structure.sql が生成される。

db:structure:dump

schema.rbは削除してしまって問題ないだろう。

参考

blog.toshimaru.net

オブジェクトの配列から文字列の配列を作る

課題

Rubyでこんな感じのオブジェクト配列があったとする。

        "tags": [
            {
                "id": 7,
                "name": "tag1"
            },
            {
                "id": 8,
                "name": "tag2"
            },
            {
                "id": 9,
                "name": "tag3"
            }
        ],

これをStringの配列で返却したい。

    "tags": [
        "tag1",
        "tag2",
        "tag3"
    ]

対応

collectを使い、要素内の値を抽出して新しい配列を作る

json.tags do
  json.array! @transaction.tags.collect {|t| t.name}
end

参考

rails jbuilder - just an array of strings - Stack Overflow

/* Responsive: yes */