WebでHTMLをいい感じにコピーさせる

こんにちは id:mstssk です。

Webサービスを作っていると、何かしらをユーザーにコピーして使ってもらうというシチュエーションが出てきます。 WebページをシェアするためにURLのコピーボタンが置いてある、なんていうのはよく見かけますね。

しかし、ある程度複雑なコンテンツはHTML情報としてコピーさせたい事が稀にあります。 画像やリンクを含んだHTMLです。

そこで、もう少し欲張って、こういうことが出来ないか調べてみました。

3行まとめ

  • ただのテキストをコピーするだけならクリップボードAPIを使えばOK。
  • HTMLをWYSIWYGに貼り付ける状態でコピーさせるには、現在非推奨な実装方法を使いつつ一工夫必要になるけど可能。
  • 一行余った
動作サンプル

stackblitz.com

以下、解説。

Webブラウザクリップボード API

developer.mozilla.org

最近のWebブラウザにはPCのクリップボードを操作するAPIが標準搭載されており、JavaScriptを数行書くだけで任意のテキストをコピーさせられます。

// 「ほげほげ」というテキストをコピーする場合
copyButton.addEventListener("click", () => {
  navigator.clipboard.writeText("ほげほげ")
    .then(()=> console.log("copied!"));
});

好き勝手にクリップボードを操作できるわけではなく制約があります。 ユーザーがボタンをクリックしたりとか何かしら操作を行った時だけクリップボードへのアクセスが許可されています。

このクリップボード APIはどのWebブラウザにもある機能ですが、2020年5月現在ではテキストまたは画像ファイルしかコピーさせられません

2020/07/10 追記

Safari 13.1.1ではクリップボード APIでtext/htmlのコピーが行えるようです。

クリップボードAPIの制限については、執筆時点のものであり、またブラウザごとに対応状況も違っています。 この記事を参考にする前にクリップボードAPIの最新の実装状況を確認するのをおすすめします。

HTMLコピーを無理やり実現するやり方

Hackyなやり方ですが、Chrome, Safari, Firefoxで動く実装はあります。

developer.mozilla.org

document.execCommand("copy"); でコピー動作を直接呼び出す方法は、クリップボードAPIが登場する以前はずっと使われてきましたが、現在では非推奨とされています。 しかし、今回はクリップボードAPIで未対応のデータ(HTML)を扱うため致し方ありません。

実際の実装は以下の通り。 行っているのは、ユーザーがマウスでWebページ内を範囲選択してコピーしているのと同じ事をJavaScriptからやっているだけ。 ただし、プレーンテキストの情報も持たせるために一工夫しています。

const html = `<a href="https://example.com/">
                <img src="https://github.com/viibar.png">
              </a>`;
copyButton.addEventListener("click", () => {
  // コピー対象にするダミー要素
  const elem = document.createElement("span");
  elem.style.height = "0px";
  elem.style.width = "0px";
  elem.innerHTML = html; // ダミー要素の中身が無いとSafariで動作しないので。
  document.body.appendChild(elem);

  // ダミー要素を選択状態にする
  const range = document.createRange();
  const selection = document.getSelection();
  selection.removeAllRanges();
  range.selectNodeContents(elem);
  selection.addRange(range);

  // コピーが行われたとき、コピーデータをすげ替える。ここが一工夫。
  document.addEventListener("copy", function listener(event) {
    event.preventDefault();
    event.clipboardData.setData("text/html", html); // for WYSIWYG
    event.clipboardData.setData("text/plain", html);
    document.removeEventListener("copy", listener);
    console.log("copied!", event);
  });

  // コピー実行
  document.execCommand("copy");

  // 選択状態解除とダミー要素削除
  selection.removeAllRanges();
  document.body.removeChild(elem);
});
実際に貼り付けてみた場合のスクリーンショット

WYSIWYGエディタやMS Wordなんかは、HTMLを解釈してそういうコンテンツとして貼り付けられます。

