hotoolong's blog

プログラムのことやエンジニアリングに関することを記事にしています。

Railsのmigrationでchange_columnを利用する

change_column

Railsのmigrationでカラムの情報を変更したい場合によく使うchange_columnですが、

change_column(table_name, column_name, type, options = {})

と変更したいtypeを設定してoptionsを設定すればいいので、add_columnなどとインターフェイスが変わらず設定できます。

change_column_null

NULLのtrue/falseを切り替えたいだけの場合は change_column_nullが便利ですね。

change_column_null(:users, :nickname, false)

このように設定できます。

change_column_comment

カラムに設定するコメントのみを変更する場合はchange_column_commentですね。 nilを入れることで設定しているコメントを削除することもできます。

change_column_comment(:users, :id, 'comment')
change_column_comment(:users, :nickname, nil)

change_column_default

最後にカラムのデフォルト値を変更するchange_column_defaultなのですが、 このメソッドは引数にHashで:from と :to で設定することで可逆的な設定がすることができます。

change_column_default(:posts, :state, from: nil, to: "draft")

これは便利ですね。 テストで作ったけど戻したい等の場合はとても役に立ちます。

mysqldumpでテーブルを対象、対象外にする

mysqldumpを定期的にするのですが、 対象外にしたいテーブルが複数でてきたときの対応方法をメモしておきます。

ダンプファイルと取るときには直接テーブル名を選択すればいいケースもあります。 以下のコマンドですと一番上を選択してデータベースを指定して登録したいテーブル名を後ろに列挙するといいですね。

shell> mysqldump [options] db_name [tbl_name ...]
shell> mysqldump [options] --databases db_name ...
shell> mysqldump [options] --all-databases

MySQL :: MySQL 5.6 リファレンスマニュアル :: 4.5.4 mysqldump — データベースバックアッププログラム

オプションはいろいろあるので見てもらうとして、対象外にしたい場合は--ignore-tableをつければ良さそうです。

ただ複数テーブル対象外にしたい場合は少し厄介で、--ignore-table=db_name.table_name をたくさん作らないといけなくなります。

shellで書くとすると以下みたいにつくります。

  db_host=hogehoge_host
  db_name=hogehoge_db
  db_user=hogehoge_user

  ignore_tables=("tests" "users" "accounts")
  ignore_option_str=""
  for tab in $ignore_tables[@]; do
    ignore_option_str=$ignore_option_str" --ignore-table=$db_name.$tab"
  done
  mysqldump --single-transaction $ignore_option_str -h $db_host -u $db_user -p $db_name

対象のテーブル名の方が少ない場合はテーブル名を列挙して取るのが早そうなので、ケースによって判断して取得するのがいいですかね。

Ruby2.6が早くもpreveiw2をリリース

今年のRubyKaigiは少し大きめのリリースがあったので残念ながら会社でお仕事してました。

Ruby 2.6.0-preview2 Released

preview2がリリースされたということで早速インストールしてみました。
brewからruby-buildを入れているので以下のように設定して置きました。

➜  ~ brew upgrade ruby-build
➜  ~ rbenv install --list | grep 2.6.0
  2.6.0-dev
  2.6.0-preview1
  2.6.0-preview2
➜  ~ rbenv install 2.6.0-preview2
➜  ~ rbenv global 2.6.0-preview2
➜  ~ ruby -v
ruby 2.6.0preview2 (2018-05-31 trunk 63539) [x86_64-darwin17]

これでしばらくruby2.6.0-preview2で生活してみます。

ActiveRecordでOR文を作るときのエラー対処

少し複雑なSQLのOR文をActiveRecordで作るときにはすこし億劫になりますね。

ArgumentError: Relation passed to #or must be structurally compatible. Incompatible values: [:joins, :references]

とエラーが出てしまったのでいろいろ調べてみました。

今回のエラーはRails5.2の以下のコードで ArgumentError が発生しています。

rails/query_methods.rb at c81a7fcf76663e6d189792d6eed57b1162199635 · rails/rails · GitHub

structurally_incompatible_values_for_or が何なのかと見てみると

rails/query_methods.rb at c81a7fcf76663e6d189792d6eed57b1162199635 · rails/rails · GitHub

      STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
      def structurally_incompatible_values_for_or(other)
        STRUCTURAL_OR_METHODS.reject do |method|
          get_value(method) == other.get_value(method)
        end
      end

Relation::VALUE_METHODS が 以下で登録されているのですが、

rails/relation.rb at ac1efe7a947ba04b276a7109f1a86e559a6ab683 · rails/rails · GitHub

[:includes, :eager_load, :preload, :select, :group, :order, :joins, :left_joins, :left_outer_joins, :references, :extending, :unscope, :limit, :offset, :lock, :readonly, :reordering, :reverse_order, :distinct, :create_with, :where, :having, :from]

ここから [:extending, :where, :having, :unscope, :references] を省いた項目が一致してることをチェックしてるので
[:includes, :eager_load, :preload, :select, :group, :order, :joins, :left_joins, :left_outer_joins, :limit, :offset, :lock, :readonly, :reordering, :reverse_order, :distinct, :create_with, :from] の項目が一致してないと ArgumentError になるということですね。
これを個別に対応していくのは大変なのですね。。

