めもめも のーと

ハマったこととか、覚えたこととか

RailsではInteger型のカラムに0始まりの値は登録できない話

これだけ書くと至極当然の話なのだが、ハマったので自戒を込めてメモ。

DB(MySQL)でinteger型のカラムがあり、そのカラムにデータを登録する際に検証ルールを付けたかったが、何故か入力値がvalidationでうまく処理されない。

例)
入力値: 00001
検証時: 1になる <- なぜ!?

原因はその入力値に対応するDBテーブル上のカラムがinteger型だったため、Railsが勝手に数値に変換してvalidation処理をしていた。

この時はXXXコードのような数字のみの項目か何かで、「5桁ちょうどであること」というルールを付けようとしたが、 入力値のパターンが00001の場合、1に変換されて、結果1桁となり検証エラーになってしまった。

development.logのParametersには{ "code" => "00001" }のように出てるから気づかなかった。。

同僚に相談して指摘され、気が付きました。というか、今まで気にしたことが無かった。。

Rubyの%wを使うときは型に気を付けよう

Railsのvalidationルールにinclusionがあるが、含まれる値をinに指定する際には注意が必要。

Rails Guideでも使っているが、配列を表現するために %wを使う場合、中身は文字列になるので、数字を検証したい場合は要注意。

例えば %w(0 1 2)[0, 1, 2]は別物なのである。

%wは文字列扱いになるので、["0", "1", "2"] ということになる。

読み読むとrubydocにも「文字列の配列を簡単に表現できる」と書いてある。 https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#percent

まとめ

%wの使い方をよく知っていなかっただけなので、Railsのinclusionバリデーションが悪いわけではないが、 「数字の0,1のみ受け付ける」という検証が出来ずにはまったので、メモとして残しておく。

collection_check_boxesで登録済みの値にチェックをつけて表示する方法を調べた

Railsで多対多のリレーションのモデルをチェックボックスで表現する際にcollection_check_boxes と言うViewヘルパー関数を使うと便利。 しかし編集時など、登録した値は当然チェックがついた状態で表示されないと困るが、どういう仕組みかの説明が見当たらなかったので調べた。

前提

下記のようなリレーションとなっていることを前提とする。

◆リレーション
post <--> author_post <--> author

◆model定義
class Post < ApplicationRecord
  has_many :author_posts
  has_many :authors, through: author_posts
...

class Author < ApplicationRecord
  has_many :author_posts
  has_many :posts, through: :author_posts
...

class AuthorPost < ApplicationRecord
  belongs_to :post
  belongs_to :author

viewにcollection_check_boxesを下記のように設置

= collection_check_boxes(:post, :author_ids, Author.all, :id, :name) do |b|
  = b.label { b.check_box + b.text }

formオブジェクトを使う場合
= f.collection_check_boxes(:author_ids, Author.all, :id, :name) do |b|
  = b.label { b.check_box + b.text }

  # ※viewにAuthor.allを直接記述するのはよくない

:id:nameは下記のように展開される

<input type="checkbox" value="4" checked="checked" name="post[author_ids][]" id="post_author_ids_4">

value : authorのID
id : post_{第2引数の文字列}_{authorのID}

各項目の意味

各引数の役割はそれぞれ下記の通り

:post 更新対象のオブジェクト(のシンボル名)。form_forでは省略可。

:author_ids 更新対象のオブジェクトが持つメソッド(のシンボル名)。チェックボックスの各チェックに関連付けられるID。登録や更新の際にparameterに含まれるので、StrongParameterの対応が必要。

# app/controller/posts_controller.rb
def post_params
  params.require(:post).permit(:title, {  author_ids: [] })
end

# ここで定義しないと「Unpermitted parameter: author_ids」となって登録や更新がされない。

:id chekcboxのinputタグにてid属性に割り振られる値。かつ、登録済みの値かどうかを比較する対象となる属性(後述)。authorが持つメソッドを指定。

:name チェックボックスに表示されるinputタグのテキストとなる値。authorが持つメソッドを指定。

# 例えばauthorが下記メソッドを持つ場合、:nameのところで代替可能
def honorific
  name + ''
end

まとめ

今回分かってよかったのが、第2、4引数(form_forの場合は第1、3引数)であるauthor_ids:idの関係。

post(正確には第1引数のオブジェクト)はauthor_idsというメソッドを持っており、その結果と第4引数idを比較し、合致する場合にチェックボックスにチェックが付いた状態で画面表示される。(第4引数のidは、第3引数のそれぞれのオブジェクトが持つid。今回ではAuthor.allのそれぞれのauthorのauthor.id)

例えばpostに紐付くauthorのidが1,3,4の場合、post.author_ids = [1,3,4]となり、 Author.allの各authorのidと比較し、合致する場合にcheck属性が付与される。

上記を理解しておけば、登録した値が編集画面でチェックされていない、という場合にも原因の切り分けがしやすくなるので、覚えておこう。

bashで配列の扱い方

bashのshellスクリプトで配列の定義方法と取り出し方についてメモ。

下記のコードで確認します。

#!/bin/bash

declare -A FRUITS;
FRUITS=(["apple"]="100" ["banana"]="120" ["orange"]="150");
for f in ${FRUITS[@]}; do
  echo ${f};
done

declare -a ANIMAL;
ANIMAL=("dog" "cat" "bird");
for a in ${ANIMAL[@]}; do
  echo ${a};
done

実行結果

150
100
120
dog
cat
bird

解説

配列の定義

declare は変数の宣言をするコマンドです。オプションには主に下記があります。

option 意味
-a 配列として定義
-A 連想配列として定義
-i 整数型として定義
-r 参照のみ可能な変数として定義

今回は連想配列と配列について確認しました。 例では変数FRUITSとANIMALを定義し、それぞれ連想配列と配列として扱います。