Gmailのメール作成画面(WYSIWYG) MS Word
Gmailのメール作成画面 Wordの画面

WYSIWYGエディタやWordにただのテキストとして貼り付けたいときは、右クリックメニューにオプションがあるはずです。

ただのテキストエディタでは、HTMLのソースコードをテキストとして貼り付けられます。

Visual Studio Code
Visual Studio Code

GitHubのOrganizationを2FA必須にしようとしたらちょっと大変でした

Viibar開発部の id:mstssk です。

もうしばらく前になるのですが、Viibarで使っているGitHubのOrganizationを2要素認証(以下、2FA)を必須にしました。

Organizationに所属するアカウントを2FA必須にするのは、設定画面でチェックボックスを切り替えるだけで簡単に行なえます。

Orgの2FAの必須設定

しかし、設定を有効にする時に既にOrganizationに2FAを有効にしていないアカウントがあると、そのアカウントは強制的にOrganizationから外されてしまいます。 事前に所属アカウントの2FAをすべて有効にしておかなければいけません。

help.github.com

よっしゃやったろ!と思って手を付けたら、思いのほか手間がかかってしまいました。

既存アカウントを2FAに

まずはOrganizationに所属しているアカウントのうちどれが2FAになっているかを確認していきます。

Organizationの管理権限をもっていれば、所属するアカウントの一覧でそれぞれ2FAになっているかどうかを確認できます。

help.github.com

既に社内のエンジニアのアカウントはすべて2FAになっていました。

他方、Viibarではコーポレートサイトの管理もGitHubリポジトリにしており、エンジニアではない広報担当者のアカウントが2FAになっていませんでした。 その時、近くに座っていらっしゃったので、早速社内で声をかけて2FAを有効にしてもらいました。

謎の共有アカウントを削除

他の2FAになっていないアカウントを見ていくと、 viibar-admin というアカウントがありました。 なにかの管理用アカウントっぽい名前ですが、アクティビティを見ても何年もなにもしていません。

社内で聞いてみたところ、むかーしに共有して使っていたアカウントとのこと。 完全に使っていないので、とりあえずOrganizationから外しました。

アカウント自体は残しておこうかと少し迷いましたが、削除することにしました。

ただ名前の確保のためにアカウントを取得しておくことはGitHubのポリシーに反します。 かつ、使われていないアカウントは思いの外カジュアルに名前を他の希望者に移したりされたりします。 将来的に活用したいというのも現時点では無く、素直にアカウントを消しておきました。

GitHub はアカウント名を不正に占拠することを禁止しており、現時点で使用しないアカウント名を将来の使用のために保持することはできません。 使用されていないアカウントは、GitHub スタッフの裁量で名前を変更または削除される場合があります。

GitHub ユーザ名ポリシー - GitHub ヘルプ https://help.github.com/ja/github/site-policy/github-username-policy

いざアカウントを削除しようとした際に、あれ?パスワードどこ?というひと悶着があったのはまた別のお話。

Machine Userの2FA化

GitHubは様々なタスクの自動化のために専用アカウントを作る事を許容していて、それは Machine User と呼ばれます。

ViibarでもBeaverくんと呼んでいるアカウントを一つ使っています。

さてビーバーくんのアカウントを2FA化しようとして一つ壁にぶつかりました。 アカウントを開発部の中で共同管理出来ている状態を維持しつつ、どうやって2要素目の認証を設定するのか。

GitHubの2FAにはTOTPアプリまたはSMS認証が必要です。 つまり、特定のスマートフォンに依存するのが普通です。

1Password の有料プランであればワンタイムパスワードを共同管理できますが、弊社で使っているのは LastPass です。

どうせ特定のデバイスに依存せざるを得ないなら、セキュリティトークンを社内で管理すればよいのでは?とも思いました。 しかし、セキュリティトークンはTOTPまたはSMS認証を設定した上でのバックアップ的にしか使えません。

