2019年11月28日木曜日

年間 60 万円の配当金を得るまでの道のり

ちゃんと資産運用しないといけないと思い 1 年間ほど株の運用をしてみました
独学で株を学びとうとう目標にしていた年間 60 万円の配当金を得ることができるようになりました
ボートフォリオや実際に投資した株式、教訓として紹介したいと思います

証券会社選択

まず株を始めるにあたり証券会社を選択する必要があります
自分はマネックスを選択しました
理由は単純で外国株に投資したかったのでマネックスに決めました (結果的には外国株は購入しませんでした)
とりあえずここで国内株を購入するための口座と外国株を購入するための口座を開設しました

はじめての株購入

正直何をすればいいかさっぱりでした
いろいろとネットで調べてどんな株を買えばいいのか、どれくらい株を買えばいいのかいろいろと調査しました
が、正直調べただけではさっぱり不明だったので、とりあえず購入してみました
一歩踏み出す勇気というか、とりあえず試してみれば分かるだろう精神が自分にはあるのでとにかく購入してみました
このとき購入した株は「7201 日産自動車」で当時の株価は 1057円/1株 でした
これが自分の株投資の第一歩になります
(これも今思えば高すぎる株価だったかなと思います)

ポートフォリオ

株や投資をする上でポートフォリオは非常に重要だと言われています
正直素人の自分には大事さはさっぱりですが見様見真似することも大事なので自分なりのポートフォリオを作成してみました
とは言ってもゴールは「年間 60 万円の配当金」と明確に決めていたのでポートフォリオの作成はそれほど大変ではありませんでした

国内株の投資割合

投資中期の段階での国内株への投資割合は以下の通りです

  • 日本たばこ産業 (2914 東証)・・・1800 株 - 2800株
  • 日産自動車 (7201 東証)・・・6500 株 - 9500 株

基本は高配当株狙いなので上記のようになっています
今思えば、さすがに投資先が少なさすぎる気はします、、
上限があるのはナンピンです

手数料の罠

株に詳しい方には常識かもしれないですが株を購入したときの最終的に「平均取得単価」として計算されます
調べれば計算方法など詳しく出てきますがすごい簡単に説明すると株を購入時に発生する手数料や消費税を上乗せした金額が実際に購入した金額になります
つまり指値で購入したとしても実際はそれよりも少し上の金額で購入することになります
おそらくこれはマネックスだけではなくすべての証券会社でそうなんだと思います (手数料はいろいろと変わってくると思いますが)

マネックスの場合主に株を購入する方法は「電話」「Web」「スマホ」の 3 種類になります
電話はないとして、当初ブラウザを使って Web サイトから購入していました
で、これが罠だったのですがスマホから購入するほうが手数料が少なくてすみます
※マネックスの手数料の詳細はこちら

当時これを知らず何度か Web サイトから購入してしまい「平均取得単価」を上げる結果になってしまいました
「平均取得単価」が上げると何が問題かというと単純に利益が少なくなるのが問題です (たぶん)
なので手数料も少ないほうが断然お得です
しかも手数料は株の注文ごとにかかるので何度も売り買いすればその都度手数料もかかるのでもったいないです

これに気がついてからは株の注文はすべてマネックスのスマホアプリ「マネックストレーダー株式」から行うようにしました
とは言っても配当金目当ての投資の場合、株の上下はデイトレーダほど気にはしないのであまり気にしなくても良いのかもしれません

はじめての配当金

初めての配当金は 12 月に権利確定日がある JT の配当金でした
1800 株 75 円で円の 135,000円の配当金が出ました
あとは 3/9 月に日産、6 月に再度 JT の配当がありました
金額はそれぞれ以下の通りです

JT

  • 2018/12/25 権利付き最終日
  • 2019/03/02 配当金入金 (1800 x 75 x 0.79685 ≒ 107,575 円)
  • 2019/03/22 配当の食品が到着
  • 2019/06/25 権利付き最終日
  • 2019/09/02 配当金入金 (2800 x 77 x 0.79685 ≒ 171,801 円)
  • 2019/09/28 配当の食品が到着

こんな感じの流れで配当金の入金がありました
マネックスの特定口座に税金 (復興特別所得税) を差し引いた上記金額が入金されました
JT の場合書面での連絡もくれます
基本的には株主総会後に配当金の振り込みと配当商品の発送が行われるっぽいです
営業報告書など書面が来た段階では配当金の入金はされていません
またマネックスからのメールなどでの配当金確定連絡はありませんでした

日産

  • 2019/03/26 権利付き最終日
  • 2019/06/26 配当入金 (9500 x 28.5 x 0.79685 ≒ 215748 円)
  • 2019/09/26 権利付き最終日
  • 2019/11/27 配当入金 (8000 x 10 x 0.79685 ≒ 63,748円)

日産は入金と同日に書面での連絡がきました

確定申告

基本は不要です
マネックスで口座を開設する際に特定口座で開発した場合は個別での確定申告は不要になります
基本は特定口座で口座を開設することをオススメします

デイトレーディング

デイトレーディングはほとんどやりませんでした
理由は毎時毎分、株価をチェックするのが難しいからです
あとは単純に株の「読み」に自信がなかったからです

