Subscribed unsubscribe Subscribe Subscribe

Islands in the byte stream

Technical notes by a software engineer

Herokuのreview appsでRailsのLetter Opener WebをつかうHack

github.com

これのREADMEにもありますが、Letter Opener (-Webも含む)をHerokuで使うにはちょっと注意が必要です。つまり、

  • Letter Openerはデータを #{Rails.root}/tmp/letter_opener に保存する
  • Herokuはdyno (node) 間でデータのやりとりは出来ない設計
  • ActionMailerの deliver_later! はActiveJobで実行するので、web dynoではなくworker dyno のファイルシステムにLetter Openerのデータを保存する

という状況なので、 #deliver_now! で送信したものはLetter Openerで見えますが、 #deliver_later! で送信したものは見えない、ということになります。

じゃあHeroku review appのときだけ #deliver_later!#deliver_now! に置き換えればいいんですねということでApplicationMailerにこんな感じのhackを入れました。

ActionMailerの内部実装に強く依存しているのでRailsのアップデートなどで動かなくなる可能性はありますが、 Rails 4.2.x, 5.1.x あたりはこれで大丈夫そうです。

# rubocop:disable Style/MethodMissing

class ApplicationMailer < ActionMailer::Base
  # Heroku web apps do not access worker's filesystem
  # https://github.com/fgrehm/letter_opener_web#usage-on-heroku

  # override
  def self.method_missing(method_name, *args)
    if action_methods.include?(method_name.to_s)
      MyMessageDelivery.new(self, method_name, *args)
    else
      super
    end
  end

  class MyMessageDelivery < ActionMailer::MessageDelivery
    # override
    def deliver_later!
      if Rails.env.heroku_review_app?
        deliver_now!
      else
        super
      end
    end

    # override
    def deliver_later
      if Rails.env.heroku_review_app?
        deliver_now
      else
        super
      end
    end
  end
end

Orma v4.2.0 の Relation#upsert() の設計

https://github.com/gfx/Android-Orma

Orma v4.2.0 で Relation#upsert() を実装しました。これは、モデルのインスタンスを渡すとその状態に応じて INSERT または UPDATE を実行するというメソッドです。

モデル同士の関連もよしなにしてくれるので、とりあえず #upsert() で突っ込むといい感じに保存されるという便利メソッドです。

ただひとつ注意点があって、primary key の自動採番モード(デフォルトの挙動)のときに #upsert() 後にそのモデルを参照する場合は、#upsert() の戻り値を使わなければいけません。というのも、Ormaは どんなモデルでもimmutableであるという仮定 を置いており、自動採番したprimary keyをモデルのフィールドに代入したりはしないからです。

つまり、次のようなモデルクラスがあったときに:

@Table
public class Todo {

    @PrimaryKey
    public long id;

    @Column(indexed = true)
    public String title;

#upsert() により次のような状態になります。

Todo todo = new Todo();
todo.title = "foo";

Todo newTodo = todoRelation.upsert(todo);

assert todo.id == 0; // todo.idは変化なし

assert newTodo.id > 0; // newTodoのほうは割り当てられたidを持っている

newTodo.title = "bar";

todoRelation.upsert(newTodo); // 変化する要素がないので戻り値は不要

associationについても同様です。

少し煩雑ですが、実は Inserter#execute() も渡されたモデルを変更しないという意味では同じで、モデルをimmutableと見なす設計です。

というわけで、ちょっとクセはありますが便利だと思いますので使ってみてください。

新技術を学ぶ技術と三つの壁とDroidKaigi 2017

こないだの@onkさんのスライドがとても良かったんですよ。

短時間といいつつ守破離の「離」までいくのに3年かかるといってて、高速道路なんてものはないんだなということがわかりますね。

とはいえ自分自身に照らし合わせてみてもそのとおりだなと思いました。ぼくもAndroidで対外的にアウトプットできるようになるまで3年くらいかかってますし。まあ、ぼくは新技術を学ぶのはわりと苦手なほうではあるんですが。

で、スライドにはないけど新しい技術を学ぶ際には大きな壁がいくつかあるなとあると思ってます。それを 意識して 乗り越えるための指標としてもこのスライドはよさそうだなと。

ついでなのでちょっと ぼくの感じる 三大壁をまとめてみました。まあ、壁を壁と感じない人もいると思いますけどね!

Lv.1 着手の壁

症状:

