オレオレORM「Moco」についてのお気持ち

去年辺りからチマチマとMocoというオレオレORMを作っているので技術的な話ではなくお気持ち的な話を書いてみたくなった。
技術的な話は気が向いたら別の記事にしようかなと思ってるけどまだ気分ではないかな。

Mocoの思想

JavaでDB操作をする時は自分でゴリゴリSQLを書く薄いORMが好んで使われる。DomaとかSpring JDBCとか使ったことないけどMyBatisもそうなのかな?
自分でSQLを書くことには僕は抵抗がないしDomaとかは個人的に結構好きだったりする。ただ、遊びでピャッっとWebアプリ作ろうかなになった時、リレーションをJavaの世界で組み立てるのが結構面倒だったりする。複数のCustomerに各Customerが複数のOrderを持っているケースでJavaのオブジェクト煮詰めていくのが結構面倒くさいな~と思ったりしてた。
その時に出会ったのがActiveRecordのpreloadで、ActiveRecordでは以下のように書くことができる。

customers = Customer.preload(:orders).to_a
orders = customers[0].orders

preloadに関連を書くだけで良い!なんだこれは便利すぎる…。あとSQL書かなくてよいの意外と楽だぞ!?けど補完があまり効かないしそこはJavaのほうが気持ちが良いんだよな…Javaならもっとうまくできるんじゃないのか?となったのが開発着手までの流れです。
なので「リレーションをうまく扱いたいね」と「ちゃんと補完バリバリできる方がいいよね」という思想がベースにあります。

Mocoという名前

Mocoという謎の名前ですが、これはセブンイレブンの「しろもこ」シリーズから取っています。なのでモジュール名もシュークリームっぽい感じにしてます。僕がすごい好きなイラストレーターがFANBOXで定期的にもこシリーズの話を書いていたのでそこから取りました。
ちなみに今まで食べた中ではティラミスもこが一番好きでした。

どうやって気持ちよく書くか

Javaの話に戻って、ActiveRecordっぽいものをJavaでやろうとしたライブラリはいくつかあるようですが、Stringで頑張ろうとしている辛いものばかりでした。MocoではPluggable Annotation Processing APIを使ってコンパイル時にクラスを生成することを選択しました。
POJOだけユーザーが定義して、そこからいい感じ操作をするためのクラスを二種類生成します。
1つ目はPOJOの複数形名のクラスで、カラムを表す定数や後述するEntityListを作るstaticメソッドが生えているTableClassと呼んでいるクラス。
2つ目はEntityListと呼んでいるクエリを作って結果を得るためのクラスです。

これらを使うことで以下のように書くことができます。

// このクラスはユーザー定義
@Table(name = "customers")
public class Customer {
    @Column(name = "id", generatedValue = true, unique = true)
    private int id;
    @Column(name = "name")
    private String name;
    @Column(name = "city")
    private String city;
    @Column(name = "company_id")
    private int companyId;

    @BelongsTo(key = "company_id", foreignKey = "id")
    private Company company;
    @HasMany(key = "id", foreignKey = "customer_id")
    private List<Order> orders = new ArrayList<>();
    // getter...
}

// CustomersとCustmerListはコンパイル時に生成される
// Tokyoに住んでいる顧客
Customers.all().where(Customers.CITY.eq("Tokyo")).toList();

// preloadで関連を一緒に引いてマッピングする
// noteを買ったOrder一覧
 List<Order> noteOrders = LineItems.all()
        .where(LineItems.NAME.eq("note"))
        .preload(LineItems.LINE_ITEM_TO_ORDER)
        .stream()
        .map(LineItem::getOrder)
        .collect(Collectors.toList());

今後やりたいこと

今は一段のpreloadしかできないですが、近いうちにActiveRecordのpreload(hoge: :fuga)のような多段preloadをできるようにしたいと考えています。
どう書けば気持ち良いんだろうね…。

こう書けたほうが気持ち良いはず!などあればIssueやPRを送っていただけるとうれしいです。

github.com

また、試してみたいだけの方はSQLiteで試せるサンプルリポジトリがあるのでこちらで遊んでみてください。

github.com

ジェネリクスの型宣言付きの型の可変長引数って何がダメなんだっけ?