そもそもデイトレーディングは「キャピタルゲイン」による増益を目的にした投資です
自分の場合「インカムゲイン」を目的にした投資方法になります
この 2 つは一長一短かなと思います
詳しくは調べてもらったほうが良いと思いますが個人的な感覚では

  • キャピタルゲイン・・・ハイリスク・ハイリターン、利益重視
  • インカムゲイン・・・ローリスク・ローリターン、安定重視

かなと思います
ローリスク・ローリターンは少しニュアンスが違う感じもしますが要するにインカムゲインは株のやり取りを毎日のようにしません
基本は長期間 (数年) 株を保有することで配当金を受け取ることを目的にしています
なので、株価が上がろうが下がろうが気にしません (気にしませんと書いていますが当然気にはなります)

ただ、インカムゲインであろうと購入時の株価は安ければ安いほうが良いです
そのために多少の売り買いをして「平均取得単価」を下げるというのはやってもいいかもしれません
配当金生活をやめていざ株を売りたいときには平均取得単価が安いほうが利益が大きく出ます

ナンピン

平均取得単価を下げる方法に「ナンピン」というものがあります
要するに株価が下がったときに買い増しすることで購入時の株価の平均値を下げるという方法です
ナンピンはあまり良いイメージがないと思われがちです
確かにそうかもしれませんが、インカムゲイン目的の投資であれば仕方ないかなと思います

配当金自体は長期間持ち続けなくても得ることができます
配当金確定日というものが存在しておりそれまでに 1 株でも持っていれば受け取ることができます
ただ、配当確定日に近づけば近づくほど株価は上昇しがちです
なのでギリギリで購入するのは大変なので、事前に購入しておいてそれを保持し続けるほうが安定する場合もあります
そうなると株も必然的に塩漬け状態になりナンピンもありかなと思えてきます

平均取得単価を下げるのが目的で一度株を売るという手もあります
売った上で再度買い戻しすれば平均取得単価を下げることができます
しかし売る価格を利確まで待つと再度購入するときに株価の下落を待たなければなりません
当然ですが株価が急落する保証などないので、そういう方向から考えてもインカムゲイン目的で購入した株は長期間保有したほうが (精神的にも) 良いかなと思っています

もちろん投資金額に余裕があるのであればナンピンもありですし、保有株数が増えたほうが配当金も増えるので、それに越したことはありません

終値、始値は関係ない

例えば前日 1000 円で終了して翌日 1000 円から開始されるということはほぼありえません
理由は株価の値の決定方法によるものなので詳しくはネットで調べてください
なので配当を確定した翌日に終値付近で売りたくても始値が決定しないことには売れないのです

おまけ: 日産ショック

2018/11/20 に起きた金融商品取引法違反 (有価証券報告書の虚偽記載) の疑いによるカルロスゴーン会長の逮捕による日産株の急落です
自分ももろに影響を受けました
一時 800 円あたりまで株価が落ち含み損も数 100 万まで膨れ上がりました
しかし 1 年持ち続けてみると一応元の株価に戻ってきました

個人的な見解ですが内部告発による問題だったのと日産株自体もともと配当金が強い株だったのもあり急落したときに売りと同じくらい買いもあったのだと思います
正直一時はかなり焦りましたが今は元の株価に戻ったので安心していますがかなり怖いニュースでした

加えて 2018/10-11 はアップルショックなども重なりダウおよび日経平均もかなり軟調に推移していたのでこの時期に株を始めた人はかなりつらいスタートになったんじゃないかなと思います

おまけ: 2018 年末大暴落

2018/12 に発表があった FOMC の利上げ時にパウエル議長の発表を皮切りに世界経済の減速が懸念され暴落が起きました
かなりの暴落でここでも保有株は 10-20% ほど下落しました

また 2019 年の配当は 28.5 から 10 にまで減りました
純利益がかなり減ったのでしかたないのは仕方ないですが、頑張ってほしいです

最後に

この記事は 1 年前から執筆を開始してようやく公開にこぎつけました
そしてこの記事は実はこの「最後に」から記載しています
というのも目標の 60 万円というのは執筆時点で決定していたのでゴールは先に書けるということで結末から記載しました
なのでこの記事が世の中に出回っているということは目標を達成できているということになります

正直やってみて辛かったです
配当金目当ての長期投資とは言え含み損が膨れ上がったときは精神的に参っていたと思います
配当金の場合基本的には年に 2 回しか受け取ることができないので、それも辛い点かなと思います
FX のスワップポイントなどは毎日もらえるので精神的にも FX のほうが安定するような気がします

現在も株は保有していますが別の投資もやってみたいので利益が出たところで売ってしまうかもしれないし、やっぱり保有し続けて配当金をもらい続けるかもしれません

最後にあくまでもこの記事は自分の体験談になります
この方法で必ずしも利益が出る保証はありません
株への投資はご自身の責任で計画的に行うようにしてください

2019年11月27日水曜日

UITableViewController の Static Cells でセルが選択できない

概要

よく紹介されているのが Single Selection になっているかですが自分は Single Selection になっていても選択できない状態になっていました
原因は Extension でした

