(WIP)Realm Swiftメモ

swiftでエンティティの永続化をするためにealmを触ってみたのでメモする。
あんまり詳しくないけどios, android共にアプリ内のDBはRealmがデファクトに近いらしい。

インストール

cocoapodsで入れる

初期化
pod init
Podfileを編集
target 'SampleApp' do
  use_frameworks!
  pod 'RealmSwift'
end
インストール
pod install

使い方

※基本チュートリアルに書いてある内容 realm.io

クラスを定義

永続化したいクラスは通常のSwiftのクラスと同じように定義する。
realmで永続化するクラス同士のリレーションを持たせたい時には通常のクラスと同じようにプロパティに追加する。
リレーションはoptionalにしておかないとRLMExceptionが発生し "Item.user" property must be marked as being optional と言われる。

import RealmSwift

final class Item: Object {
    dynamic var id: String
    dynamic var title: String
    dynamic var description: String
    dynamic var user: User?
    
    //primaryKeyを定義
    override static func primaryKey() -> String? {
        return "id"
    }
}

final class User: Object {
    dynamic var id: String
    dynamic var name: String
    
    override static func primaryKey() -> String? {
        return "id"
    }
}
更新系の処理
//インスタンス化
let realm = try! Realm()

//追加
let item = Item(value["id": "a1", "title": "hogehoge", "description": "hugahuga"]
)
item.user = User(value["id": "b1", "title": "piyopiyo"])

try! realm.write() {
    realm.add(item)
}

//更新
try! realm.write() {
   item.title = "hogehoge2"
   item.description = "hugahuga2"
}

//削除
try! realm.write() {
    realm.delete(item)
}
参照系の処理
//全件取得
let items = Array(realm.objects(Item.self))   //Result型からArray型にする

//検索
let items = Array(realm.objects(Item.self).filter("name = hogehoge"))

//関連オブジェクトから検索
let items = Array(realm.objects(Item.self).filter("user.name = piyopiyo"))

変数を埋め込む際は下記のようにする。
普通の文字列に変数を埋め込むやり方をするとエラーになってしまう。(内部でNSPredicateを使用しているため。)

//変数を使って検索
let items = Array(realm.objects(Item.self).filter("name = %@", itemName))

//これは失敗する
let items = Array(realm.objects(Item.self).filter("name = \(itemName)"))

マイグレーション

既存のクラスに破壊的変更を加えた場合、実行時にエラーが発生する。 解消するにはマイグレーションを行う必要がある。
https://realm.io/jp/docs/swift/latest/#migrations

データを格納したファイルを削除することでも対応できるので、データが消えても問題ない場合はこちらの方が早い。

//ファイルの場所を取得して消す
if let fileURL = Realm.Configuration.defaultConfiguration.fileURL {
    try! FileManager.default.removeItem(at: fileURL)
}

hubotで株価を取得するbotを作る

hubotを使ってSlackのbotを作ってみたので手順を簡単にメモっておく。
今回は簡単なサンプルとしてgoogle financeのAPIから株価を取得して、前日との差分を教えてくれるbotを作成した。

インストール

npmでyeoman、generator-hubotをインストール

npm install -g yo generator-hubot

ディレクトリを作って初期化する

mkdir mybot
cd mybot
yo hubot

色々質問されるので適当に入力しておく。adapterはslackを選択する。

コード

自動生成されたscriptというディレクトリにコードを書いていく。
デフォルトではcoffee scriptが置いてあるけど普通にJavascriptでも書ける。

google financeのAPIを叩くのにfetchを使用する。

npm install node-fetch --save

コードはこんな感じ。最新の株価と前日との差分を返す。
tsで株価の取得開始日を指定できなくなったのが辛かった。

gist.github.com

実行

起動する前にslackのconfigurationでhubot用のトークンを発行して環境変数に設定しておく。

bin/hubot --adapter slack

こんな感じの結果が返る
f:id:ymtttk:20170916173429p:plain:w300

所感

ものすごく久しぶりにJavascriptを書いたけど、fetchとpromiseすごく便利だった。
一方で日付とか値の文字列とかの挙動は相変わらず扱いにくかった。

現状証券コード問い合わせる仕様になっているが、証券コードとか覚えてられないので会社の名前で問い合わせられるようにしたい。あと、急騰や急落を通知してくれる機能なんかを実装したい。

vagrantメモ

久しぶりにvagrantでローカル環境を作ってみたが、結構使い方を忘れてしまっていた。 毎度ググらなくて済むようにメモを残しておく。