最終的には、開発部の共用スマートフォンにTOTPアプリを入れてビーバーくんを2FA化しました。

終わりに

思ったより時間がかかってしまいましたが、これでGitHubのOrganizationを2FA必須にできました。 以後、Organizationに追加するメンバーにも常に2FAを要求します。 いちいち「セキュリティのために2FAにしてね」などとアナウンスしなくても済むようになりました。

セキュリティはもちろん大事ですが、常にセキュリティを意識するのは大変です。 Organizationの設定ひとつで所属メンバーに常に2FAを必須にできるのは楽ちんなので、この記事をご覧になった方もお忘れでしたら設定しましょう。 もし問題が起きたら、この記事が参考になれば幸いです。

ピアボーナスの投稿状態をグラフデータベースで見てみる

こんにちは。id:umanodaです。 2020年3月から弊社で unipos をつかったピアボーナスを導入しました。

前々から社内には、Slack上のコメントに特定のスタンプが押されたことを集約するチャンネルがあり、他部署で発生した称賛などをウォッチすることができました。 ピアボーナスを導入したことで、部署を超えて他部署の人に感謝する空気が生まれてより良くなったなあと感じています。前からも他部署の人を積極的に称賛する人は一部いたのですが、仕組みとしてそのマインドが全社的に広がったのかなと。

で、このピアボーナスですが 「AさんがBさんに感謝を送る」 という方向性のある行動が、グラフデータのサンプルとして良さそうじゃないか? と思ったので、社内のやり取りを neo4j に打ち込んでみたよというのがこの記事の趣旨です。

Setup & データ投入

ざっとどうやったのかを概略だけ紹介

  • Mac/Homebrewでneo4jをインストール
  • uniposの投稿一覧画面で、Chrome Dev toolのコンソールを使い、HTML要素から「投稿者」「投稿先」「投稿日時」のCSVをjsにて作成。 API でもデータ取得できるらしいが、今回は遊びなのでお手軽のこの方法をついました
  • neo4jrb/activegraphCSVデータをDBにインサート
    • "MATCH (a:Person{name:$from}), (b:Person{name:$to}) CREATE (a)-[:Like]->(b)" 的なデータ構造
    • 同じ人から同じ人への称賛が複数回あった場合でも、1Likeとしてデータ投入。(解析時に同一人物同士の称賛をDistinctするのが面倒だったので)

観察

とりあえず全部のNodeを表示してみました。

f:id:umanoda:20200401143557p:plain
neo4j-all

ごしゃぁ…線が絡み合っていてよくわかりません。 やる前は、部署ごとにクラスターっぽいものやハブっぽい人がわかったら面白いなあと思っていたのですが、厄介な事に部署間交流が盛んなようです。

データを絞って傾向を探るため、自分を中心に

  • 一度でも称賛した人
  • 一度でも称賛してくれた人

をだしてみました。

f:id:umanoda:20200401143639p:plain
neo4j-shota-1-link

ふむふむ。左の方の人は相互に褒めあっていって、活発に使っているユーザーっぽいですね。ちなみに灰色の輪っかで囲まれているのが umanoda のNodeです。 さらにこの人達に称賛した/された人もたどってみます。

f:id:umanoda:20200401144108p:plain
neo4j-2-link

ごしゃぁ...。灰色の輪っかがumanodaですが、皆さん活発にuniposを活用して見るみたいです。 SNSで3人友だちをたどればアメリカ大統領とつながるとかいいますし、100人足らずの社内ならこうなるのも仕方ない。

ジーッと図を眺めていると、妙に線(Edge)をたくさんはやしている人が何人かいます。ふむふむ、どうやら称賛をたくさん活用しているアクティブユーザーがいるみたいですね。10以上に賞賛を送った人を調べてみます。

f:id:umanoda:20200401150055p:plain
neo4j-10over-crap

ほほーこの人達が、などと一人で楽しむなどしました。