環境

  • macOS 10.15.1
  • Xcode 11.2.1 (11B500)

問題

UITableView のタッチイベントを UIView で取得するために以下のような extension を作成していました

extension UITableView {
    open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.next?.touchesBegan(touches, with: event)
    }
}

これを実装していると UITableViewController がタッチイベントを取得できなくなるため Single Selection をちゃんと設定していたもセルが選択できなくなり didSelectRowAt などが呼ばれなくなります

対策

UITableViewController と UIViewController + UITableView が混在しているプロジェクトで起こるかなと思います
対策としては extension ではなく UITableView のスーパークラスを作成します

import UIKit

class CustomTableView: UITableView {
    open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.next?.touchesBegan(touches, with: event)
    }
}

そして UIViewController + UITableView の UITableView にカスタムクラスとして上記のスーパークラスを設定します

こうすることで UITableViewController 側の UITableView には影響しないためちゃんと didSelectRowAt が呼ばれるようになります

2019年11月26日火曜日

UITextField の入力に pickerView を設定する方法

概要

UITextField はデフォルトだとキーボードからの入力を受け付けます
セレクトボックスの用に入力する項目が決まっている場合は pickerView を入力に設定することができます

環境

  • macOS 10.15.1
  • Xcode 11.2.1 (11B500)

UITextField を設置する

まずは StoryBoard を使って UITextField を設置します
コンポーネントは右上の「+」ボタンから設置することができます

場所はどこでもいいです
確認のために UILabel も 1 つ設置しています

ViewController と接続する

IBOutlet を使ってコードと UI を紐付けましょう
StoryBoard の右上から「Assistant」を選択します

すると StoryBoard に紐づく ViewController が表示されるので Ctrl を押しながら各種 UI コンポーネントをコードに紐付けましょう

UIPickerViewDelegate を実装する

次に pickerView を作成します
pickerView のイベントをコントロールするために UIPickerViewDelegate, UIPickerViewDataSource を実装しましょう

import UIKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return 3
    }

    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var result: UILabel!
    var pickerView = UIPickerView()

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

numberOfComponents は pickerView に表示する列の数です
numberOfRowsInComponent は pickerView に表示するデータの数です
今回は 3 つのデータを pickerView に表示して選択できるようにします

pickerView を UITextField の入力に設定する

UITextField を選択したときに pickerView のキーボードが表示されるようにします
今回は createPickerView というメソッドを準備しました

import UIKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return 3
    }

    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var result: UILabel!
    var pickerView = UIPickerView()

    override func viewDidLoad() {
        super.viewDidLoad()
        createPickerView()
    }

    func createPickerView() {
        pickerView.delegate = self
        textField.inputView = pickerView
        // toolbar
        let toolbar = UIToolbar()
        toolbar.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 44)
        let doneButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(ViewController.donePicker))
        toolbar.setItems([doneButtonItem], animated: true)
        textField.inputAccessoryView = toolbar
    }

    @objc func donePicker() {
        textField.endEditing(true)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        textField.endEditing(true)
    }
}

ポイントは textField.inputView = pickerView の部分で UITextField が持つ inputView に pickerView を設定します
こうすることで入力のキーボードを pickerView にすることができます
今回はおまけで UIToolbar も設置しています
これは必須ではないですが UX 的にはあって良いかなと思います
今回は toolbar 上のボタンを押すかキーボード以外の場所をタップしたときにキーボードを非表示にしています

イベントの実装

あとは pickerView のデータを選択したときのイベントを実装します
最低限実装するのは titleForRowdidSelectRow です
titleForRow は pickerView に設定するデータを登録するためのメソッドです
didSelectRow は pickerView の各種データを選択したときに呼ばれるメソッドです
今回はここで UITextField と確認用の UILabel に選択されたデータを表示するようにしています

また pickerView に表示するデータは固定の配列データにしています

import UIKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return 3
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return data[row]
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        textField.text = data[row]
        result.text = data[row]
    }

    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var result: UILabel!
    var pickerView = UIPickerView()
    var data = ["Orange", "Grape", "Banana"]

    override func viewDidLoad() {
        super.viewDidLoad()
        createPickerView()
    }

    func createPickerView() {
        pickerView.delegate = self
        textField.inputView = pickerView
        // toolbar
        let toolbar = UIToolbar()
        toolbar.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 44)
        let doneButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(ViewController.donePicker))
        toolbar.setItems([doneButtonItem], animated: true)
        textField.inputAccessoryView = toolbar
    }

    @objc func donePicker() {
        textField.endEditing(true)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        textField.endEditing(true)
    }
}

動作確認

ビルドして動作確認してみましょう

UITextField を選択すると pickerView のキーボードが表示され選択するとデータが反映されるのが確認できると思います

最後に

UITextField の入力に pickerView のキーボードを設定する方法を紹介しました
Web で言うところのセレクトボックス的に使えるかなと思います

今回は実装していませんが UITextField はキーボードからの直接入力を不可にしてもいいかもしれません

2019年11月20日水曜日

golang で struct が持つ既存の関数をスタブにしてテストする方法を考える

概要