box追加

box名、boxのurlを指定してインストールする。
公式が提供しているboxを利用する場合は飛ばせる。(初回起動時にインストールされる)

vagrant box add Ubuntu14.04 https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-i386-vagrant-disk1.box
初期化

boxを指定してカレントディレクトリを初期化する。Vagrantfileが作られる。

mkdir myvagrant
cd myvagrant
vagrant init Ubuntu14.04
起動
vagrant up
ssh接続
vagrant ssh
プロビジョニング

Chef, ansible, シェルスクリプトなど様々な方法を選択することができる。 自分は良くansibleを利用するのでansibleのサンプルを書いておく。
下記はlocal provisionerを使用する例。local provisionerを使用する場合、ホストにansibleがインストールされ、ホスト上でプロビジョニングが行われる。

config.vm.provision "ansible_local" do |ansible|
  ansible.playbook = "playbook.yml"
  ansible.inventory_path = "hosts"
  ansible.install_mode = "pip"
  ansible.version = "1.9.6"
end
シャットダウン
vagrant halt

RabbitMQでロストしないようにpublish/consumeする

RabbitMQでメッセージをロストせずにpublish/consumeする方法を軽く調べたのでメモしておく。

consume

メッセージの処理が終わったらacknowledgementを返す。

val connection = factory.newConnection(addresses)
val channel = connection.createChannel()
// noAck=false
channel.basicConsume(queue, false, consumer)
//メッセージの処理
if (isSuccess) {
  channel.basicAck(deliveryTag, multiple)
} else {
  channel.basicNack(deliveryTag, multiple, requeue)
}

acknowledgementが返ってくるまでRabbitMQはメッセージを保持するので、consume中にクライアントが死んだりコネクションが切れたりした場合でもメッセージはキューに残る。
multipleは指定したdeliveryTagまでの全てのメッセージにacknowledgementを返すときtrue、指定したdeliveryTagのメッセージのみにacknowledgementを返す時falseにする。
requeueはnack時にメッセージをキューに詰め直す場合trueに、メッセージを捨てたりdead letterキューに入れたりする場合falseにする。

publish

publishの失敗を検知するには、transactionとconfirmという2通りの方法がある。
普通にbasicPublishするだけだとpublish後の失敗は検知できない。(publish先のExchageやキューが存在しなかったりしても何も言われない。)

transaction

RabbitMQがキューにデータを保存し、永続化されるまでをブロックする?(このへんの理解は不十分です…) AMQPの仕様ではトランザクションはatomicな処理とされているが、RabbitMQではこの仕様を満たしていないらしい。(失敗したがメッセージのpublishはできているということがあるらしい。)

val connection = factory.newConnection(addresses)
val channel = connection.createChannel()
channel.basicPublish(exchange, routingKey, mandatory, immediate, properties, body)
channel.txCommit()
channel.close()

mandatoryをtrueにするとメッセージをキューにルーティングできない時にエラーになる。falseの時はバインド先が存在しない場合メッセージが吐き捨てられる。
immediateをtrueにすると、メッセージは送信先のキューからすぐにコンシュームされない場合エラーになる。

transactionはコストの高い処理をしているらしく、処理速度がかなり遅くなる。 メッセージをロストしたくないという目的に対してコストが高すぎるため、RabbitMQではconfirmという仕組みが提供されている。(AMQPの仕様には存在しないRabbitMQオリジナルの仕様)

confirm

confirmを使うとbrokerがメッセージを処理した段階でpublisherにacknowledgementが返る。
下記のようにするとackknowledgementが帰ってくるまでwaitする。

val connection = factory.newConnection(addresses)
val channel = connection.createChannel()
channel.confirmSelect()
channel.basicPublish(exchange, routingKey, mandatory, immediate, properties, body)
channel.waitForConfirmsOrDie(timeout)
channel.close()

下記のようにListenerを設定することで非同期的にacknowledgementを受け取ることもできる

channel.addConfirmListener(new ConfirmListener {
  override def handleAck(deliveryTag: Long, multiple: Boolean): Unit = {
    //ackの時の処理
  }
  override def handleNack(deliveryTag: Long, multiple: Boolean): Unit = {
    //nackの時の処理
  }
})

メッセージをロストしたくないだけならconfirmで十分なので、transactionを使用するケースはあんまり多くなさそうに感じる。

pythonのプロジェクト構成

