あとから@vue/cli 3 + Jest

まとめ

いろいろやって、結局丸一日くらいは掛かることが分かった。

気をつけるべきポイント

  • Babel がすでに入っているなら 7系 にちゃんと上げておく
  • @vue/cli は Webpack と一部概念が違う
  • 関係するツールが増えると当然だが検証時間は伸びる

狙い

大きなフレームワークが入っておらず、中途半端に JavaScript が散らかっている状態の小規模のプロジェクトがある。このうち一部のコードに対して

I/O 周り(HTTPS? や localStorageアクセス)に変更を施す必要がある

ことが分かった。そのため

  1. ビルドプロセスに手を入れる
  2. 既存のコードをテスタブルな状態にもっていく

を行う必要があると判断した。

方向

すでに動いているコードは以下の状態。

  • jQuery ベースのコードがある
  • ビルドプロセスを伴わない状態で Vue も採用されている

テストの欲しい変更を行うコードは Vue の方。そのため、

  • @vue/cli
    • webpack
  • jest

を入れることとした。

理由はimport / exportを書くとasset bundlerが必要になるから

I/O 周りのコードに対する変更を行いたいわけだが、このために

  • I/O 周りのコードを Model として分離する

こととした。

必要なのは View の方ではないので、Vue を利用して構築している部分から I/O 周りを分離し、Model だけをテスタブルにしたうえで変更を加えていくのはごく当たり前の判断と言ってよいと思う1。View 周りまで含んだコードをテスタブルな状態に持っていくのはコストが大きすぎる。幸い、Vue の場合は jQuery ベースよりは anonymous function だらけにはなりにくく、「継ぎ」2を作ればテスタブルな状態を作るのはそれほど難しくなさそうに見えた。

そして Model をテストするわけだが、ここで問題がある。

モダンな開発を行うに当たり、コードを責務の単位で分離したうえで import / export して作っていくのは当たり前のことである。しかし、import / export は Babel の領域ではなく、asset bundler の領域。

asset bundler と言えば Webpack … ?

Webpack の設定はしたくない。絶対にだ。

ということですでに Vue が入っているなら Webpack の面倒くささを避けるために @vue/cli だろうという当然の帰結。

やることリスト

  1. 既存の Babel があれば上げておく
  2. まず @vue/cli プロジェクトを別なところで作る
  3. .babelrc などは babel.config.js へ変更3
  4. package.json などなどを別プロジェクトからコピー
  5. webpack の multi entry は @vue/cli 側で multi page として再定義されているのでそっちを参考に
  6. .eslintrc.js を追加4
  7. Jestの導入(以下のようになる場合アリ)
    • [NG] @vue/cli-plugin-jest-unit
    • [OK] jest

意外とハマる

特に

  • Multi Page という @vue/cli 独自の概念
    • このために HtmlWebpackPlugin とか、あまり欲しくない知識が必要になる5
    • @vue/cli のデフォルト設定の entry 周りから multi entrypoints に持っていく方が面倒
  • @vue/cli が .eslintrc.js を無視してしまう問題
  • @vue/cli-plugin-jest-unit がなぜか babel-jest を適切に適用できない問題

辺り。

@vue/cli は抽象化のレベルがなかなかよいと思っていたのだが、やはりレイヤーが増えればややこしさは増える。まだまだ伸び代だらけだなぁ。Vue に関してはほぼ本家扱いと言ってよいのでそこまで心配は要らないだろうが、Babel, Webpack, Jest は繋ぎの部分の作り込みのレベルにバラツキがあるように見えるので注意が必要。

また Jest が Babel 7 と合わない時代もあったりしたようで、Babel と Webpack の migration は今後も面倒ごとになり続けそう。

参考

  1. この Model 相当のコードが複数箇所から参照されるのは分かっていたのでその判断を加速させたが、それがなくても分離はした方がよい。 

  2. sprout classとかアンチレガシーのプラクティスのこと。 

  3. @vue/cli-service, jest が自動で認識してくれる 

  4. @vue/cli 3.1.1 はバグで .eslintrc.js を無視するので直っているバージョンに上げておくこと 

  5. 読み込み用の HTML も不要だし、後始末が必要になったり。 

今さらRails3メモ - その5: Model Association -

まずは設計以前の話から。(というか設計は語れません。)

あと、Rails ガイド読むならこのエントリ要らない。

Ruby on Rails Guides: A Guide to Active Record Associations

基本概念

用語

DBMS 用語としては entity の relation だと思うんだけど、Rails 的には クラスベース OOP の用語をそのまま拝借して Model(クラス)の association と呼ぶらしい。