過去に紹介した rspec-mock のように既存の関数のオーバライド的な感じでモックを作成したかったのですが gomock では厳しいことが判明しました
とりあえず gomock などは考えずに golang でどうやって実現できるのかを考えてみました

環境

  • macOS 10.15
  • golang 1.12.9

テストしたいコード

package main

import (
    "fmt"
)

type A struct {
    b B
}

type B struct {
    key string
}

func (b B) Method(value string) {
    fmt.Printf("%s => %s\n", b.key, value)
}

func main() {
    b := B{key: "hello"}
    a := A{b: b}
    a.b.Method("world")
}

この a.b.Method をテストするにあたって Method をスタブしたいと思います

DI を使った仕組みに書き換える

B 構造体を DI (dependency injection) を使った仕組みに書き換えます

package main

import (
    "fmt"
)

type A struct {
    b B
}

type B struct {
    key string
    Method func(key string, value string)
}

func main() {
    m := func (key string, value string) {
        fmt.Printf("%s => %s\n", key, value)
    }
    b := B{
        key: "hello",
        Method: m,
    }
    a := A{b: b}
    a.b.Method(a.b.key, "world")
}

B 構造体のメンバに関数を持たせることで関数の実装をあとから注入する方法です
こうすることでテスト時には main とは異なる挙動をさせることができるため a.b.Method のスタブが書きやすくなります

package main

import (
    "fmt"
    "testing"
)

func TestMethod(t *testing.T) {
    // stub
    m := func(key string, value string) {
        fmt.Printf("%s => %s\n", key, value)
        fmt.Println("test func")
    }
    b := B{
        key: "hello",
        Method: m,
    }
    a := A{b: b}
    a.b.Method(a.b.key, "world")
}

ただこの場合は冒頭に紹介した B 構造体の仕組みから大きく変える必要があるため既存のロジックにも影響する可能性があります

既存のメソッドのオーバライドはできないか

新しい構造体を作成して B 構造体を組み込んで Method をオーバライドする的なことはできますが B 構造体が持つ Method 自体を変更することは golang ではできなさそうです

Ruby でいうところの「クラスの展開」や「特異メソッド」のように既存のクラスに対する実装の変更などはできないようです (そもそも golang にはクラスの概念はありませんが)

BInterface を使う

これも既存のロジックを書き換えてしまいますが A 構造体が持っていた B 構造体を B インタフェースに変えることであたかも B 構造体のように振る舞う fakeB 構造体をフィールドのセットします
こうすることで関数の振る舞いを上書きしているように見せかけます

package main

import (
    "fmt"
)

type A struct {
    b BInterface
}

type BInterface interface {
    Method(value string)
}

type B struct {
    key string
}

func (b B) Method(value string) {
    fmt.Printf("%s => %s\n", b.key, value)
}

type fakeB struct {
    key string
}

func (b fakeB) Method(value string) {
    fmt.Printf("%s => %s !!\n", b.key, value)
    fmt.Println("by fake")
}

func main() {
    b := B{
        key: "hello",
    }
    fb := fakeB{
        key: "hello",
    }
    a := A{b: b}
    a.b = fb
    a.b.Method("world")
}

一度 B 構造体をセットしているにも関わらずあとからセットしている fakeB 構造体で上書きされているのがわかります
A 構造体を書き換えるのでかなりリスクがある感じはしますが一応これでも対応できました

最後に

golang で構造体が持つ関数をスタブする方法を考えてみました
一番良さそうなのは DI を使う方法かなと思います
自由度も高いのでいろいろなところで使える技だと思います

2019年11月19日火曜日

Mac のディスク容量がその他で逼迫しているときの対処方法

概要

MacOS のアップデートやアプリのアップデートをするとディスクが「その他」の領域で逼迫することがよくあります
そのたびにキャッシュや不要なアプリの設定などを削除するので備忘録として残しておきます

環境

  • macOS 10.15

~/Library/Developer

Xcode のビルドキャッシュやシミュレータのキャッシュ、古い Xcode のバージョンなどが保存されているようです
削除しても問題ないので Developer ディレクトリごと削除しましょう
Xcode を再度開くとディレクトリも再作成されます

~/Library/Containers/com.docker.docker

docker をインストールしている場合にはここが肥大化することがあるようです
不要な docker イメージやコンテナ、ボリュームがあれば削除しましょう

~/Library/Android

昔 AndroidStudio などをインストールした場合に残っていることがあるようです
他にも Android 関連のディレクトリが残っている場合は削除して OK です
参考

~/Library/Caches

ユーザキャッシュになります
キャッシュなので削除すれば OK ですが

インフラツール系のキャッシュ

~/.vagrant.d~/.minikube などです
使っていなければ一旦削除でも良いと思います
~/.local ディレクトリは Python の virtualenvs で作成したプロジェクトが作成されるので不要であれば削除しましょう

メディア系のファイル

GarageBand (~/Music) や iMovie (~/Movies) で編集したファイルは大きくなりがちなので削除するか外部のドライブに避難させましょう

最後に

基本は Library 配下が大きくなるようなのでそこで sudo du -h -d 1 | sort -h を叩いて大きいサイズのディレクトリを削除していけば OK です
削除して OK なディレクトリかどうかは調べてみるとよいと思います