今までちょっとしたスクリプトくらいでしかpython使ってこなかったので、ちゃんとしたプロジェクト構成について知識がなかった。
githubリポジトリを漁って見たが、結構まちまちであまり決まった構成というのはないのかなという印象。

下記のドキュメントが参考になりそうだったのでひとまずこれに従ってやってみようと思う。
python初学者向けのドキュメントで構成についてだけでなく全般的な内容が書かれている。

Structuring Your Project — The Hitchhiker's Guide to Python

google financeのAPIのメモ

株価データを取得するためにgoogle financeのAPIを触ってみたときのメモ。
このAPIはおそらくGoogleFinanceの裏で使われているAPIで、公式のドキュメントが存在しない。
公式で提供しているAPIは終了しているっぽいので、使い続けられるか怪しいかもしれない。 https://developers.google.com/finance/?hl=ja

リクエス

下記はトヨタの過去1年分の株価を取得する例 。
https://www.google.com/finance/getprices?p=1Y&i=86400&x=TYO&q=7203

p:データを取得する期間。年(Y)または日(d)で指定ができる
i:データの間隔。秒単位で指定する。上記では1日を(86400秒)指定している。
x:銘柄が取引される市場。TYOは東京証券取引所
q:銘柄のコード。

またデータの取得を開始する日時をts(UNIXタイムスタンプ)で指定できる
https://www.google.com/finance/getprices?p=1Y&i=86400&x=TYO&q=7203&ts=1451574000

※2017/09追記
tsのパラメータがいつのまにか効かなくなっていた(常に現在の時刻がベースになる)

レスポンス

下記のようなレスポンスが返ってくる

EXCHANGE%3DTYO
MARKET_OPEN_MINUTE=540
MARKET_CLOSE_MINUTE=900
INTERVAL=86400
COLUMNS=DATE,CLOSE,HIGH,LOW,OPEN,VOLUME
DATA_SESSIONS=[MORNING,540,690],[AFTERNOON,750,900]
DATA=
TIMEZONE_OFFSET=540
a1455861600,5999,6086,5961,6050,14020000
3,5980,6044,5850,5906,11427200
4,5994,6070,5954,6065,10895500
5,5974,5989,5835,5900,10888100
6,5931,5967,5827,5881,15819200
7,5910,6006,5895,5960,12753100
10,5897,6062,5897,6001,18539900
11,5893,5935,5824,5853,11093200
...

株価のデータは9行目から。どの列が何を表すのかはCOLUMNSを見れば分かる。
DATE 列は一行目がタイムスタンプになっており、2行目以降は最初のデータからiで指定した期間が何回経過したかを表している。
上記はデータの間隔が1日の例だが、dateの数値が飛んでいるのは土日などを挟んでいるため。
Date列の上限は999でそれを超えると一行目のようなタイムスタンプを挟んでまた1から数え始める。
また、終値は単にCLOSEとされているが、ちゃんと調整後終値株式分割を反映させた終値)が返ってくる。

サンプル

すごく雑に1年分の株価をcsvにして吐き出すものを書いた。
データ解析に向いていると聞いてpythonで書いてみたが、正直pythonよくわからずに書いているのでいいコードではないと思う。

gist.github.com

elasticsearchでfilterとpost_filterを使ったらうまくいかなかった

termによる検索結果をさらに絞り込むためにpost_filterを使ったがうまくいかなかった。

post_filter

検索結果からさらに絞り込みを行うためのクエリで主にaggregationと組み合わせる。

このようなクエリを投げたが、 filterの結果から絞り込むのではなく、post_filterのみを適用した結果が返ってきてしまう。(2.2で確認。5以降では動くかも)

{
  "filter":{
      {"term":{"field1":"hoge"}},
  },
  "from":0,
  "size":100,
  "sort":{"createdAt":{"order":"desc"}},
  "post_filter":{"not":{"term":{"field2":"huga"}}}
}

queryと一緒に使うようにしたら動いた。

{
  "query":{
      {"term":{"field1":"hoge"}},
  },
  "from":0,
  "size":100,
  "sort":{"createdAt":{"order":"desc"}},
  "post_filter":{"not":{"term":{"field2":"huga"}}}
}

filterとqueryの違いについては公式のドキュメントで説明されている。 https://www.elastic.co/guide/en/elasticsearch/guide/current/_queries_and_filters.html

これによるとqueryはマッチした結果のスコアも一緒に知りたい時に使用するもので、filterは単にマッチするかどうか知りたい時に使うものらしい。 post_filterが使われる場合は単なる絞り込みではないと言うことなのだろうか。