DBMSの制約はただの約束

  • 外部キー制約 = リレーションシップではない
  • DBMS はこの制約を無視して構わない
  • リレーションシップはテーブル設計を開発者が「そう決めた」から

リレーションシップを設定するのが外部キー制約ではないことに注意してください。外部キー制約は、列の値がターゲットテーブルの既知のキーを参照するかどうかチェックすべきことを、データベースに指示するだけです。DBMS は、この制約を無視して構いません(実際に MySQL の一部のバージョンは無視します)。テーブル間リレーションシップが設定されるのは、開発者が product_id 列と order_id 列に products テーブルと orders テーブルのキー値を入力することを決めたからです。

(『RailsによるアジャイルWebアプリケーション開発』第1版 p232)

「要は外部キーでしょ」と分かった気になっていただけでに少なからずショック。

以下、とりとめもなく感想とか。Before Rails な世界の住人としては最近のアプリの DBMS の schema dump とか見ると制約が全然なくてすごくスカスカした感じがするんだけど、DBMS の制約を使うと変更に弱くなるので使わない、という方針なんだよね。逆に Model の中身は制約というか validation がズラッと並んでいて「おぉこれは」という感じになってる。つまり Model の方に注目しなきゃいけない。だから Model を利用したうえで ER 図っぽく起こしてくれる

Rails ERD – Entity-Relationship Diagrams for Rails

みたいな道具は必須だなと感じた。こういうのなかった時代はどうしてたんだろう、ほんと。素朴な ER 図作成ツールとか使っても実態と合わないわけでしょ?