なお、社内の人にはあんまりアクティブユーザーとか気にせずゆるく使ってほしいし、特に共有はしない(個人的な主目的はグラフデータベースを使うことそのものだったというのもある)

まとめ

とまあ、こんな感じで unipos 導入後のデータを使ってグラフデータで遊んでみました。

社内でみんなから称賛されている注目株の人に、早いうちから媚を売るなどの活用方法がありそうです。嘘です、冗談です。

グラフデータベースは、RDBとは違う視点でのデータ分析などができます。グラフデータベースを使って物と物の関係性を見てみるというのは面白いので、興味のある方は是非身近なテーマでトライしてみてください。

クラスメソッドメンバーズをやめていいんじゃないかと検討して継続になった話

メリークリスマス!

id:shrkw です。今日で現職最終日だからというわけではないですが、最近、クラスメソッドメンバーズをやめていいんじゃないかと検討しているので、そのことを書きます。

サービス自体は AWSの請求代行から構築まで支援「クラスメソッドメンバーズ」|クラスメソッドのサービス の通りで、利用料割引とサポートが主な利点だと思います。

弊社はプレミアムサービスに加入しており、それは本当に助かっているのですが、いくつか未知だったデメリットがあったので記録しておきます。

デメリット 1: AWS Organizationsが利用できない

メンバーズにログインすると閲覧できるFAQに以下のように記載があります。

AWS Organizations を利用することはできますか?

弊社メンバーズサービスを利用いただいている AWS アカウントでは、AWS Organizations をご利用できません。

クラスメソッドメンバーズでの請求代行はクラスメソッド側のOrganizationsの配下に入ることで実現されるので、自由にOrganizationsを利用することはできません。正確には、絶対利用できないわけではなく、メンバーズ加入前に作成したOrganizationsはそのまま使えて、加入後にOrganizationsを作るのがダメなようです。ちょっと前に見たときは面倒なフォームから申請すれば作れたような気がするのですが、変わったのかな。

最近のAWS管理はOrganizationsを複数分けてACLにするというのが王道だと思うのですが、それができないというのが非常に痛いです。

デメリット 2: AWS SSOが使えない

AWS Organizationsが自由に使えないため、AWS SSOが利用できません。 FAQは下記。

AWS Single Sign-On (SSO) は利用できますか?

AWS Single Sign-On (以降、AWS SSO) を利用する際の前提条件として AWS Organizations のマスターアカウント管理権限が必要であるため、メンバーズへ加入されている AWS アカウントでは AWS SSO を利用することができません。(2018年 1月現在)

AWS SSOもセキュリティ管理に大きな利点のあるサービスなのでこれが使えないのというのもとても痛いです。

デメリット 3: 請求から支払いまでのサイトが30日しかない

弊社の場合、請求が毎月10日頃通知されてきます。これだけならいいんですが、支払い期限が翌月10日だったりして、支払いまでの期間が短く、経理部門に負担を掛ける運用になってしまっています。

サービス紹介サイトにはこう書いてあるけどなんで違うんだろ。

  1. 支払い方法、支払いサイクルを教えてください

銀行振込(円建て)でのお支払いとなります。為替レートは三菱UFJリサーチ&コンサルティングの月中平均を利用しています。お支払いの期日は月末締め翌月末支払いとさせていただいております。ご希望に応じてドル建てでのお支払いも可能です。

デメリット 4: Developers.ioで紹介された機能でも使えないと悲しい

上記のAWS SSOのように、クラスメソッドメンバーズであるせいで使えないものがDevelopers.ioで紹介されていたりすると、非常に悲しい気持ちになります。

デメリット 5: Organizationsが利用できないなどの情報がログインしないと閲覧できない

サービス加入時には説明があると思うんですが、デメリットになりうる情報こそ、パブリックに公開されている状態になっていてほしいなと思います。

社内で話して継続を決めた