配列からデータの取り出し方

for文を使って配列からデータを取り出します。 取り出し方はどちらも同じですが、連想配列の場合、以下のようにするとkeyの値が取れます。

declare -A FRUITS;
FRUITS=(["apple"]="100" ["banana"]="120" ["orange"]="150");
for f in ${!FRUITS[@]}; do    # <- ! (エクスクラメーションマーク)を付ける
  echo ${f};
done

# 結果
orange
apple
banana

Rspecで undefined method `validate_uniqueness_of' が出たので対応した件

とても久しぶりにRailsRspecを思い出しながら書いていたら、

NoMethodError:
       undefined method `validate_uniqueness_of' for ...

が出て困って対応したときのメモ。 結論から書くと、Railsでよくあるバージョンの問題でした。

環境

※抜粋
rails (5.0.0.1)
rspec-rails (3.5.2)
shoulda-matchers (2.8.0) <=今回の肝

is_expected.to ensure_length_of とか is_exepcted.to validate_uniqueness_ofなどはshouda-matchers が提供しているテストコードを簡潔に書くためのgemですが、 使用しているRspecのバージョンによっては動かないことがあるようです。

(2016/11/07現在のshoulda-matchers最新バージョンは3.1.1、Rspecは3.5.2)

そこでshoulda-matchersのバージョンをいろいろ変えて試したところ、2.8.0ならエラーがなくSpecが通りました。

# Gemfile に下のようにバージョンを指定して bundle install

gem 'shoulda-matchers', '~> 2.8.0'

結構、はまりました。

FuelPHPのDB操作で「mysqli::mysqli(): (HY000/2002): そのようなファイルやディレクトリはありません」が出た件

FuelPHPマイグレーションを実行しようとしたら、次のエラーが出て失敗したので、その時の対処法を残しておきます。

$ oil refine migrate
caught exception Fuel\Core\Database_Exception: mysqli::mysqli(): (HY000/2002): そのようなファイルやディレクトリはありません

環境

vagrant上に環境構築
php : 5.6.25
fuelphp : 1.8
mysql : 5.6.33

対応

エラーメッセージで検索すると、mysql.sockがphp.iniに指定していないと起こる、とのことだったので、指定してみます。

php.iniの編集前(no valueになっている)
$ php --ri mysqli

mysqli.default_socket => no value => no value


mysql.sockの場所を確認し、下のように編集
$ vim /home/vagrant/.phpenv/versions/5.6.25/etc/php.ini

mysqli.default_socket = /var/lib/mysql/mysql.sock


改めて確認
$ php --ri mysqli

mysqli.default_socket => /var/lib/mysql/mysql.sock => /var/lib/mysql/mysql.sock

マイグレーションを実行
$ oil r migrate
Performed migrations for app:default:
001_create_form

mysql.sockを指定してもダメな場合もあるようです、今回はうまくいったので良かったです。 他のハマりポイントとしては、fuel/app/config/db.php にてmysqliとPDOで記述が若干異なるため、間違えると上手く動かないようです。

# PDOの場合
return array(
    'default' => array(
        'type'  => 'mysqli',
        'connection'  => array(
            'dsn' => 'mysql:host=localhost;dbname=fuel_dev'
            'username' => 'username',
            'password' => 'password',
        ),
    ),
);
# mysqliの場合
return array(
    'default' => array(
        'type'  => 'mysqli',
        'connection'  => array(
            'hostname' => 'hostname',
            'username' => 'username',
            'password' => 'password',
        ),
    ),
);


参考にさせていただきました!

kimagureneet.hatenablog.com

psqlのバージョンをあげた話

CentOS6系でyumでインストールしたPostgreSQLpsql で操作すると、以下のような警告が毎回出るので、対応した手順をメモします。

# psql -U postgres
psql (8.4.20, server 9.3.10)
WARNING: psql version 8.4, server version 9.3.
         Some psql features might not work.
Type "help" for help.

PostgreSQLのバージョンは9.3.10

postgres=# SELECT version();
                                                    version
----------------------------------------------------------------------------------------------------------------
 PostgreSQL 9.3.10 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16), 64-bit
(1 row)

原因は、psqlの古いモジュールが使われているせいみたいなので、 rpmの古いモジュールをアンインストールします。

# rpm -qa | grep postgres
postgresql93-9.3.10-1PGDG.rhel6.x86_64
postgresql93-server-9.3.10-1PGDG.rhel6.x86_64
postgresql-libs-8.4.20-4.el6_7.x86_64
postgresql93-libs-9.3.10-1PGDG.rhel6.x86_64
postgresql93-contrib-9.3.10-1PGDG.rhel6.x86_64
postgresql-devel-8.4.20-4.el6_7.x86_64
postgresql-8.4.20-4.el6_7.x86_64

元から入っていた(?)8.4.20系のモジュールを削除します。

# rpm -e postgresql-devel-8.4.20-4.el6_7.x86_64
# rpm -e postgresql-8.4.20-4.el6_7.x86_64
# rpm -e postgresql-libs-8.4.20-4.el6_7.x86_64

psqlコマンドを使うと、パスが無くなってしまっているのでエラーになりました。

# psql
-bash: /usr/bin/psql: No such file or directory

9.3.10系のパスは /usr/pgsql-9.3/binなので、あとはどういう形でもいいのでパスを通せば完成です。 今回は下記一行を新規作成したファイルに追記しました。

# vim /etc/profile.d/psql93.sh
export PATH=$PATH:/usr/pgsql-9.3/bin

警告が出なくなりました。よかった。

# psql -U postgres
psql (9.3.10)
Type "help" for help.

vagrantで作った環境に元から8.4.20系のpostgresモジュールが入ってたのかも知れません(未確認。。)