これらの項目を設定するメソッドはいろいろ用意されてはいますが、

ActiveRecord::QueryMethods#methods:
  extending_values   group_values     includes_values=  left_joins_values        left_outer_joins_values=  lock_value    offset_value=  preload_values   readonly_value=     reordering_value     reverse_order_value=  set_value
  eager_load_values   extending_values=  group_values=    joins_values      left_joins_values=       limit_value               lock_value=   order_values   preload_values=  references_values   reordering_value=    select_values         unscope_values
  eager_load_values=  get_value          includes_values  joins_values=     left_outer_joins_values  limit_value=              offset_value  order_values=  readonly_value   references_values=  reverse_order_value  select_values=        unscope_values=

a の オブジェクトから b のオブジェクトに移すみたいなことは項目数から考えても面倒ですね。

b.joins(a.joins_values)

項目を並べてget_value と set_value で移すこともできますが、、、

[:includes, :eager_load, :preload, :select, :group, :order, :joins, :left_joins, :left_outer_joins, :limit, :offset, :lock, :readonly, :reordering, :reverse_order, :distinct, :create_with, :from].each do |method|
  b.set_value(a.get_value(method))
end

流石に面倒そうです。

User.
  joins(:accounts).
  group(:id).
  having('count(accounts.id) > 2').
  whrere(created_at: Time.zone.today.all_day)

このSQLにORを付けたいのですが、
当日と5日前に追加されたユーザを調べたいとします。

today = Time.zone.today
relation = User.
          joins(:accounts).
          group(:id).
          having('count(accounts.id) > 2')
relation.where(created_at: today.all_day).or(scope.whrere(created_at: today.days_ago(5).all_day))

このようにローカル変数にor前のRelationを渡してしまってwhereをつくればいいということですね。 少し面倒ですが、これで大丈夫そうです。

スコープを使ったケースを見てみます。

  scope :hogehoge, -> {
     where(created_at: Time.zone.today.all_day)
  }

このようなscopeがあるとします。まだ条件は当日のみです。

User.
  joins(:accounts).
  references(:accounts).
  having('count(accounts.id) > 2').
  group(:id).
  extending(Pagination).
  unscoped.
  hogehoge

先程の処理にhogehogeを追加してみました。 hogehogeのなかでOR文を作ってみます。

  scope :hogehoge, -> {
    today = Time.zone.today
    where(created_at: today.all_day).or(where(created_at: today.days_ago(5).all_day))
  }

ここではローカル変数なしに設定できました。

スコープではない場合は一旦変数に入れないといけないというのは不便ではあります。

これ自体が面倒な場合は String で直接SQLを記載するのがいいかもしれないです。

今回のケースはDateTime型で日付に対する条件になるので BETWEENが使われます。

day1 = today.all_day
day2 = today.days_ago(5).all_day
where('(users.created_at BETWEEN ? AND ?) OR (users.created_at BETWEEN ? AND ?)', day1.first, day1.last, day2.first, day2.last)

このような感じなります。
? に 設定する値がnilだった場合はこれだと面倒になったりするので
ケースによって使い方を選択してもいい気がします。

以下、参考にしたサイトです。

Rails 5 の or を色々試してみた - Qiita

sql - Relation passed to #or must be structurally compatible. Incompatible values: [:references] - Stack Overflow

Unicode エンコードの競合 というファイル名になってしまう

20180419_プロダクションレディマイクロサービス.txt

というテキストファイルをDropboxディレクトリに作っていたのですが、

20180419_プロダクションレディマイクロサービス (Unicode エンコードの競合).txt

という名前に変換されてしまっていました。 何なのかと思い、調べてみると

リンクされている別のパソコンで同期されないファイルがある場合 – Dropbox のヘルプ

ここの中に ユニコード エンコード問題 という項目があり、 Unicode Encoding Conflict とファイル名につくとありますがこれの日本語バージョンのようです。 とりあえず日本語を使うのをやめると解消されていました。 困ったものですね。。

fish shell の時に プロセスを起動し直す

source ~/.config/fish/config.fish

でconfigファイルの再読込はできるようなのですが、

bashzsh のときのように

exec -l $SHELL

を使いたいなと思っていましたがそのままではできないようです。

$SHELLがそのままでは実行できないようで

exec fish

すると上手く読み込んでくれました。 他にいいやり方がある気がしますが、configにfunctionを登録しておくと便利です。

function reload
  exec fish
end

Nodeのバージョンが10.0.0に

Nodeが早くも10.0.0です。 パフォーマンス改善も著しく注目も高いですね。 以下、CHANGELOGの内容です。

github.com

ちょうどNode学園の30時限目がありお邪魔してきました。

speakerdeck.com

speakerdeck.com

Nodeの改善に取り組んできた内容や今回の改善点、今後の展望?のようなことが聞けて為になりました。 HTTP GET のサンプルコードは async await書くことでコード量もスッキリしてとても良さそうです。 ローカルのNodeのバージョンも10.0.0を入れてみて楽しんでみます。