『星野、目をつぶって。(13)』を読んだ

榊は星野を信頼していたから、メイクのことを黙っていたことが悲しかったし、一方で星野は榊に憧れていたから、幻滅されたくなくてメイクのことを隠した。 お互いが相手を大切に思うあまりに起きた、ボタンの掛け違い。榊にアクシデントがあったが、そのおかげで腹を割って本音をぶつけることができたのは怪我の功名だな。青いね。青春だ。

松下と加納は、まさか2人が親友的な関係になるとは、最初の頃からはとても想像できなかったな。加納が歩み寄った理由が男だったとしても、実際に変われるのは凄いことだし、松下もそんな加納に対して結果を出して答えていて、宿敵と書いてトモと呼ぶみたいな関係で良いね。

小早川と星野はこのままくっつかずに終わるのかと思いきや、まさかコミックに後日談が収録されているとは思わなかったな。マガジン本誌に掲載されていなかったよな?2人が今後どうなるかは読者の想像におまかせかと思っていたので、作者が正史を示してくれて満足した。

『アオアシ(14)』を読んだ

福田監督に発破をかけられた冨樫や黒田といったAチームの1年は夜練を始めるわけだが、その姿はBチームの1年にも影響を与え、巻き込んでいく。自分より先を行っているライバルのそんな姿を見せられたら、堪えるし燃えるだろうな。そして、予想だにしなかった福田監督の登場は胸熱だった。

葦人・花・杏里の三角関係も目が離せない状況になってきた。杏里が意外と積極的でグイグイ来る。かと思えば過保護な一面も見せたり。最近のウジウジしている花はイマイチなので、個人的には杏里推しなのだが、三人の関係はどうなることやら。

そして物語は2ヶ月とんで、いよいよプレミアの天王山へ。上位3チームの直接対決に入ると思われる。2ヶ月で葦人たちはどれくらい成長したんだろうな。続きが楽しみで仕方ない。

それにしても青森星蘭の10番、この後姿はどう見ても柴崎がモデルだろ。青森の高校だし。栗林に匹敵する選手だったりするんだろうか。

Cloud Functions as a Frontend for Firestore

Web から Firestore を使うと、アクセスキーが開発ツールから丸見えになってしまうのが不安。いやまぁ、ちゃんとアクセスルールを設定すれば問題ないんだろうけど、自分の Firestore スキルはまだそこまでではない。うっかり設定し忘れもあり得る。

いっそ、Cloud Functions for Firebase で Web API を実装し、その中から Firestore にアクセスすれば、アクセスキーを覗かれずに済むんじゃないか?というわけでやってみた。

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as express from 'express';

const serviceAccount: admin.ServiceAccount = {
  "projectId": "FIrebase プロジェクト ID",
  "privateKey": "サービスアカウントに発行したアクセスキーの JSON の privateKey",
  "clientEmail": "サービスアカウントに発行したアクセスキーの JSON の clientEmail",
};

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
});

const db = admin.firestore();

const app = express();

app.get('/items', async (req, res) => {
    const snapshot = await db.collection('items')
        .orderBy('createdAt', 'desc')
        .get();

    const items = [];
    snapshot.forEach((doc) => {
        const item = doc.data();
        items.push(item);
    });

    res.writeHead(200, {
        'Content-Type': 'application/json',
    });
    res.send(JSON.stringify(items));
});

app.get('/items/:id', async (req, res) => {
    const id = req.param('id');
    const doc = await db.collection('items')
        .doc(id)
        .get();
    if (doc.exists) {
        const item = doc.data();

        res.writeHead(200, {
            'Content-Type': 'application/json',
        });
        res.send(JSON.stringify(item));
    } else {
        res.status(404);
    }
});

export const api = functions.https.onRequest(app);

せっかく Firestore 使っているのに、Cloud Functions for Firebase を使って Web API を実装するというのは本末転倒ではあるけど、Firestore を使いこなせるようになるまでの繋ぎってことで。

兼虎 天神店

つけ麺を出す店が福岡に増えてきたとはいえ、その数はまだ少ない。そんな希少なつけ麺屋で一番のお気に入りが『兼虎』。赤坂にあったんだけど、先月天神南に移転したみたいなので行ってきた。

先月からずっと、昼はいつも外に行列ができていた。平日昼は無理かなと半ば諦めていたところ、今回は運よく店内の待ちのみ。5分くらいで席に着けた。食券機で購入したのは、いつも食べている『濃厚つけ麺』。

普通盛から中盛に増やしても無料なのが嬉しい。もちろん中盛。国産小麦の太麺はしっかり締めてありツルツルで、麺だけ最初に味わっても美味いやつ。

この麺を魚粉たっぷりでドロリ高濃度なつけ汁にくぐらせて食べると、もう最高オブ最高。チャーシューも肉厚でトロトロで良い出来だった。やっぱり兼虎のつけ麺が自分の中で今のところ一番だな。

天神に移転してからようやく食べることが叶った。ただ、今回はホント運が良かったので、やはり平日昼は難しいだろうな。どうも席は1階だけみたいだったし。前の店舗より席数減ってないか?アクセスは圧倒的に良くなったので、その点は嬉しい。客足が落ち着いてきたらちょくちょく食べに行きたい。落ち着くことがあるのか怪しいけども。

兼虎

食べログ 兼虎

『渡くんの××が崩壊寸前(6)』を読んだ

恋心を自覚したマキナが攻勢に出るも、今のところどれも空振りに終わっている。危うさがあるキャラしかいない本作において、マキナも例外ではないんだけど、ケジメを付けつつ渡くんのことは諦めないと宣言した態度は好感が持てる。石原さんより読んでて面白い。