などなど色々厳しい点があるのですが、サポート問い合わせのやりやすさ、脆弱性診断などが有償オプションとして可能という点が、人的リソースが常に足りないスタートアップとしては大きなメリットであり、加入継続となりました。上記のOrgの課題はAWSではどうしようもないところなのでしょうがないかなと思いつつ、情報公開などについてはもうちょっと改善してくれるといいなと思っております。

zencoder ことはじめ

こんにちわ、id:gaoohです。

動画の変換処理にzencoder を導入してそこそこよかったのでまとめます。

Viibarという会社はB2B動画にかかわるトータル業務を請け負っています。 サービスとしてはVyncというクラウド制作管理ツールを提供していますが、基本的にサービスとして動画に関わることが多いので、社内ツール含めて動画ファイルはよく扱います。

viibar.com

動画ファイルと言っても、一般的にYouTubeや各種プラットフォームで再生される動画は mp4 ( h.264 ) であることがほとんどです。 ただしそれ以外の媒体に入稿する場合もありますし、元データは必ずしも mp4 ( h.264 ) ではないこともあります。 なので、エンコード処理は切っても切り離せません。

かと言ってこれらの処理は時間も計算リソースも食うのでなるべくならクラウドサービスにまかせたい。 ffmpeg とかで頑張りたくない!

AWS上にシステム展開している場合は、現状だとAWS Elemental MediaConvert を使うのが一番妥当な選択肢だと思います。ただいろんな事情でVyncではElasticTranscoder を利用してはいますが。 とはいえElasticTranscoderでもよほど特殊な変換をしない限り事足りますし、スケールしたい場合の方法も用意されており、もろもろ安心感もあります。

以前はAWS上にシステムを構築することが多かったのでそれ以外の選択肢を考える必要もなく済んでいたのですが、最近は用途によってGCP上に構築することも多く、GCP上にはお手軽に試せるAWS Elemental MediaConvert相当なフルマネージドなサービスはないので(2019/12月現在)、「動画のエンコードどうしよう」という問題になりました。

もちろんシステムとしてはGCP上に構築して、ファイルはs3上に保存し、トランスコードはAWSでみたいな構成はできなくはないけれど、うーん、もうちょっと楽したい。

ということでいろいろ外部サービスを検討した結果 zencoderの導入をしました。

zencoder.com

GCS との連携

GCP上で連携したいGCSバケットの読み込みと書き込みを許可したサービスアカウントを作成し、Access keyを発行、それをzencoder に設定します。

Google Cloud Storage トリガー を利用して、アップロードされたファイルを変換する

zencoder は node 用のライブラリがあるのでこれを利用します

github.com

sampleコードは筆者がTypeScript推しなので TypeScript です。

import * as Zencoder from 'zencoder';


export const transcode = functions
  .region('asia-northeast1')
  .storage.object()
  .onFinalize(async object => {
      
  // zencode側のAPIキーとCredentialsのNicname、出力バケット名などはfunctions.config()で管理
  const config = functions.config();  
  const outputBucket = config.output.bucket;
  const credential = config.zencoder.credential;
 
   const params = {
      input: `gcs://${this.object.bucket}/${this.object.name}`,
      credentials: credential,
      region: 'asia-tokyo',
      outputs: {
        url: `gcs://${outputBucket}/${this.object.name}`,
        credentials: credential,
        format: 'mp4',
        headers: {
          'Content-Type': 'video/mp4'
        },
        quality: 4,
      }
    };

    const zencoder = Zencoder(config.zencoder.api_key);
    await this.zencoder.Job.create(params)
      .then(({ data }) => {
        logger.info(`zencoder request: ${JSON.stringify(data)}`);
      })
      .catch(err => {
        logger.error(`zencoder request error: ${JSON.stringify(err.errors)}`);
      });
  });

お手軽!

変換エラー時の処理

フルマネージドなAWS Elemental MediaConvert や ElasticTranscoder の場合、処理の開始と完了はSNS経由で通知を受け取ったり、SQSへ貯めるなどの対応可能です。