一旦テンポラリのディレクトリに移動してから該当のアプリを立ち上げて挙動を確認してから削除しても良いと思います

2019年11月18日月曜日

go-workers で失敗したジョブをリトライさせる

概要

go-workers ではデフォルトだと失敗したジョブはリトライされません
意図的にリトライさせるにはエンキュー時にリトライをオンにする必要があります
今回はその方法を紹介します 

環境

  • macOS 10.15
  • golang 1.12.9
  • go-workers dbf81d0b75bbe2fd90ef66a07643dd70cb42a88a

事前準備

  • redis-server

失敗するコード

まずはワーカーが失敗するコードを作成します

  • vim main.go
package main

import "github.com/jrallison/go-workers"

func main() {
        workers.Configure(map[string]string{
                "server":   "localhost:6379",
                "process": "1",
        })
    workers.EnqueueWithOptions("default", "SampleWorker", []int{1, 2}, workers.EnqueueOptions{Retry: true})
}
  • vim worker.go
package main

import (
    "fmt"
    "github.com/jrallison/go-workers"
)

func perform(msg *workers.Msg) {
    fmt.Println("perform")
    fmt.Println(msg.Jid())
    a, _ := msg.Args().String()
    fmt.Println(a[0])
}

func main() {
    workers.Configure(map[string]string{
        "server":  "localhost:6379",
        "process": "1",
    })
    workers.Process("default", perform, 1)
    workers.Run()
}

これでそれぞれ実行してみます

  • go run main.go
  • go run worker.go

するとエラーが表示され失敗したジョブをカウントする stat:failed に格納されます

  • redis-cli keys '*'
stat:failed queues stat:failed:2019-11-18
  • redis-cli get 'stat:failed'
1

リトライさせる

エンキューさせる時にリトライオプションを付与します

  • vim main.go
package main

import "github.com/jrallison/go-workers"

func main() {
        workers.Configure(map[string]string{
                "server":   "localhost:6379",
                "process": "1",
        })
    workers.EnqueueWithOptions("default", "SampleWorker", []int{1, 2}, workers.EnqueueOptions{Retry: true})
}

workers.EnqueueWithOptions を使ってエンキューし workers.EnqueueOptions{Retry: true} を指定します
これで実行してみます

  • go run main.go
  • go run worker.go

すると stat:failed はカウントされるものの失敗したジョブの情報が goretry に格納されるようになります

  • redis-cli keys '*'
queues stat:failed:2019-11-18 stat:failed goretry
  • redis-cli zrange 'goretry' 0 -1
{"args":[1,2],"class":"SampleWorker","enqueued_at":1574046394.6627982,"error_message":"runtime error: index out of range","failed_at":"2019-11-18 03:05:57 UTC","jid":"74ffa4ab4e81a809c9bd7002","queue":"default","retried_at":"2019-11-18 03:06:34 UTC","retry":true,"retry_count":1}

しばらくするとリトライされます
リトライの仕組みは Sidekiq とおそらく同じでバックオフを採用しています (30s -> 46s -> 1m16s …)

デフォルトだと最大で 25 回リトライします

回数を指定してリトライさせる

25 回リトライさせない場合はリトライをカウントするインクリメント数を指定します
例えば 2 回リトライさせたい場合には インクリメントする数字を 13 にします
インクリメントする数字はエンキュー時のオプションに RetryCount を付与します

  • vim main.go
package main

import "github.com/jrallison/go-workers"

func main() {
        workers.Configure(map[string]string{
                "server":   "localhost:6379",
                "process": "1",
        })
    workers.EnqueueWithOptions("default", "SampleWorker", []int{1, 2}, workers.EnqueueOptions{Retry: true, RetryCount: 13})
}

今回はテストなので 13 にしています
この数字分リトライカウントがインクリメントされます
インクリメント後に 25 以上の数字になればリトライは終了します

ただ上記の場合次回実行時は 8 時間後くらいになります
さきほど説明した通り Sidekiq はバックオフなので 13 回リトライしたあとの時間分待つことになります

最後に

go-workers でリトライする方法を紹介しました
基本的には Sidekiq のリトライ方式を踏襲しているので最大 25 回でリトライの待ち時間はバックオフになります

デフォルトではリトライは無効になっているので使用する場合にはオプション付きでエンキューしましょう
またリトライ回数を指定したい場合は RectryCount を調整することでリトライ回数を指定しましょう

参考サイト

2019年11月15日金曜日

Google の OAuth で「このアプリは確認されていません」を表示されないようにするまでの道のり

概要

Google の OAuth を使ったアプリを作成していると以下のような画面を見たことがあると思います
Google の OAuth を使ったアプリは Google に正しいアプリなのか確認をしてもらわないと以下の画面が出続けてしまいます

今回はこの画面を表示させない対応をしてみたのでその方法ややったことを紹介します

環境

  • Google Cloud Console (2019/11/07 時点)

サイトの所有者を確認する

おそらく必要だと思います
自分は所有者を確認済みのサイトをそもそも使っていたので確信はないのですがやっておいて損はないと思います

OAuth確認画面でスコープを正しく設定する