石原さんの危うさはあいかわらず。 石原さんが性急な理由は紗月の存在だろうけど、渡くんの腰が引けてるのにもちゃんと理由があったのか。自身の出生にそんな背景があるなら、前向きでないのは仕方ないと思う。結局は流されちゃうんだけどね!

石原さんの母親も登場。最初は絵に描いたような理想的な家族かと思ったが、所々に描写される違和感。やはりまともな登場人物は出てこないのか。予想はしてた。まだ本性を表しておらず、今回はその片鱗だけ。娘を持つ親として気持ちは、まぁ理解できる。次巻ではこの母親がいろいろとちょっかい出してきそう。

『ぼくたちは勉強ができない(8)』を読んだ

複数のヒロインを絡ませた話が増え、新しい面白さの境地を開拓している。今回は、うるかと桐須先生という、今までなかったのが不思議な組み合わせ。桐須先生は元フィギュアスケート選手だったので、アスリート同士、馬が合いそうだとは思っていた。

そもそも、うるかは文乃や理珠と違って、才能を無駄にしようとはしていないから、2人とは前提が違うんだけどね。成幸に教育係をしてもらっているのは、推薦入試に英語が必要だからで、いわば才能を活かすために弱点を克服しようとしている。才能の味方である桐須先生が冷たく当たる理由がない。

そして、8巻のメインは学園祭。6話にもわたる長編シリーズは本作初めてだろう。学園祭のクライマックスで、成幸と結ばれる相手がついに発表!

…シルエットだけというオチだった。とはいえ「全員に触れていました」という方向で濁したわけではないのは好印象。読者にはまだ相手がわからないけど、ヒロインの誰かと結ばれるのはハッキリさせたわけだから。

本命文乃、対抗うるか、大穴桐須先生ってとこだろうか。1話から登場していて、父親エピソードを残している文乃は最有力。うるかはなんだかんだいって、成幸が一番意識している異性に思える。ホント、何度も結ばれるチャンスがあったのにもったいない。グッドルーザーになってしまいそうな予感がする。桐須先生は可能性かなり低いと思うが、物語の結末は読者の力によって変わることはありえる。人気投票1位だったし。成幸の進路は特別VIP推薦希望以外決まってないけど、教育係の経験を生かして教師になりそう。相手が桐須先生というのもアリでは。だいぶ希望が入ってしまってるな。

.NET で Firestore

Firebase を使ってアプリをサクッと作れるようになりたかったので勉強することにした。まずは Firestore。アプリは Xamarin で作るつもりなので、C# から Firestore を触ってみる。NuGet で専用のパッケージが公開されているので、それを使う。まだベータ版だけど、Firestore 自体まだベータ版だし。

www.nuget.org

Firestore で CRUD をやるサンプルを書いてみた。コードはサクッと書けたけど、GCP 外から Firestore にアクセスするために必要な、アクセスキーファイルを入手するのが最初にして最大の関門だった。GCP の 「API とサービス」の「認証情報」でサービスアカウントを作成して、アクセスキーファイルをダウンロードしておかないといけない。

using Google.Cloud.Firestore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace FirestoreSample
{
    class Program
    {
        const string ProjectId = "your-project-id";

        static void Main(string[] args)
        {
            // API とサービスの認証情報でサービスアカウントを作成し、
            // ダウンロードしたアクセスキーファイルのパスを環境変数で指定
            Environment.SetEnvironmentVariable(
                "GOOGLE_APPLICATION_CREDENTIALS",
                Path.Combine(AppContext.BaseDirectory, "your-key.json"));

            MainAsync().GetAwaiter().GetResult();

            Console.WriteLine("Press Enter Key.");
            Console.ReadLine();
        }

        static async Task MainAsync()
        {
            var db = FirestoreDb.Create(ProjectId);

            // books コレクションにドキュメント追加
            var book1Ref = await db.Collection("books")
                .AddAsync(new Dictionary<string, object>()
                {
                    ["title"] = "かぐや様は告らせたい",
                    ["price"] = 500,
                });

            // books コレクションに ID 指定でドキュメント追加
            var book2Ref = db.Collection("books").Document("bokuben");
            await book2Ref.CreateAsync(new Dictionary<string, object>()
            {
                ["title"] = "ぼくたちは勉強ができない",
                ["price"] = 450,
            });

            // books を価格順にソートして取得
            var querySnapshot = await db.Collection("books")
                .OrderBy("price")
                .GetSnapshotAsync();
            foreach (var doc in querySnapshot.Documents)
            {
                Console.WriteLine($"{doc.GetValue<string>("title")}({doc.GetValue<int>("price")}円)");
            }

            // ドキュメントを更新
            await db.Collection("books")
                .Document("bokuben")
                .SetAsync(new Dictionary<string, object>()
                {
                    ["price"] = 420,
                }, SetOptions.MergeAll);

            // ID を指定して 1 件取得
            var documentSnapshot = await db.Collection("books")
                .Document("bokuben")
                .GetSnapshotAsync();
            Console.WriteLine($"{documentSnapshot.GetValue<string>("title")}({documentSnapshot.GetValue<int>("price")}円)");

            // ドキュメントを削除
            await db.Collection("books")
                .Document(book1Ref.Id)
                .DeleteAsync();
            await db.Collection("books")
                .Document("bokuben")
                .DeleteAsync();
        }
    }
}

実行してみた結果は下の通り。

f:id:griefworker:20180904163909p:plain

Xamarin だとロジックの大部分を iOSAndroid で共有できるので、Firestore を単純にデータベースとして使い、ロジックを .NET Stadard ライブラリとして書いて、iOSAndroid から使う戦略が使えそう。