zencoderの場合は正常にエンコード処理が終了したら、指定した場所にファイルが作られるだけなので、エラー通知やJob管理が必要なら別途作り込む必要があります。APIでJobの進行状況や完了状況は問い合わせ可能なので、仕組み的には対応は可能です。

変換スピード

zencoderは公式でパフォーマンスは数百件の同時ジョブ送信のテストにおいて Amazon Elastic Transcoder の平均 14 倍といっています。 利用しているシステムが違うため、パフォーマスを正しく比較しているわけではないので、同時処理数が多い場合は比較検討の余地があるかと思います。

料金

zencoder側はプランによって違うので比較はしにくですが、一番安い月1000分までで1分あたり4.3円で、用意されている最上位プランまであげると1分あたり2.1円ぐらい。 対してAWS Elemental MediaConvert は出力フォーマットによってかなり値段が違いますが、だいたい1分あたり2.3円ぐらい。 システム全体でいうと、当然ファイルの転送量などを加味されるので、適用するシステムによる部分はありますが、単体の料金的にはどちらも遜色ない感じです

社内向けWebアプリのフロントをスプレッドシート+GASで作る

こんにちは。Webエンジニアの @umanoda です。 今年のブラックフライデーfitbit versa 2 を買いました。使い勝手よくて気に入っているのですが、手首と電極が触れているところがカブれてしまいました……。惜しい。

さて最近、社内向けのWebアプリをGASで作成しました。 弊社では G suite を利用して社内アカウントを管理しています。GASを利用することで、Webアプリで必要になるユーザー認証周りの処理をまるっと省略できるのが便利だと感じたので紹介したいと思います。

実現したいこと

  • G suiteに登録されている社内ユーザーにWebアプリでサービスを提供したい
  • 誰が実行者かを判別したい
  • 実行者にファイルをダウンロードさせたい

実現方法

共有ドライブに置いたスプレッドシートとGASをフロントとして利用しました。

見た目にはこだわらない、動けばいいやのショボアプリです。ざっくりと以下のような仕組みで動きます

f:id:umanoda:20191223190746p:plain

  • フォームっぽいものをセルで表現する
  • ボタンっぽいものを図形の挿入で表現する
  • ボタンにスクリプトを割り当て、クリックしたときにGASが実行されるようにする
  • GAS内部でセルの情報を読み取り、バックエンドのAPIにリクエストを投げる。バックエンドはGoogle Cloud Functionsを用意
  • ファイルをダウンロードするために、モーダルダイアログを開く

今回、スプレッドシート+GASをつかってファイルをダウンロードするアプリで、こういう工夫をしたというところを二点紹介します。 なお、GASの一般的な使い方の説明は今回しません。

GASでのユーザー認証

社内システムではあるのですが、特定の担当者にだけ利用させたいというニーズがあり、実行ユーザーの制限を必要がありました。 GASの Session.getActiveUser()) によって操作しているG suiteにログインしているEmailを取得することができるので、これを利用します。

実行できるユーザーを限定する場合、「シートの保護」機能をつかって一部のユーザーにしか変更できないシートを用意すると便利です

// GAS: Code.js
function main() {
  if (!_validateAccount()) {
    SpreadsheetApp.getUi().alert("実行権限がありません");
  }

  // 続きの処理。APIリクエスト組み立てなど
}

function _validateAccount() {
  var currentAccount = Session.getActiveUser();

  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName("管理者用");
  var range = sheet.getRange(1, 2, sheet.getLastRow(), 1); // 「管理者用」シートのB1から最後まで取得
  var rangeValues = range.getValues();

  for (var idx=0; idx < rangeValues.length; idx++) {
    let row = range_values[idx][0];
    if (row == currentAccount) {
      return true;
    }
    return false;
  }
}