ジェネリクスの宣言付きの可変長引数を受け取るメソッドを定義したときにlintに怒られて「あれ?これってダメなんだっけ?」ってなったのでメモ。

やりたいこととしては、ORMを作っていてupdate文を作るときにvoid update(UpdateValuePair<E, ?>... pairs)というメソッドを作りたい。
呼び出し側はUsers.all().update(Users.NAME.set("hoge"), Users.ACTIVE.set(true))のように書けるのが理想。

危険な操作をしてみる

これを書いた時「Possible heap pollution from parameterized vararg type」という警告が出てくる。どのように危険な操作ができるのかサンプルを書きました。

public class Main {
    public static void main(String[] args) {
        hoge(Arrays.asList("aaa"), Arrays.asList("bbb"));
    }

    public static void hoge(List<String>... args) {
        Object[] objArray = args;
        // List<Integer>を入れる
        objArray[0] = Arrays.asList(1);

        // List<String>として取り出せてしまう
        List<String> stringList = args[0];
        // 実はIntegerが入っているのでClassCastExceptionが出る
        String str = stringList.get(0);
    }
}

Object[]を経由してargsの最初の要素にListを入れた後args[0]を取り出すとListとして見えてしまうので、getしたときにClassCastExceptionが出てしまう。詳しい解説は非具象化可能仮パラメータを可変長引数メソッドに使用する場合のコンパイラの警告とエラーの改善というドキュメントを見ると良さそう。

対策

取れる対策は2つで

  1. 可変長引数を使わず専用の型を用意する
  2. SafeVarargsを使って警告を抑制する

理想としては可変長引数をやめるのが一番安全。ただ、今回はUpdate文を作るときに可変長引数で更新する値を受け取りたいので、更新するたびに新しいインスタンスを作って組み立てて…とすると使いづらいのでSafeVarargsを使うことにした。

SafeVarargsで警告を抑制する

方針としては呼び出し側は問題なくて実装側が気をつければ良さそう(気を付けるだけはもにょるけど…)なので警告を抑制する。
警告を抑制するにはfinal or staticメソッドにして@SafeVarargsアノテーションをつければ良い。

最終的にはMocoのコードは以下のようになりました。

    @SafeVarargs
    public final void update(UpdateValuePair<E, ?>... pairs) {
        createUpdate(pairs).executeQuery(ConnectionManager.getConnection(), ConnectionManager.createSqlVisitor());
    }

github.com

JetBrains IDEのLive Templateを使い倒す

はじめに

この記事はピクシブ株式会社 Advent Calendar 2017の15日目の記事です。
17新卒で、普段はpixiv PAYのサーバーサイドの開発でRailsを書いています。
入社まではJavaをメインに書いていました。昔からIntelliJ IDEAが好きなので、JetBrains IDEのLive Template機能について書きます。

Live Templateとは

よくあるコードのテンプレート機能です。 この機能の面白いところはテンプレート内に変数を置いて、入力を促したり式を埋め込むことができるというところです。
うまく使えば効率的にコードを書くことができるのではないでしょうか!?

新しいテンプレートを追加する

Preferences > Editor > Live Templatesから設定できます。 右上の+ボタンからLive Templateを選択すると新しいテンプレートを作成できます。

設定項目 説明
Abbreviation テンプレートを展開するための文字列
Description 補完候補ポップアップに表示される説明文
Template Text テンプレート
Applicate in ... Live Templateを使える場所
Edit variables テンプレート内の変数を編集するウィンドウを開く

1. 単純なテンプレートを展開する

まずは固定の文字列を展開するテンプレートを作ってみます。

Abbreviation: hello
Description: Hello Worldを出力
Applicate in ...: Java > Statement
Template Text:

System.out.println("Hello World!");

f:id:orekyuu:20171213222508g:plain

2. 変数を使ってn回任意の文字列を標準出力

次はn回の部分と出力文字列を変数にしてみます。テンプレート内で $VARIABLE_NAME$ と言った形式で変数を定義できます。また、同じ変数名を使えば同時に同じ文字列が入力されます。
$END$は特殊な変数で、すべての変数の入力が終わったあとにキャレットが$END$の位置に移動します。次に入力したくなるであろう場所に$END$を書いておくとキャレットの移動が減って便利です。