OAuth 確認画面で使用するスコープを割り当てます
有効になってる API は一覧に表示されているので必要なスコープを選択しましょう

以下は Google Photos API のスコープを追加している画面です
スコープが一覧にない場合は以下のように手動でスコープの URL を設定する必要があります

ホームページ/プライバシー ポリシーリンクを正しく設定する

ホームページ及びプライバシーポリシーページの設定は必須になります
またここで指定したページがダウンしていても申請は拒否されてしまうのでちゃんとページにアクセスできる状態でなければいけません

確認のため送信

スコープが設定できたら「確認のため送信」というボタンが活性状態になり押せるようになります
なぜそのスコープを追加したのかを入力します
また確認の結果を受け取るためのアドレスを入力します

これで Google に確認を依頼すれば OK です
が、申請が実際に通るまでいろいろと苦労したのでそれも紹介します

Request Denied

申請時にホームページとプライバシーポリシーの URL が必要なのですがそれがアクセスできない状態だったので Request Denied になりました
ちゃんとアクセスできるようにできれば OK です

Action Needed

ホームページに記載してる情報が少なかったため Action Needed になりました
最低限追加したコンテンツは以下の通りです

  • アプリの概要
  • 機能の一覧
  • アプリのアイコン
  • 使用する権限
  • バグなどがあった場合の連絡先

プライバシーポリシーのページも Action Needed になる可能性があるのでしっかりとコンテンツを作成してから申請しましょう

審査を通すコツ

やはりホームページとプライバシーポリシーのページをしっかり作成することだと思います
基本は公式の FAQ や公式のドキュメントをすることです
が、英語だらけでよくわからないという場合にすでに公開されている OAuth アプリを参考にすると良いです

例えば「Google Auth Library」は以下のようになっています
https://cloud.google.com/docs/authentication/production
こんなにたくさんの情報を記載する必要はないかもしれませんがアプリがどういうものなのかどういう情報を扱うのかなどわかるようにしましょう

確認を待つ

あとはひたすら待ちます
コンソール上だと「数週間」と書いてありました
自分は 2019/11/07 に確認依頼をしていますが上記で紹介したように何度かやり取りをしたので申請が完了するまでに結構かかりました

  • 2019/11/07 1 度目申請
  • 2019/11/08 Request Denied の連絡、対応して再度申請
  • 2019/11/09 Action Needed の連絡 (ホームページ)
  • 2019/11/12 再度 Action Needed の連絡 (プライバシーポリシーページ)

で自分はギブアップしました
諦めずに対応して審査が通れば OK です

この状態で再度 OAuth の確認画面を表示すると「このアプリは確認されていません」が表示されないのが確認できると思います

最後に

そもそもスコープを何も使わないのであれば特に必要ありません
スコープの追加や変更をした場合には確認依頼をする必要があります

また「承認済みドメイン」に登録したドメインも確認を依頼すると黄色い三角マークが表示されました
おそらくドメインの所有者の確認もしているはずなのでそういった意味でも冒頭の「サイトの所有者を確認する」をやっておいたほうが良いと思います
ちなみに「承認済みドメイン」はリダイレクト先などに使います

参考サイト

2019年11月14日木曜日

JavaScript でキーボードイベントを送信する方法

$(document).trigger({type:'keydown',keyCode:228});

Chrome のデベロッパーツールのコンソールからも送信できます

2019年11月13日水曜日

FireTV の Webアプリケーションでは Google の OAuth は使えないのか

概要

FireTVアプリは Android or Webアプリケーションのどちらかで開発することができます
Webアプリケーションとして開発を進めていたのですが Google OAuth をどうしても実装することができなかったので備忘録として残しておきます
解決方法があればご教授いただきたいです

環境

  • Ruby 2.6.2p47
  • JavaScript
  • FireTV 3gen
  • Web App Tester

やりたかったこと

Heroku にデプロイした Web アプリケーションを FireTV 上で動作させます
Web アプリケーションは Google OAuth を使ったアプリになっており認証後 Ruby アプリにアクセスできるようになります

FireTV の Web App Tester を使ってデバッグするのですが「disallowed_useragent」になり OAuth を発動させることができません
普通に Chrome や Firefox のブラウザでテストする分には動作します

どうやら Google の使用で WebView からの OAuth リクエストはすべて拒否されているようです (参考)
FireTV では Web アプリケーションの場合には Amazon ウェブアプリケーションプラットフォームと呼ばれる専用の WebView が立ち上がります
それが原因で disallowed_useragent になってしまうようです

結果的に解決できなかったのですがいろいろと試したことを紹介します

試したこと

Silk で一度ログインしてからアプリを起動する

Google 的にはデバイスに内蔵されているブラウザで一度認証すれば再度アプリで認証する必要がなくなるとのことだったので FireTV の Silk ブラウザを使って一度認証してからアプリを開いてみたのですがダメでした
もしかしたら実装方法が悪いだけかもしれませんが

アプリ内から認証時にだけ Silk を立ち上げる

これもできませんでした
Web アプリケーションなので立ち上げる方法は window.open を使ったのですが新規でブラウザは立ち上がらず先に進めませんでした