注意点としては、GASはスプレッドシートの編集権限があれば変更することができます。実行ユーザーの権限チェックをコメントアウトするなどされると、このシートにアクセスできる人ならば誰でもチェックをすり抜けることができてしまいます。 厳密にやるなら、APIリクエストのサーバーでも権限チェックが必要でしょう。あくまでここでは、「余計なリクエストが走らないためのチェック」「誰が担当者として指名されているのかの明示化」程度のもの割り切りきっています。

ファイルのダウンロード

GASの処理はサーバー側で完結してしまうため、ユーザーにファイルをダウンロードさせるには一工夫が必要です。 テンプレートファイルを用意することで、レンダー結果がiframeに描画されたモーダルダイアログをスプレッドシート上に表示することが出来ます。

Ref. HtmlService | G Suite Developer

// GASでテンプレートを呼び出す

// hoge.html をテンプレートとして使う
var template = HtmlService.createTemplateFromFile("hoge");

// 変数に値やメソッドを設定
template.huga = "aiueo"
template.piyo = function () { alert("kakikukeko") }
テンプレート側での呼び出し
<div>
  <?= huga ?> // 値の埋め込み
  <? piyo() ?> // メソッドの実行
</div>

これを利用して、以下のようにするとGASで生成したURLからファイルをダウンロードすることが出来ます。

<!-- GAS: index.html -->
<html>
<body>
  <p>ダウンロード中です</p>
  <a id="download" href="<?= url ?>" target="_blank">ダウンロード</a>

<script>
  var a = document.getElementById("download");
  a.click();
</script>
</body>
</html>
// GAS: Code.js
var template = HtmlService.createTemplateFromFile("index");
template.url = downloadUrl;
SpreadsheetApp.getUi().showModalDialog(html.evaluate(), "Dialogタイトル");

コード管理

GAS上で使えるエディタだと、最低限の構文チェックのみしかされません。ESLintなどを活用するため、ローカルでコーディングして clasp を使ってデプロイするようにしています。

Typescriptで書いたコードをclaspでデプロイすると、自動的にトランスコンパイルしてサーバー上に配置してくれるのがとても便利です。

まとめ

社内向けのちょっとしたサービスを作る際のTipsとして、スプレッドシート+GASでのフロントエンド構築について書きました。

G suite管理下で公開範囲を限定したサービスを手軽に作れるのが最も大きな利点です。その他、スプレッドシートの入力規則を利用して手軽に入力フォームを作成できるのも良い点かなと思います。

欠点としては、スプレッドシート上が自由に編集できてしまうため、容易にアプリケーションを壊せてしまいます。防ぐためには「シートの保護」機能を使って編集できるセルを限定するなどの工夫が必要となります。

公開範囲をカスタマイズしたり、もっとリッチなGUIを提供するのならば、Firebaseを使うのも良いでしょうね。

MySQLとmacOSにおける大文字・小文字の扱いについて知ったこと

こんにちは。開発部でWebエンジニアをしているjwmxです。

最近、よく利用しているスーパーがセルフレジを大幅に増設し、5・6台あった従来のレジが2台になりました。
時代の流れを感じます。

さて、先日、ローカル開発環境(Mac)で通ったMySQLのSELECT文を別の環境(GCP Cloud SQL)で実行するとシンタックスエラーになることがありました。
原因はテーブル名のエイリアスの大文字・小文字を間違えていたという凡ミスでしたが、その時はじめてmacOSMySQLにおける大文字・小文字の扱いを意識したので調べたことを簡単ですがまとめます。

以下は調査したときの環境です。

MySQLにおける大文字・小文字の区別

公式ドキュメントの9.2.3 Identifier Case Sensitivityにありました。

MySQLにおいてデータベースはデータディレクトリ内のディレクトリ・ファイルに対応しているため、ファイルシステムが大文字・小文字を区別するかどうかが影響するそうです。
具体的にはデータベース名、テーブル名、テーブルのエイリアス、トリガー名が影響を受けます。

また、テーブル名とデータベース名(エイリアスも)についてはlower_case_table_namesというMySQLのシステム変数の影響も受けます。