  • 何の役に立つのかわからないので興味がわかない
    • たとえば同僚から「最近◯◯勉強してみてる」といわれてフ~ンと受け流している状態
  • 知らない概念・用語が多くて難しく着手できない

解決方法:

  • 詳しい人の話を聞く
  • 入門系記事をいくつか読んでとにかく用語や概念を頭になじませる

ぼくの例:

  • Androidの例だとSQLite, ContenProvider, RxJava, Fragmentあたりはこの壁を強く感じて着手するまでが大変だった
  • Elasticsearchは新概念が多くて最初は非常につらい

Lv.2 知った気になる壁

症状:

  • 関連情報を一通り読んでなんとなく知った気になり、何も作ってない状態で満足してしまう状態
  • 実際にはこのレベルのスキルはほとんど使い物にならないし、このレベルの技術を調子にのって批評してると的外れなことも多い(自戒をこめて)

解決方法:

  • 何か作る
  • OSSのコードを読んでコミットする(not just “fix a typo")

ぼくの例:

  • Androidだとマテリアルデザインの実装の詳細とかアニメーションまわりはこの壁を超えてない感覚がある
  • ほかは、たとえばちょっと学んだだけのプログラミング言語: C#, Go, Rust, Swift(3)あたりは明らかにこの壁を超えてない

Lv.3 雰囲気でやっているの壁

症状:

  • ある程度使えるようになって仕事でも使えているけど詳細は理解していない状態
  • 自分が書いたコードを説明できない
    • 理解せずにコードを書いている “オマジナイ” が多いときはこの壁の手前にいるということ

解決方法:

  • 疑問に思ったことやなんとなく理解しているだけのことをそのままにせず調べる
  • あとはひたすら読み書きして功夫(クンフー)を積むしかなさそう

ぼくの例:

  • AndroidだとRxJavaは「使えるようになった気がする」からの何かにハマって「全然理解してなかった…」というパターンが頻発するのでまだこの壁を超えられていないのかも
  • React, Elasticsearchなども一通り使えるようになったものの、まだ雰囲気でやっている感じ

この三つの壁を乗り越えると、やっと「使いこなしている」という感覚になります。

で、DroidKaigi 2017ですが、@onkさんのスライドにもあるとおり、コミュニティ主導のカンファレンスで知り合いを作るのは効率よく学ぶ環境作りためのいい機会です。

それと同時に、新しい技術に触れて第一の壁を突破する勢いを得たり、第二・第三の壁を突破してる人の話を聞いたり質問したりして自分のショートカットに役立てたりと、いろいろといい刺激を得られるのではないかと思います。2017年2月2日現在、まだチケットは70席ほど余っているので、刺激を得るために参加してみるのもいいんじゃないでしょうか。

なおぼくは二日目の朝10:40からAndroidのORMまわりの話をします!ぜひ聞きに来てください。

ObjectBox vs Orma

greenDAOの開発元であるgreenbotが新しいNoSQL ORMのβ版をアナウンスしたようです。

greenrobot.org

いわく、マルチスレッドで使いやすく、バイトコード操作のようなマジカルな方法はとらず、マイグレーションを自動で行えてしかも爆速だと。これらの特徴はOrmaも標榜しているので、気になりますね。

ORMにとってのパフォーマンスは一番重要というわけではないものの、それなりには速くないと困るのは確か。というわけで、まずは軽くベンチマークをとってみました。コードベースはOrmaのリポジトリ内のベンチマークコードに追加した形です。

結論からいうと、読み込みはわりと速いです。

  • 書き込みはOrmaとほぼおなじ水準
    • 何度か繰り返すとOrmaより速いこともあります
    • なおOrmaは手書きの素朴なコードよりも高速に見えますが、手書きのコードでの最適化をサボっているためです
    • Realmが遅いのはよくわかりません。普通にtransaction作ってinsertしているだけだと思うんですが
  • 読み込みはOrmaよりも高速なことが多い
    • 何度か繰り返してみるとだいたい10~30%高速です。これはすごい
    • Realmがかなり遅いのは、すべてのフィールドにアクセスしているため。Realmはlazy loadを行うので、アクセスするフィールドが少ない場合には有利ですが…

具体的なコードは次のPRで確認できます (with Orma v4.1.1)。

Benchmark with ObjectBox by gfx · Pull Request #371 · gfx/Android-Orma · GitHub

詳細:

f:id:gfx:20170125220218p:plain

Rails + React + SSRのもとでレスポンシブにするにあたって現状とっている方法

SSRと絡めようとすると難しくて、いろいろ試行錯誤しました。いまはこういう方向でやろうとしていて、そこそこメンテナンス可能に書けそうだなという手応えがあります。

三行で

  • CSSのメディアクエリのみを使い、Railsでテンプレートエンジンを使うにせよReactでSSRするにせよReactでCSRするにせよ同じ方法を使う
  • PCのみ表示させる場合は .showPcOnly クラスを、スマホでのみ表示させる場合は.showSmartphoneOnlyクラスをつけたdiv囲む
  • たまに .showPcOnly などとは別に width: 100% などを指定しないといけないが、それは別途クラスを定義して与える

ヘルパーSCSSクラス

こんな感じのヘルパーを用意しておく。

$smartphoneBreakpoint: 767px;

@mixin smartphone {
  @media screen and (max-width: $smartphoneBreakpoint) {
    @content;
  }
}

@mixin pc {
  @media screen and (min-width: $smartphoneBreakpoint + 1) {
    @content;
  }
}

@include smartphone {
  .showPcOnly {
    display: none;
  }
}

@include pc {
  .showSmartphoneOnly {
    display: none;
  }
}

ヘルパーJSX components

こんな感じのJSX componentsを定義しておく。

import React from 'react';
import classNames from 'classnames';

export class SmartphoneOnly extends React.Component {
  static propTypes() {
    return {
      children: React.PropTypes.any.isRequired,
      className: React.PropTypes.string,
    };
  }

  render() {
    return (<div className={classNames(this.props.className, "showSmartphoneOnly")}>
          {this.props.children}
      </div>);
  }
}

export class PcOnly extends React.Component {
  static propTypes() {
    return {
      children: React.PropTypes.any.isRequired,
      className: React.PropTypes.string,
    };
  }

  render() {
    return (<div className={classNames(this.props.className, "showPcOnly")}>
            {this.props.children}
        </div>);
  }
}

試したこと

  • .showPcOnly クラスを@extendする→❌
    • そのクラスにdisplay: flexをつけたい場合に衝突してうまくいかない
  • react-responsive ライブラリをつかう→❌
    • サーバーサイドでJavaScriptのmedia query APIを使えないのでダメ
    • JS media queryのための情報をクライアントから送る(e.g. width: 1024)こともできるが所詮シミュレーションでしかないし、キャッシュとの相性も悪い
  • CSSでクラスごとに地道にがんばる→❌
    • DOM構造をみて(=hamlやJSXをみて)どうスイッチされるかを把握できないのでつらい
    • DOM構造に直接.showPcOnly<ShowPcOnly>で書くのがわかりやすくていい

Ruby extのためのVisual Studio Codeのclang extension用設定を書き出す

Visual Studio Codeの C/C++ Clang extensionのためのいくらかの設定をすると、Ruby extensionを開発するときに補完が効くようになり、開発が楽になります。

C/C++ Clang - Visual Studio Marketplace

ところでRuby extensionのCFLAGSなどの設定はわりと難しくて、実際のMakefileを読み込んでmakeを実行するのがてっとりばやいです。

というわけで、extconfを起動してMakefileをつくり、そこから必要なマクロを抜き出して .vscode/settings.json に書きだすためのタスクをつくってみました。

# additional Rake task for "ext/foo"
namespace :vscode do
  desc "Setup Visual Studio Code C/C++ settings (.vscode/setting.json)"
  task :setup do
    require "json"
    require "shellwords"

    FileUtils.mkdir_p("tmp/vscode")

    cflags = Dir.chdir("tmp/vscode") do
      # to create Makefile
      system("ruby", File.join(__dir__, "ext/foo/extconf.rb"))

      # Makefile macro to get CFLAGS for editors / IDEs
      task = "\n\nshow-cflags:\n\t$(ECHO) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS)\n\n"
      IO.write("Makefile", task, mode: "a")

      Shellwords.split(`make show-cflags`)
    end

    settings = {
      "clang.cflags" => cflags,
      "clang.completion.triggerChars" => [
        ".", ":", ">", "_"
      ],
    }

    FileUtils.mkdir_p(".vscode")
    IO.write(".vscode/settings.json", JSON.pretty_generate(settings))
  end
end

こんな感じで補完が出ます。

f:id:gfx:20170115121534p:plain

しかし、補完が出るのに数秒かかるので思ったほど快適ではない… 😔


追記: Microsoft公式のvscode.cpptoolsだと補完が快適でした。それ用のtaskはこんな感じです。

namespace :vscode do
  desc "Setup Visual Studio Code C/C++ settings (.vscode/setting.json)"
  task :setup do
    require "json"
    require "shellwords"

    FileUtils.mkdir_p("tmp/vscode")

    cflags = Dir.chdir("tmp/vscode") do
      # to create Makefile
      system("ruby", *Dir.glob(File.join(__dir__, "ext/**/extconf.rb")))

      # Makefile macro to get CFLAGS for editors / IDEs
      task = "\n\nshow-cflags:\n\t$(ECHO) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS)\n\n"
      IO.write("Makefile", task, mode: "a")

      Shellwords.split(`make show-cflags`)
    end

    include_path = cflags.select do |flag|
      flag.start_with?('-I')
    end.map do |flag|
      flag.sub(/\A-I/, '')
    end

    # http://code.visualstudio.com/docs/languages/cpp
    configurations = [
      {
        name: "Mac",
        includePath: include_path,
      },
      {
        name: "Linux",
        includePath: include_path,
      },
      {
        name: "Win32",
        includePath: include_path,
      },
    ]

    FileUtils.mkdir_p(".vscode")
    IO.write(".vscode/c_cpp_properties.json", JSON.pretty_generate(configurations: configurations))
  end
end

Android SDK 25.2.3から付属のsdkmanager(1)でパッケージのインストールをする

Android SDK 25.2.3にsdkmanagerとういコマンドが新しくく追加されていたようです。

これは、CLIでAndroid SDK packageを管理するためのツールのようです。Gradle Android pluginと同様にlicensesファイルに対応しているので、CIなどで扱いやすいですし、なによりandroidコマンドにはなかったパッケージのアップデートがサポートされています。このため、androidコマンドよりは扱いやすく工夫の余地が増えてると考えてよさそうです。

使い方はこんな感じです:

# homebrewだとまだパスが通らないので絶対パスで参照する

# インストール済みパッケージと利用可能なパッケージを出力する
$ANDROID_HOME/tools/bin/sdkmanager --list

# platform-toolsと旧名extra-android-m2repositoryをインストールする
$ANDROID_HOME/tools/bin/sdkmanager "platform-tools" "extras;android;m2repository"

# インストール済みパッケージをアップデートする
$ANDROID_HOME/tools/bin/sdkmanager --update

Circle CIでの導入はこんな感じです:

https://github.com/gfx/Android-Orma/pull/365

Circle CIにインストール済みのAndroid SDKは25.2.3より古いため、まずandroidコマンドでSDK toolsのアップデートをしないとsdkmanagerが使えません。このためCircle CIのAndroid SDKがアップデートされるまではandroidコマンドから脱却はできません。残念。