google-api-javascript-client を使ってみる

JavaScript のみで Google OAuth するライブラリです
認証時に別ウィンドウを開いて行ってくれるので Silk が起動してくれるかなと思って試したのですがダメでした
WebView 内で使っているので当然ダメでした

UserAgent を書き換えられないか

window.navigator.__defineGetter__ を使ってユーザエージェントを書き換えてアクセスしてみたのですがこれもダメでした
もしかしたらこれも実装方法が悪かっただけかもしれません

考察

FireTV + Webアプリケーションの構成では必ず WebView Google OAuth は現状突破できないんじゃないかなと思います
もし Google OAuth を使っているのであれば Silk を使えということなのだと思います

今回は試さなかったのですが FireTV では Android アプリが動作します
なので Android アプリとして作れば Google の公式の Android SDK があるのでこれを使って FireTV でも Google OAuth が使えるようになるのではないかと思っています

参考サイト

2019年11月12日火曜日

JavaScript で UserAgent を変更する方法

概要

JavaScript のみで UserAgent を変更する方法を紹介します
サーバ側で偽装とされる場合もあるので使用する際にはご注意ください
また動作しないブラウザもあるようです

環境

  • Chrome 78.0.3904.97

サンプルコード

  • vim index.html
<html>
  <head>
    <title></title>
    <script type="text/javascript">
      function show_ua() {
        window.navigator.__defineGetter__('userAgent', function() {
          return 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36 (Dummy)';
        });
        var ua = window.navigator.userAgent;
        document.getElementById("ret").textContent = ua;
      }
      window.onload = show_ua;
    </script>
  </head>
  <!-- <body text="#FFFFFF" style="padding: 30px;"> -->
  <body>
    <h2>User Agent</h2>
    <div id="ret"></div>
  </body>
</html>

window.navigator.__defineGetter__ を使うようです

動作確認

  • open index.html

2019年11月11日月曜日

FireTV の WebView で開いたページから Silk が開けるか検証してみた

概要

FireTV アプリを HTML5+JavaScript の Web アプリ構成で作っている場合そのアプリは FireTV の内部的には Amazon WebView という WebView で開かれます
WebView だけで問題ないアプリであれば良いのですが Google の OAuth などは WebView では使えません
そういった場合はデバイスが持つブラウザを使う必要があります
今回はそういったケースを想定して FireTV の Web アプリケーションから Silk と呼ばれる FireTV のデフォルトブラウザが開けるか試してみました

環境

  • FireTV 3gen
  • Web App Tester
  • macOS 10.15

確認アプリ

  • vim index.html
<html>
  <head>
    <title></title>
    <script type="text/javascript">
      function open_url() {
        var nw = window.open('https://kaka-request-dumper.herokuapp.com/');
        document.getElementById("ret").textContent = nw.innerWidth;
      }
      window.onload = open_url;
    </script>
  </head>
  <body text="#FFFFFF" style="padding: 30px;">
    <h2>nw.innerWidth</h2>
    <div id="ret"></div>
  </body>
</html>

ホスティング

  • docker run -d -p 80:80 -v $(pwd):/usr/share/nginx/html --name host nginx

Web App Tester で確認

ホスティングしているマシンの IP を登録して確認します
あとはテストしてみましょう

結論

Silk は開けない

アプリを起動して動作確認しても別のウィンドウが立ち上がったりすることはありませんでした
また FireTV のホーム画面に戻って Silk が起動した形跡があるか確認もしましたが特にありませんでした
もしかしたらどこかでエラーになっている可能性はあるかもしれません

2019年11月10日日曜日

FireTV の WebView のユーザエージェントを調べてみた

概要

公式のドキュメントでは FireTV の WebView は Amazon WebView と呼ばれています
ユーザエージェントのサンプルも記載されているのですが気になったので実際にサーバにリクエストして確認してみました

環境

  • FireTV 3gen
  • macOS 10.15
  • Ruby 2.6.2p47

確認用アプリ

  • bundle init
  • vim Gemfile
gem "sinatra"
  • bundle install --path vendor
  • vim app.rb
require 'sinatra/base'
require 'net/https'
require 'json'

class TestWebApp < Sinatra::Base
  get '/' do
    @user_agent1 = request.env['HTTP_USER_AGENT']
    uri = URI.parse 'https://kaka-request-dumper.herokuapp.com/'
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    req = Net::HTTP::Get.new uri.request_uri
    res = http.request req
    @user_agent2 = JSON.parse(res.body)['headers']['HTTP_USER_AGENT']
    erb :user_agent
  end
end
  • vim config.ru
$stdout.sync = true
require './app'
run TestWebApp
  • mkdir views
  • vim views/user_agent.erb
<html>
  <head>
    <title></title>
  </head>
  <body text="#FFFFFF" style="padding: 30px;">
    <h2>user_agent1</h2>
    <div><%= @user_agent1 %></div>
    <h2>user_agent2</h2>
    <div><%= @user_agent2 %></div>
  </body>
</html>

ホスティング

  • bundle exec rackup config.ru -o 0.0.0.0

Web App Tester から IP でアクセスするためバインドする IP を外部からアクセスできるようにします