以下は設定できる値と意味です。
※記載内容はMySQL5.7のドキュメントと同じだったので日本語訳してあるMySQL5.6のドキュメントから持ってきてます。

意味
0 テーブル名とデータベース名は、CREATE TABLE または CREATE DATABASE ステートメントで指定された大文字または小文字を使用してディスク上に格納されます。名前比較では大文字と小文字が区別されます。大文字小文字を区別しないファイル名を持つシステム (WindowsOS X など) で MySQL を実行する場合、この変数を 0 に設定しないでください。大文字と小文字を区別しないファイルシステムで --lower-case-table-names=0 を使用して強制的にこの変数を 0 に設定し、大文字と小文字を変えて MyISAM テーブル名にアクセスした場合、インデックスが破損することがあります。
1 テーブル名はディスク上に小文字で格納され、名前比較では大文字と小文字は区別されません。MySQL では、保存およびルックアップ時にすべてのテーブル名が小文字に変換されます。この動作はデータベース名やテーブルエイリアスにも適用されます。
2 テーブル名とデータベース名は、CREATE TABLE または CREATE DATABASE ステートメントで指定された大文字または小文字を使用してディスク上に格納されますが、MySQL ではルックアップ時に小文字に変換されます。名前比較では大文字と小文字が区別されません。これは大文字と小文字が区別されないファイルシステムでのみ機能します。InnoDB テーブル名は lower_case_table_names=1 のように、小文字で格納されます。

デフォルトの設定値は、UnixmacOS除く)は0、Windowsは1、macOSは2です。
大文字・小文字を区別するのはlower_case_table_names=0のみで、lower_case_table_names=2となっていたローカルのMacで0を指定してもMySQLは起動エラーとなりました。

Cloud SQLのOSが分かる情報は見つけられなかったですが、ふるまいから察するに大文字・小文字を区別するプラットフォームのようですね。

こういったプラットフォーム毎の違いによる問題を回避するために、一貫した命名規則を設けることを公式ドキュメントでは推奨しています。

macOSにおける大文字・小文字の区別

macOSはフォーマット時に大文字・小文字を区別するかどうかを選択できます。

Mac の「ディスクユーティリティ」で利用できるファイル・システム・フォーマットより抜粋。

macOS 10.13 以降を使用する Mac コンピュータに、以下の APFS ファイル・システム・フォーマットのいずれかを選択します。

  • APFS:APFS フォーマットを使用します。
  • APFS(暗号化):APFS フォーマットを使用し、ボリュームを暗号化します。
  • APFS(大文字/小文字を区別):APFS フォーマットを使用し、ファイル名およびフォルダ名の大文字/小文字を区別します。たとえば、「Homework」と「HOMEWORK」という名前のフォルダは、2 つの異なるフォルダです。
  • APFS(大文字/小文字を区別、暗号化):APFS フォーマットを使用し、ファイル名およびフォルダ名の大文字/小文字を区別し、ボリュームを暗号化します。たとえば、「Homework」と「HOMEWORK」という名前のフォルダは、2 つの異なるフォルダです。

新品のMacを購入したときは「APFS」になっているはずですし、いままでフォーマットするときも最初に選択してある「APFS」を無意識に選択していました。

ちなみに、「APFS(大文字/小文字を区別)」でフォーマットされていれば、MySQLlower_case_table_namesに0を設定できました。デフォルトで0が設定されます。

なお、大文字・小文字を区別しないファイルシステムmacOSAdobe製品をインストールできない問題があるようです。

以下はmacOS 10.12以前のファイルシステムであるHFSで発生した問題で、APFSでも発生するか試していないため不確かですが、「APFS(大文字/小文字を区別)」を使いたい場合は注意した方が良いかなと思います。

helpx.adobe.com

まとめ

MySQLの実態はディレクトリ・ファイルということを実感できる良い機会でした。
これまでなんとなくMySQLを使っていたんだと改めて感じました。