Abbreviation: each_sout
Description: n回任意の文字列を出力
Applicate in ...: Java > Statement
Template Text:

for (int $VARIABLE_NAME$ = 0; $VARIABLE_NAME$ < $LOOP_COUNT$; $VARIABLE_NAME$++) {
    System.out.println("$TEXT$");
    $END$
}

f:id:orekyuu:20171213223541g:plain

3. 関数を使って補完をサジェストを出す

Edit variablesからダイアログを開くとテンプレート内の変数に対しての設定をするためのダイアログが開きます。ここでIntelliJが用意してくれている関数を使って決められた候補内からサジェストを出したり、変数名を提案させることができます。
どのような関数があるかは公式ドキュメントに説明があるのでそちらを確認してください。 Springで使われるAutowiredを使ったフィールドを作るテンプレートを作ってみます。

Abbreviation: autowired
Description:
Applicate in ...: Java > Decration
Template Text:

@Autowired
private $TYPE$ $VARIABLE$;

Edit variables:

Name Expression Default value Skip if denfied
$TYPE$ className() false
$VARIABLE$ suggestVariableName() false

f:id:orekyuu:20171213225801g:plain

4. enum関数を使ってサジェストする

Javaのような静的型付けの言語ではIntelliJのサポートが手厚くサジェスト系の関数が豊富ですが、RubyMineではサジェストはあまり効きませんし、サジェスト系の関数もほぼありません。しかし、FactoryBotで作ったfactoryやtraitの名前を覚えるのは大変です。
そこでtraitのバリエーションをLive Templateに書いておいて良い感じに補完してくれるようにしました。決められた候補から選択させる場合、enum関数を使うと便利です。JetBrainsのIDEは中間マッチングをしてくれるので「たぶんhogeという文字列含んでたよな・・・」のような状態でもサジェストを出してくれるので、人間が考えることを減らしてくれます。

Abbreviation: create_user
Description: ユーザーを作成
Applicate in ...: Ruby
Template Text:

FactoryBot.create(:user, $trait$)

Edit variables:

Name Expression Default value Skip if denfied
$trait$ enum(":admin", ":anonymous") false

f:id:orekyuu:20171213231638g:plain

5. groovyスクリプトを使う

より高度な補完を行いたくなったときにgroovyScript関数を使うことでgroovyのコードを実行することができます。
第一引数にgroovyスクリプトもしくはスクリプトファイルのパスを指定し、スクリプトに渡したい値があれば第二引数以降に渡します。第二引数以降は$1、$2のような変数に順に割り当てられます。また_editorという変数にcom.intellij.openapi.editor.Editorインスタンスが入っているので、ここから編集中のファイルの情報やプロジェクトの情報が取得できます。
サンプルとして標準関数ではとれない現在編集中のファイルのプロジェクト内でのパスを展開するテンプレートを作ってみました。雑スクリプトなのですが、きちんと書けばrequest specのコメントをうまく補完することができそうです。

Abbreviation: relative_file_path
Description: ファイルのパスを展開
Applicate in ...: Ruby
Template Text:

"$relative_file_path$"

Edit variables:

Name Expression Default value Skip if denfied
$relative_file_path $ groovyScript("長いので下に書きました") true
com.intellij.psi.PsiDocumentManager.getInstance(_editor.getProject())
  .getPsiFile(_editor.getDocument())
  .getVirtualFile()
  .getCanonicalFile()
  .getPath()
  .minus(_editor.getProject().getBasePath())

f:id:orekyuu:20171214004530g:plain

育てたLive Templateを別PCと共有する

実際にコーディングで育てたテンプレートは自宅PCと職場PCの両方で使いたくなります。JetBrainsが出しているプラグインIDE Settings Syncを使うことで同じアカウントに紐付けされた複数のIDEで設定を共有できます。

おわりに

Live Templateは書いて終わりではなく、使いながら不満を感じたら手を加えていくといった運用をすると使い勝手の良いものになっていくかと思います。
明日は@tadsanさんがPHPEmacsの話をしてくれます。2日連続のエディタ記事ですね!お楽しみに!


ピクシブ株式会社ではではエディタにこだわりのあるエンジニアを募集しています!