Web App Tester で確認

Web App Tester を起動してホスティングしているホストの「IP:9292」を入力して動作確認します
結果は以下の通りです

Mozilla/5.0 (Linux; Android 7.1.2; AFTN Build/NS6268; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36 cordova-amazon-fireos/3.4.0 AmazonWebAppPlatform/3.4.0;2.0

最後の部分が FireTV を特定しそうな文字列でした
WebView -> Ruby アプリのユーザエージェント (user_agent1) が上記になります
user_agent2 は確認用のアプリから外部のサーバにアクセスした際のユーザエージェントなのでそれはサーバのものになりました

おまけ: JavaScript だけで User-Agent を確認する方法

<html>
  <head>
    <title></title>
    <script type="text/javascript">
      function show_ua() {
        var ua = window.navigator.userAgent;
        document.getElementById("ret").textContent = ua;
      }
      window.onload = show_ua;
    </script>
  </head>
  <body text="#FFFFFF" style="padding: 30px;">
    <h2>User Agent</h2>
    <div id="ret"></div>
  </body>
</html>
  • docker run -d -p 80:80 -v $(pwd):/usr/share/nginx/html --name host nginx

最後に

FireTV の WebView のユーザエージェントを調べてみました
実際に外部のサーバから見えたユーザエージェントなので間違いないと思います
FireTV の OS のバージョンによってユーザエージェントの値も変わってくるので参考程度に見てください
実際の自身のデバイスを調べたい場合は同じようにアプリを立ち上げてみるのが良いと思います

2019年11月9日土曜日

Heroku にデプロイしたアプリを Google Search Console のプロパティとして登録する方法

概要

Heroku にアプリをデプロイすると https で xxxx.herokuapp.com というドメインがアプリに振られます
Google の OAuth などと連携することを考慮するとサイトの所有者であることを証明しておく必要があります
今回は Heroku にデプロイした Ruby のコンテナアプリの所有者であることを証明する方法を紹介します

環境

  • Google Search Console (2019/11/07 時点)
  • Heroku Container Registry (2019/11/07 時点)
  • docker 19.03.4
  • heroku cli 7.33.3

Heroku にテスト用のアプリ作成

まずは Heroku にアプリを作成します

  • heroku create -a test-app-20191107

サンプルアプリ作成

Heroku にデプロイする Ruby アプリを作成します
このアプリの所有者であることを Search Console を使って確認します

  • bundle init
  • vim Gemfile
gem "sinatra"
  • bundle install --path vendor
  • vim config.ru
$stdout.sync = true
require './app'
run TestWebApp
  • vim app.rb
require 'sinatra/base'

class TestWebApp < Sinatra::Base
  get '/' do
    'ok'
  end
end

Dockerfile 作成

Heroku にデプロイする際に Container Registry としてデプロイするので Dockerfile を作成します

FROM ruby

ADD . /home
WORKDIR /home
RUN gem install bundler
RUN bundle install --path vendor

CMD bundle exec rackup config.ru -o 0.0.0.0 -p $PORT

$PORT を使ってポートが Heroku によって割り当てられる変数を使うのがポイントです

Search Console にプロパティを登録する

Google Search Console にアクセスしましょう

今回は右側の「URL プレフィックス」を選択します
Heroku に作成したアプリから URL を入力します
今回であれば https://test-app-20191107.herokuapp.com になります

続行を選択するとサイトの所有者であることを確認するための HTML ファイルがダウンロードできるのでしましょう
これをアプリのルートに配置します
また HTML ファイルを配置するまで確認は押さないようにしましょう

HTML ファイルをルートに配置する

  • mkdir public
  • mv ~/Downloads/google1234567890abcd.txt public/google1234567890abcd.html

ダウンロードしたファイルが Chrome だと .txt になってしまうので .html にリネームします
Sinatra アプリの場合ルートは public ディレクトリ配下になります
ここは各自のアプリによってことなると思うので注意してください

イメージ作成

ビルドします

  • docker build -t registry.heroku.com/test-20191107/web .

イメージアップロード

作成されたイメージを Heroku Container Registry にアップロードします

  • heroku container:login
  • docker push registry.heroku.com/test-20191107/web

コンテナアプリデプロイ

イメージのアップロードが完了したらアプリをリリースしましょう

  • heroku container:release web

デプロイできたら確認用の HTML ファイルが見えるか確認しましょう
https://test-app-20191107.herokuapp.com/google1234567890abcd.html という感じのパスで確認できれば OK です

動作確認

アプリがデプロイできたら Search Console に戻って確認ボタンを押しましょう
以下のように「所有権を確認しました」となれば登録完了です

最後に

Heroku にデプロイしたアプリを Google Search Console に登録しサイトの所有者であることを証明しました
やっていることはサイトのルートに HTML ファイルを置いているだけです
なので、アプリによってルートのパスが様々なので各自のアプリにあったパスに HTML ファイルを配置してデプロイしてください
また認証などがある場合も注意してください
認証が全ページにあるようなアプリでは Search Console から HTML ファイルが見えなく確認が失敗するので HTML ファイルのパスだけは認証がかからないような工夫が必要になるかもしれません