key と id

  • ActiveRecord のデフォルトルールでは primary key は `id' 列(自動でセットされる)

attribute 上では変更は可能。

class Model < ActiveRecord::Base
  set_primary_key "code"
end

ってやったら attribute 上は code が primary key になる。ただし migaration の方もいじっておかないと DBMS 上では id のまま。のはず。一応確認したけど間違ってたらごめん。

  • 同じく外部キー列の名前は #{table}_id

これも

class Model < ActiveRecord::Base
  belongs_to :foo, :foreign_key => 'foreign_key'
end

のように association の option で変更できる。

基本的なAssociation

  • belongs_to
  • has_one
  • has_many

基本的には Rails ガイドを読めば分かる。なめちゃいけない、すごく丁寧。

中でも belongs_to を忘れると話にならないのでとりあえずこれだけ。

class Model < ActiveRecord::Base
  belongs_to :table
end

そして自分の所属する table の id ( primary_key ) を保持するカラムを作る migration を用意する。

class AddForeignkeyToModel < ActiveRecord::Migration
  def self.up
    add_column( :models, :table_id )
  end

  def self.down
    remove_column( :models, :table_id )
  end
end

こんな感じ。

この部分は自然な読みやすさを重視してるらしく、has_many だと

class Model < ActiveRecord::Base
  has_many :tables
end

になったりする。自然かもしれないけど、決まってないと不安な気もしないではない。

オプションもいっぱいある。

Module: ActiveRecord::Associations::ClassMethods

先に挙げた foreign key の他にもいろいろ。

応用Association

  • has_and_belongs_to_many
  • has_many , :through =>
  • has_one, :through =>
  • belongs_to , :polymorphic => true

has_and_belongs_to_many

Module: ActiveRecord::Associations::ClassMethods

いわゆる 多:多, M:N と呼ばれる関連を表現する。has_and_belongs_to を相互に書くことで結合テーブルを隠蔽した多:多の association を定義できる。

※ 素朴な話をすると 多:多 っていう relation は存在しなくて、実際この association も隠蔽されているだけで結合テーブルを利用する。DBMS の基本が分かっているなら 多:多 も特別怖がる必要はない。あくまで ORM は DBMS の使い勝手をよくするもので DBMS の機能に変化はない。

具体的には

Foo < Model
  has_and_belongs_to_many :bars
end

Bar < Model
  has_and_belongs_to_many :foos
end

と定義すると

foos_bars

という結合テーブルを使って相互に更新される。migration は以下の通り。

class FoosBars < ActiveRecord::Migration
  def self.up
    create_table :foos do |t|
      ...
    end

    create_table :bars do |t|
      ...
    end

    create_table :foos_bars, :id => false do |t|
      t.column :foo_id, :integer, :null => false
      t.column :bar_id, :integer, :null => false
    end
  end

  def self.down
    drop_table :foos
    drop_table :bars
    drop_table :foos_bars
  end
end

primary key を持たない(should not have)結合テーブル を手動で migration して作らなければならない(you must manually generate)。

この場合、結合テーブルに該当するモデルは定義する必要がない。言い換えると結合テーブルを操作することはできない。

class Foo
  bars.push_with_attributes( barのレコードオブジェクト, column symbol => 値 )
end

という形で参照先のテーブルを更新する。

has_(one|many), :through =>

結合テーブルを利用した association に独自の何かが欲しい場合もある。先ほど隠蔽した 多:多 の association を表現する Model を定義し直すと以下のようになる。

class Foo < Model
  has_many :bar, :through => :foo_bar
end

class Bar < Model
  has_many :foo, :through => :foo_bar
end

FooBar < Model
  belongs_to :foo (, :dependent => :destroy )
  belongs_to :bar (, :dependent => :destroy )
end

明示的な Model を作ると独自の処理を自然に記述しやすくなるし、明確な意味を持たせやすい。

上の例では結合テーブルのような名前になっているが、当然名前も自由に決められる。このテーブルの意味が明確になる名前に設定するとよい。

Polymorphic

Model の関係を固定せず、似た association を複数実現する機能。仮想のテーブルをここでは :polymorphable とした場合に具体的に Model 上の記述では

Foo < Model
  belongs_to :POLYMORPHABLE, :polymorphic => true
end

Bar < Model
  has_XXX :foo, :as => :POLYMORPHABLE
end

の組み合わせで実現する。

この際、migration では

create_table :foos do |t|
  t.references, :POLYMORPHABLE, :polymorphic => true
end

と定義する。実際にはこれは

create_table :foos do |t|
  t.string,  :POLYMORPHABLE_type
  t.integer, :POLYMORPHABLE_id
end

という形に展開される。大文字の POLYMORPHABLE はもちろん小文字で。

※ この部分には名詞ではなく形容詞の -able にするのがパターンとして多いみたいだけど、明確な理由は分からない。恐らく複数形への変化の影響を受けないとか :as => -able が自然に読めるとかそういうことだと思う。

特徴は

  • belongs_to では物理的なテーブル名ではなく、仮想のテーブル名を指定し、:polymorphic => true を付加
  • has 側では物理的なテーブル名を指定するが :as を使って仮想のテーブル名も指定する
  • belongs_to 側の table のカラムには has な table 名は直接入らない
    • (ここまでに登場した association では bar_id というカラムがあるはず。)
  • belongs_to 側の table は has 側の Model 名を #{POLYMORPHABLE}_type に格納する
    • ここで Model 名が可変になっているので association を柔軟に変更できる

よく例に挙がるのは画像を格納するテーブルを Polymorphic で紐づける方法。例えば RSS 2.0 などでは blog そのもの、entry それぞれなどに画像を指定できるし、その数も決まっていない。したがって

class Blog < Model
  has_one :image, :as => :imagable
end

class Entry < Model
  has_many   :images, :as => :imagable
  belongs_to :blog
end

class Image < Model
  belongs_to :imagable, :polymorphic => true
end

のように関係を記述できる。この場合、

  • :imagable_type に 'Blog' か 'Entry' が入る
  • :imagable_id にそれぞれの Model の id が収まる

ことで複数の Model に belongs_to することができる。若干分かりにくいがとても便利。

上の例の Entry のインスタンスで .images を呼ぶと :image テーブルから自身に紐づいているレコードを取得できる。以下のような感じ。

entry = Entry.find(params[:id])
entry.images

単一テーブル継承との組み合わせに注意

あまりこんなことはやらないかもしれないけど。

  • Polymorphic で使われる Model クラス名は DBMS のテーブルに紐づくクラス名
  • 単一テーブル継承の子どものクラス名を格納してしまうと検索できない

ので注意。

参考

gem メソッドで gem ライブラリのバージョンを指定

21:07:06 wtnabe< ruby で require 時にバージョン指定することってできない

のかな
22:23:11 ma2> @wtnabe gemを使わないとダメなんじゃないでしょうか。
22:25:20 wtnabe< @ma2 えっと、require_gem でできるってことですか?
22:30:49 wtnabe< あー Kernel#gem か。なるほど。
23:15:35 wtnabe< @ma2 gem 'GEM_NAME', 'version'; require 'GEM_NAME' で
すね。ありがとうございます。
$ ri Kernel#gem
------------------------------------------------------------- Kernel#gem
     gem(gem_name, *version_requirements)
------------------------------------------------------------------------
     Use Kernel#gem to activate a specific version of +gem_name+.
(snip

例えば以下のように mechanzie 0.8.5 と 0.9.2 がインストールされているとします。

$ gem list -a mechanize

*** LOCAL GEMS ***

mechanize (0.9.2, 0.8.5)

普通に require すると当然バージョンは 0.9.2 になりますが、

$ ruby -e '
require "rubygems"
require "mechanize"
puts WWW::Mechanize::VERSION
'
0.9.2

gem メソッドでバージョンを明示するとそのバージョンを使うことができます。

$ ruby -e '
require "rubygems"
gem "mechanize", "0.8.5"
require "mechanize"
puts WWW::Mechanize::VERSION
'
0.8.5

この辺を見ると

RubyGems User Guide | RubyGems Manuals

ある特定バージョンだけでなく、「以降」、「以前」、これらを組み合わせた「範囲」も記述できるようです。なるへそ。

sh づいている日々

高級なスクリプトでも当然書けるんだけど、例えばあるパターンで行を処理してごにょごにょ、とかって処理は結構 awk と組み合わせて sh スクリプトで書いた方がやりやすかったりする。で、ちょっと最近凝った sh スクリプトを書くようになってハマったのが

  1. 引数のチェック
  2. エラーの意味がよく分からん

の辺り。

1 については getopt と case の組み合わせをマスターしないと面倒くさくなりそうなんで、これが今後の課題。1

2 については xtrace を使ったり verbose にするとよいらしい。つまり起動時に -xv をつける。ただしこれはオリジナル sh のマニュアルがないと気づきにくい。

特に bash のマニュアルはあんまりよくない。bash のマニュアルしか手元にないようなら、set コマンドのところを見る。-o option-name のところに same as -x と書いてあれば、それはコマンドラインオプションの -x と同じだよという意味になっているんだけど、コマンドラインオプションの説明なんか端折りまくりじゃないか。

zsh の場合は man zshoptions の sh/ksh emulation set に書かれている。これはまだ分かりやすい。

というかオリジナルの sh のマニュアルくらい用意しておいてくれよ。

というわけで最近物色していた shell 関係の本は以下の通り。

: ASCII の プロフェショナル・シェルプログラミング のページも参考まで。昔からものすごく気になっていたもの。今の自分なら csh についての説明は要らないし、基本的な部分の説明も要らないんだけど、安いしコンパクトだし、名前に反して入門向けとしていいと思う。もし後輩が買おうかどうしようか迷ってるんですと言ってきたら間違いなく薦める。昔のこういう ASCII の本は大好きだ。あ、自分の手元には 、 があるんで、スクリプトだけじゃなくてシェル上の操作も含めて基本からっていう話ならこれ貸すかな。

: とても詳しいし、イチからきっちり勉強したいんですっていう人にはオススメ。上の -xv オプションも書かれている。というかこれ、前の版から絶賛されているらしいのでここに挙げるのは今さらか。

: 一冊置いてあると絶対安心できると思う。移植性の辺りは他にあまり扱ってる本がないので重宝するかと。

 
コンパクトにまとまっていてかつ素人が素人向けに書いた Web 上の情報よりはるかに使えると思う。日本で書き下ろされた小さい本よりさらに安い(はずだ)し。なんかいま日本の Amazon には在庫ないし思いっきり bash って書いてあるけど。
  1. 例えば $2 が存在しているかどうかはいきなり [ $2 ] って書いてもうまく判別できないみたい。しかも標準状態ではエラーメッセージの意味がよく分からない。 

CLIE 終了ですか

その前に自分の中のちっこい機械好きがなんかどうも終了してるっぽいのですが。

実はポケコンから HP200LX へ行った由緒正しきキーボード付きのちっこい機械好きなのですが、PDA サイズのものに結局魅力を感じなくなって HP200LX 以降はまったく手にしていません。理由のいちばんは

  • 電車移動じゃないから

というものですが、それ以外にも

  • なんだかんだで(キーボードも含めて)フル機能ほしくなっちゃうから、ノート PC の方がいい
  • PC と PDA で違う操作方法(アプリ)を用意して、データをシンクロして、、、って考えるだけで面倒くさい
  • もう少し出せばノート PC 買える値段を投じられるほど余裕がない

という辺り。型落ちモバイルノートをメインマシンにしてしまえば上の3つの問題を全部解決できるのでそうしてます。メインをデスクトップにしてりなざうとかにすればいいじゃんという指摘もあるかもしれないけど、なんかどうもデスクトップの「決まった場所に座らないといけない」という制約がそもそもきらいなのね。きらいなのです。

ということで、そういう使い方をし始めて、、、もう10年経つのか。うわーどーゆーことだーorz

スケジュールとかメモとかは今はもう携帯でいいです。基本的には PC で整理して携帯にメール、携帯でメモして PC にメール、だけで済ましてます。特別なツールは使わず、Wiki からメールに、メールから Wiki にコピペしてるだけ。どうせそんなに大量のデータのやりとりは必要ないから。携帯用フルキーボードが安価に手に入ったらもっとヘビーに使うかもしんないけど、それも最初のうちだけだろうなぁ。

About

例によって個人のなんちゃらです