20200924のJavaScriptに関する記事は30件です。

【Rails】ページの上部に戻るボタン

 はじめに

・Railsを使用してアプリケーションを開発
・viewsの下部地点から上部に戻るボタンを設置する

Ruby 2.5.7
Rails 5.2.4.4

流れ

gem 'jquery-rails' を導入
・アクションを起こすボタンを用意(画像でなく文字やfont-awesome等でも代用可能)
・viewにボタンを記載しcss及びjavascriptへ追記

ボタン画像

top_btn.png

適当に準備します・・・

画像ファイルをapp/assets/images配下に格納

viewsへ追加

<span id="top-btn">
  <a href="#">
    <%= image_tag asset_path('top_btn.png'), class: 'top-btn' %>
  </a>
</span>

・asset_path('~~') は、app/assets/images配下の画像を読み込む

css 及び jsファイルへ追記

.top-btn {
  position:   fixed;
  right:      30px;
  bottom:     40px;
}

.top-btn a {
  width:      50px;
  height:     50px;
}
$(function() {
  $('#top-btn a').on('click',function(event){
    $('body, html').animate({
      scrollTop:0
    }, 1000);
    event.preventDefault();
  });
});

初期状態(ページの上部)では表示したくない場合?

$(function() {
    var topBtn = $('#top-btn a');
    topBtn.hide();
    $(window).scroll(function(){
         if ($(this).scrollTop() > 100) {
             topBtn.fadeIn(1000);
         } else {
             topBtn.fadeOut();
         }
    });
    topBtn.click(function () {
        $('body, html').animate({
      scrollTop:0
    }, 1000);
    event.preventDefault();
  });
});

fadeIn 及びfadeOut のif文にてボタンの表示を設定しています・・

終わり

上記記載内容で、実装できるはず・・・
不明点及び説明がずれている(誤っている等)ございましたら、コメントにてにてお伝えいただければありがたいです。

以上、ご参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript package比較サイトまとめ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ツール比較サイトまとめ

全体

stackshare
https://stackshare.io/

npm

openbase
https://openbase.io

npm trends
https://www.npmtrends.com/

NPMCompare
https://npmcompare.com/

npmcharts
https://npmcharts.com/

Awesome Node.js
https://nodejs.libhunt.com/

静的サイトジェネレーター

StaticGen
https://www.staticgen.com/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[PlayCanvas]他のEntityのScriptを取得する

概要

例えば、衝突処理などが発生した時に、そのぶつかった対象に影響を与えるなどをするために必要な処理。

実装例

Sample.js
var Sample = pc.createScript('sample');

Sample.prototype.test = function( param ){
    console.log( param );
};
Call.js
var Call = pc.createScript('call');

Call.prototype.initialize = function(){
    var entity = ~~~; // 何かしらの手段でEntityを取得したとする

    // 安全でないアクセス
    entity.script.sample.test('Unsafe Call.');

    // 安全なアクセス方法
    // 1. EntityにScriptコンポーネントがあるかを調べる
    // 2. Scriptコンポーネント内にScriptがあるか調べる
    if(entity.script && entity.script.has('sample')){
        entity.script.sample.test('Safe Call.');
    }
};

PlayCanvasはEntityにScriptというコンポーネントを追加して、そこにJavaScriptで書かれたScriptを追加していく方式なので注意が必要。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GASを使ってスプレッドシートをPDF化してメールに添付する。

やったこと
・特定のシートを複数PDF化けること。
・別途JSONファイルを保存して、そこから個人情報を取得すること
・複数のPDFをメールに添付すること。

経緯:
今まで直書きしてたけど、.envfile のように個人情報は分けたくなりました。

下記のようなJSONファイルを用意

{
  "user_info": {
    "mail_adress": [
      "hoge@gmail.com",
      "huge@gmail.com"
    ]
  }
}

コードがこちら

//個人データを保存したJSONファイル。共有用ファイルから確認可能。
const file_id = "1Jo6Dx6QPU_baFnCUzXlFiFyjRpyS71Nq";
//送信内容を保存したスプレッドシートのID
const sheet_id = "1x6olwI_XZALOzayxh0vXlxKXqtzhEwT_b92c0QUW7zY";

function sendMail(){

  const currentTime = new Date();
  //ファイルIDを元にスプレッドシートを開く
  // 実行ファイルを利用する場合は、SpreadsheetApp.getActiveSpreadsheet();, spreadsheet.getId();でも可。

  const spreadsheet = SpreadsheetApp.openById(sheet_id);

  const seetNameList = ["sample1", "sample2"];

  const token = ScriptApp.getOAuthToken();

  //PDFファイル配列を作成するためのmap処理
  const pdfFiles = seetNameList.map(function(seet){ 
    //seetNameListから取得したseet名を元にsheetを取得
    const sheetName = spreadsheet.getSheetByName(seet);
    const gid = sheetName.getSheetId();
   //PDF生成するURLをfetchする。特定のスプレッドシートの特定のシートだけPDF化するため
    const url = `https://docs.google.com/spreadsheets/d/${sheet_id}/export?gid=${gid}&format=pdf&portrait=false&size=A4&gridlines=false&fitw=true`;
    const pdf = UrlFetchApp.fetch(url, {headers: {'Authorization': 'Bearer ' +  token}}).getBlob().setName(`${currentTime}.pdf`);
    return pdf
  });

  // Jsonファイルを元にデータを取得
  var file = DriveApp.getFileById(file_id);
  // objectとして取得。 中身は空
  var blob = file.getBlob();
  var data = blob.getDataAsString();
  var jobj = JSON.parse(data);

  //jsonファイルに保存した連絡先の取得
 const contactLists = jobj.user_info.mail_adress;

   for (let i = 0; i < contactLists.length; i++) {
    var to = contactLists[i];
    var subject = "メールのタイトルを入力";
    var body ="メールの本文を入力"; 

    GmailApp.sendEmail(to,
                       subject,
                       body,
                       {attachments: pdfFiles})
  }
}

ファイル名に時間を利用していますが、表記に利用するならmoment ライブラリを利用するのがおすすめです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

非同期処理のタイムアウト時の処理をPromiseで書く方法の考察

背景

JavaScriptで非同期処理をコールバック関数としてセットしたとき、そのコールバック関数がいつまでたっても呼ばれないケースをケアするために、タイムアウト処理を書きたいことがある。

例:

  • HTTPのリクエストを送信したがレスポンスがいつまでたっても返ってこない
  • WebSocketで返信を期待して送信したが返信が返ってこない (ping を送ったが pong が返ってこない, etc.)

JavaScriptでは非同期処理をうまく扱うためにPromiseが言語組み込みでサポートされている。
したがって、以下のようなPromiseを作るのが言語の作法としても良さそうだし await キーワードとも相性がよいだろう。

  • 成功したら fulfilled になる
  • 失敗したら rejected になる(失敗の原因はタイムアウトに限らないが reason でわかる)

やりたいこと

今回考えるシナリオの概要は
「Node.js上で、処理を移譲するため子プロセスを起動する。子プロセスの準備ができるまで待ち、準備ができたら実際に通信する」
というものだ。
具体的には以下のとおり。

  1. Node.jsの親プロセス (Main) が、子プロセス (Sub) を起動する
  2. Main は、Sub から Ready の通知を受け取るまで待つ
  3. Sub は、準備が整ったら Main に Ready の通知を送る
  4. Main は、一定時間内に Sub から Ready の通知を受け取らなかったら、 Sub の起動に失敗したとみなす (タイムアウト)

シーケンス図

子プロセスのラッパーとなるクラスは以下のようなものを想定することにする。

interface ISomeService {
  start(): void;
  stop(): void;
  onReady(callback: () => void): void;
}

onReady にコールバックを仕掛けておいて start で子プロセスを起動する。
子プロセスの準備ができたら onReady に仕掛けておいたコールバックを発火させる。
このインターフェースだけでは startstop しかできずありがたみが無いが、実際は他にも様々な機能がメソッドとして公開されている情景を想像して欲しい。
今時RPC的な呼び出しは珍しいかもしれないが、ありうるシナリオではあると考えている。

トライ1: Promise.race を使う

「Promise タイムアウト」などで検索すると、 Promice.race を利用する例が多くヒットする。
このアイデアで書いてみる。

startService1.js
const startService = (timeoutSec) => {
  const service = new SomeService();
  const successPromise = new Promise((resolve, _) => {
    service.onReady(() => resolve(service));
    service.start();
  });
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(`Proc did not launch within ${timeoutSec} seconds.`), timeoutSec * 1000);
  });
  return Promise.race([successPromise, timeoutPromise]);
};

成功時の Promise とタイムアウト時の Promise を競わせるわけだ。これはよいアイデアだ。
この startService() を使う時は、以下のようにする。

startService(5)
.then((service) => {
  console.log('Proc launched successfully.');
  service.xxxxx();
}, () => {
  console.log('Attempt to launch proc was timed out.');
});

ダミーのサービスを作って動作確認してみた。
Pythonで数秒sleepしてファイルをtouchするだけのサービスと、ファイルが touch されたら onReady コールバックを実行する JavaScript のラッパークラスがこちら。
https://gist.github.com/kosuke-suzuki/95f951901e71c65e232d5993caa4853e

欠点

タイムアウトしたときにもサービスが起動したままになってしまう。

タイムアウトしたとき、返す Promise 自体は rejected の状態になっているので、呼び出している側で then の第二引数に指定した関数が実行される。
しかし、 startService 内で successPromise として定義した処理自体は止まらない。
サービスの起動がタイムアウトしたとき、サービスプロセスは存在するが Ready 状態ではないということになる。このような中途半端なプロセスは殺してしまいたい。タイムアウトとして扱うにも、これを殺したうえで then の第二引数の関数を実行したいのだ。

トライ2: Promise.race + タイムアウト時の処理

シーケンス図更新。 kill を追加した。
シーケンス図2

catch をチェーンしてその中で中途半端なプロセスを殺す処理を書く。Promise.race が rejected となったときだけこの処理が実行される。

startService2.js
const startService = (timeoutSec) => {
  const service = new SomeService();
  const successPromise = new Promise((resolve, _) => {
    service.onReady(() => resolve(service));
    service.start();
  });
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(`Proc did not launch within ${timeoutSec} seconds.`), timeoutSec * 1000);
  });
  return Promise.race([successPromise, timeoutPromise])
    .catch((reason) => {
      service.stop();
      return Promise.reject(reason); // これがないと呼び出し元に返す Promise が fulfilled になってしまう
    });
};

注意点としては、エラーを呼び出し元に伝播させるためには、明示的に rejected な Promise を返す必要があること。
これをやらないと、呼び出し元は fulfilled として扱ってしまう。
これで、動作としては期待通りとなる。

欠点

Promise.reject を明示的に呼び出さないといけないのはまどろっこしく感じる。
そもそも元の Promise コンストラクタに渡す executor の仮引数 resolve, reject も、それぞれ片方しか活かせていない。

トライ3: Promiseコンストラクタに渡す executor の中で行う

すべてひとつの Promise でやってしまおう、という案である。
タイムアウト時にサービスを止める処理は setTimeout の中で行う。
タイムアウト処理をセットする、というこのようなユースケースでは setTimeout という名前はピッタリに思えますね。

startService3.js
const startService = (timeoutSec) => {
  const service = new SomeService();
  return new Promise((resolve, reject) => {
    service.onReady(() => {
      clearTimeout(onTimedout); // 成功時は監視を止める必要あり
      resolve(service);
    });
    service.start();
    const onTimedout = setTimeout(() => {
      service.stop();
      reject(`Proc did not launch within ${timeoutSec} seconds.`);
    }, timeoutSec * 1000);
  });
};

これで Promise としてはひとつに集約される。
今度の注意点としては、サービスが正常に起動できた場合、タイムアウト監視を止めなければならない点である。これをしないと、サービスが起動して呼び出し元に返す Promise も fulfilled になっているにもかかわらず、タイムアウト監視によってサービスが止められてしまう。

欠点

タイムアウト監視を止めるコードを書き忘れると、成功時もサービスが止まる。
成功時に失敗時のケアをしないといけないのはイマイチな感じがする。

まとめ

function(timeoutSec) {
  const successPromise = new Promise((resolve, _) => {
    // 成功時の処理のなかで resolve()
    // 処理開始
  });
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(`Timeout: ${timeoutSec} seconds.`), timeoutSec * 1000);
  });
  return Promise.race([successPromise, timeoutPromise])
    .catch((reason) => {
      // Timeoutの後処理
      return Promise.reject(reason);
    });
}

もしくは

function(timeoutSec) => {
  return new Promise((resolve, reject) => {
    // 成功時の処理のなかで clearTimeout(onTimedout); resolve();
    // 処理開始
    const onTimedout = setTimeout(() => {
      // Timeoutの後処理
      reject(`Timeout: ${timeoutSec} seconds.`);
    }, timeoutSec * 1000);
  });
};

Web API では

XMLHttpRequest

XMLHttpRequesttimeout プロパティ、 ontimeout イベントハンドラを指定できる。
https://developer.mozilla.org/docs/Web/API/XMLHttpRequest

したがってこのような感じになるだろう(動作未確認)。

const request = (timeout) => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.timeout = timeout * 1000;
    xhr.ontimeout = () => reject(`Request was not responded within ${timeout} sec.`);
    xhr.onload = () => resolve(xhr.response);
    xhr.send();
  });
};

fetch

Fetch API は、組み込みで Promise を返してくれる使い勝手のよいAPIなのだが、なんとタイムアウトをサポートしていない!こんなことってありますか?
https://developer.mozilla.org/docs/Web/API/Fetch_API

見ると、 AbortSignal という実験的なAPIを使えばタイムアウトも書けるようだ。
https://developer.mozilla.org/docs/Web/API/AbortSignal
こんな感じだろうか(動作未確認)。

const fetchWithin = (url, timeoutSec) => {
  const controller = new AbortController();
  setTimeout(() => controller.abort(), timeoutSec * 1000);
  return fetch(url, { signal: controller.signal });
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

varとletとconstの違い

はじめに

自分が忘れないために備忘録的な物として残します。
ES6から変数宣言に利用可能なletとconstが追加されました。
10年選手のWEBアプリの開発をやっていたりすると、世の中から取り残される。
varとletとconstの違いを理解した上で、今後はletやconstを使う。

先にまとめ

初期化なしの宣言 再宣言 再代入 スコープ
var 可能 可能 可能 関数
let 可能 不可能 可能 ブロック
const 不可能 不可能 不可能 ブロック

var

初期化なしの宣言

可能

var x;

再宣言

可能

var x = 1;
var x = 2;

再代入

可能

var x = 1;
var x = 2;
console.log(var); // 2

スコープ

関数スコープ

var x = 0;
function fn(){
    var x = 1;
    console.log(x); //1
}
fn();
console.log(x); //0
var x = 0;
function fn(){
    console.log(x); //0
}
fn();
console.log(x); //0
function fn(){
    var x = 1;
    console.log(x); //0
}
fn();
console.log(x); //ReferenceError: x is not defined

let

初期化なしの宣言

可能

let x;

再宣言

不可能

let x = 1;
let x = 2; // SyntaxError: Identifier 'x' has already been declared

再代入

可能

let x = 1;
x = 2;
console.log(x); // 2

スコープ

ブロックスコープ

{
    let x = 1;
    if(1){
        x = 2;
        console.log(x); // 2
    }
    console.log(x); // 2
}
{
    if(1){
        let x = 1;
        console.log(x);
    }
    console.log(x);// ReferenceError: x is not defined
}
{
    let x = 1;
    {
        // この場合はスコープがブロックのため、同じ変数の宣言と判断されない
        let x = 2;
        console.log(x); // 2
    }
    console.log(x); // 1 
}
{
    for(let x=0; x<1; x++){
        console.log(x); //0
    }
    console.log(x);// ReferenceError: x is not defined
}

const

初期化なしの宣言

不可能

const x; // SyntaxError: Missing initializer in const declaration

再宣言

不可能

const x = 1;
const x = 2; // SyntaxError: Identifier 'x' has already been declared

再代入

不可能

const x = 1;
x = 2; // TypeError: Assignment to constant variable.
console.log(x); // 2

スコープ

ブロックスコープ

    const x = 1;
    {
        // この場合はスコープがブロックのため、同じ変数の宣言と判断されない
        const x = 2;
        console.log(x); // 2
    }
    console.log(x); // 1

変数の巻き上げ

JavaScriptでは、関数内で宣言されたローカル変数は、すべてその関数の先頭で宣言されたものとみなされる。

{
    let x = 1;
    {
        console.log(x); // ReferenceError: Cannot access 'x' before 
        // この場合はスコープがブロックのためか、同じ変数の宣言と判断されない
        let x = 2;
        console.log(x); // 2
    }
    console.log(x); // 1
}

参考: 知らないと怖い「変数の巻き上げ」とは?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのドット記法とブラケット記法について

プログラミング勉強日記

2020年9月24日
「ブラケット記法」という単語を初めて聞いたので、ブラケット記法について調べていくうちに似たようなドット記法を知ったので両者を比較してみる。

ドット記法とブラケット記法

 どちらもオブジェクトのプロパティにアクセスする方法である。

 ドット記法はその名の通り.(ドット)を使ってプロパティにアクセスする。[]はブラケットと呼ばれるので、ブラケット記法もその名の通り[](ブラケット)を使ってプロパティにアクセスする方法である。

違い

 ブラケット記法はプロパティ名に変数を使うことができるが、ドット記法は変数を使うことができない。また、ブラケット記法はプロパティ名が数字で始まるような不正な文字でもアクセスできる。(ドット記法の場合はエラーになってしまう)

まとめ

 ここまで聞くとじゃあブラケット記法で書けばいいのでは?と思うが、基本はドット記法の方が読みやすいのでドット記法で書く
 違いのところでも述べているように、プロパティ名に変数を使う場合や不正なプロパティ名にアクセスしたい場合にはブラケット記法を使う。

参考文献

「JavaScript」ドット記法とブラケット記法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftで取得した画像をWKWebView内のHTMLで表示する方法

WkWebviewのコンテンツ内でSwiftで取得したPNG画像などのデータを使いたいケースがあると思います。

本記事ではWebViewJavascriptBridgeを使用した、PNG画像のSwiftとJavaScript間での通信にて表示する方法について記載します。

ローカルのHTMLをWebViewで読み込み(Swift)

まずはWKWebviewでローカルのhtmlを読み込む。

Swift
class ViewController: UIViewController {
    var webView:WKWebView?

    override func viewDidLoad() {
        super.viewDidLoad()
        // WKWebViewのViewへの追加
        self.webView = WKWebView(frame: view.frame)
        view.addSubview(self.webView!)
        let htmlPath = Bundle.main.path(forResource: "content", ofType: "html")
        let baseURL = URL.init(fileURLWithPath: htmlPath!)
        webView!.loadFileURL(baseURL, allowingReadAccessTo: baseURL)
    }
}

JavaScriptへのbridge(Swift)

WebViewJavascriptBridgeの初期化と、
registerHandlerにてJavaScript→Swiftへアクセスする受け口作成。

Swift
        self.bridge = WebViewJavascriptBridge.init(forWebView: webView)
        // WKWebView内のjavascriptからSwift内のデータを取得
        self.bridge!.registerHandler("image") { (data, callback) in
        }

JavaScript側のbridge設定(JavaScript)

読み込むhtml内のJavaScriptにてWebViewJavascriptBridgeでSwift間通信するためのテンプレートを追加後、
bridge.callHandlerにてSwift側への画像取得要求を行う。
※詳細はWebViewJavascriptBridge参照

JavaScript
        function setupWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
            if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
            window.WVJBCallbacks = [callback];
            var WVJBIframe = document.createElement('iframe');
            WVJBIframe.style.display = 'none';
            WVJBIframe.src = 'https://__bridge_loaded__';
            document.documentElement.appendChild(WVJBIframe);
            setTimeout(function () { document.documentElement.removeChild(WVJBIframe) }, 0)
        }

        setupWebViewJavascriptBridge(function (bridge) {
            bridge.callHandler('image', { 'key': 'value' }, function responseCallback(responseData) {
            })
        })

Base64データへの変換(Swift)

WKWebview側でPNGを読み込むためbase64のstringへ変換してJavaScript側へ返却。

Swift
        self.bridge!.registerHandler("image") { (data, callback) in
            if let imagePath = Bundle.main.path(forResource: "PNG", ofType: "png") {
                // PNGをUIImageに
                let image = UIImage(contentsOfFile: imagePath)
                // Data型へ変換
                let data = image?.pngData()
                // PNGのデータをbase64エンコードしてWebViewで表示できるよう修正
                let base64Image = data?.base64EncodedString(options: .endLineWithLineFeed)
                callback!(base64Image)
            }
        }

base64形式のPNGデータの読み込み

Swift側から返却されたbase64形式のresponseDataは、
PNGのデータURIとなっていないため、先頭に
data:image/png;base64,
を追加してimgタグで読み込み。

JavaScript
            bridge.callHandler('image', { 'key': 'value' }, function responseCallback(responseData) {
                var html = [
                ];
                html += '<img src="data:image/png;base64,'
                html += responseData
                html += '" alt="PNG"/>'
                document.getElementById("content").innerHTML = html
            })

上記にてWKWebview上でSwiftで取得したPNGが表示される。

サンプルアプリ

GitHubにて上記ソースのサンプルアプリ公開しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

2020年で最も需要のあるプログラミング言語

本記事はMost in-demand programming languages in 2020の日本語訳です。翻訳元に報告していますが、もし苦情が来たら消します。
翻訳は不慣れなので変なところもあると思いますが、ご容赦ください。


Most-in-demand-programming-languages-in-2020.jpeg

ソフトウェア開発業界は絶えず変化しており、それは開発者の能力に対する企業のニーズも変化していることを意味します。そのため、あなたが想像できるように、Webアプリケーション、ゲーム、アルゴリズムなどのあらゆる側面の開発をカバーするために、選択できるプログラミング言語はたくさんあります。その上で、私たちは2020年で最も需要のあるプログラミング言語とその主な特徴について触れます。

  1. JavaScript (回答者の71%がこのスキルに関する求職者を探している)
  2. Java (57%)
  3. C# (53%)
  4. Python (51%)
  5. PHP (40%)
  6. Ruby (15%)

Screenshot-2020-05-03-at-22.31.53.png
2020年で最も需要のあるプログラミング言語

JavaScript

JavaScriptが2020年で最も需要のあるプログラミング言語リストのトップであることは全く不思議ではありません。

今日では何らかの方法でJavaScriptを使用することなしに開発者になることは不可能です。調査の回答者の71%以上がJavaScriptでコードを書ける開発者を探しており、JavaScriptが最も人気のあるプログラミング言語のうちの一つであることが想像できます。また、JavaScriptはWebにおける偏在性と私たちのインターネットへの重い依存のため、非常に人気があります。Twitter、Facebook、YouTubeなどの最も人気のあるサイトの多くは、JavaScriptを使ってインタラクティブなWebページを生成したり、コンテンツを動的にユーザに表示したりしています。

JavaScriptはコア言語があり、追加の開発ツールによって柔軟性が保たれています。JavaScriptは寛大で柔軟な構文を持ち、全てのメジャーなブラウザで動作するため、初学者にとって最も簡単なプログラミング言語の一つです。今日、JavaScriptは世界中で広く使用されているプログラミング言語であり、あらゆるところで動作します:コンテナ、マイクロコントローラ、モバイル端末、クラウド、ブラウザ、サーバなど。

主な特徴

  • JavaScriptはここ数年で大規模な現代化と徹底的な点検を経てきました。ES5、ES6といったJavaScriptのメジャーなリリースはいくつかのモダンな機能が追加され、今日のJavaScriptは過去10年間のJavaScriptとは完全に別物です
  • Node.jsのおかげでJavaScriptはイベント駆動なプログラミングを提供し、特にI/Oの複雑なタスクに適しています。今日では、Node.jsとJavaScriptは、サーバとモバイル端末を含めてほとんど全てのプラットフォームで動作します
  • JavaScriptはブラウザプログラミングにおいて、議論の余地のない王様です。今日、Web開発は主にVue.js、Angular、ReactといったJavaScriptベースのSPAフレームワークによって支配されています

Java

Javaは2020年で最も需要のあるプログラミング言語リストで2位の座を手にしています。

Javaは、ビジネスでは最も人気のモバイルコンピューティングプラットフォームであるAndroidのネイティブ言語であることから人気のあるプログラミング言語です。

Javaは過去数年の間に、非常にユーザに優しいモダンな言語にビジネスの一部を奪われました。

Javaは欠陥の改善に取り組んでおり、GraalVMアクションを介してクラウドにフィットさせる努力をしています。Javaはまだエンタープライズではナンバーワンのプログラミング言語です。Javaは誕生してからずっとトップクラスの需要があるプログラミング言語です。大企業の多数がバックエンドWebシステムやデスクトップアプリケーションのためにJavaを使っているため、もし開発者がJavaを知っていれば、その開発者は継続的に需要が高い状態になるでしょう。Javaは静的型付けの言語で、そのためバグが少なく、メンテナンスを速く行えて管理しやすいです。

主な特徴

  • Javaはマルチパラダイムを提供し、強力で、多機能で、柔軟な学習曲線と高い生産性を持つインタプリタ型のプログラミング言語です
  • Javaは厳格な後方互換性を持ち、これはビジネスアプリケーションにとって重要な要件です。JavaではScalaやPythonのようなメジャーな破壊的変更が取り入れられたことはありません。その結果、Javaはいまだにビジネスにとってナンバーワンの選択肢です
  • JavaのランタイムであるJVMはソフトウェア工学の結晶で、ビジネスにおいて最高の仮想マシンの一つです。何年もの技術革新とエンジニアリングの職人技により、JVMは素晴らしい機能と性能をJavaに提供します。JVMはいくつかの優秀なガベージコレクションもJavaに提供しています

C#

C言語は、移植性と、AppleやMicrosoftのような巨大なIT企業から早期に採用されたことのおかげで最も古くて最も人気のあるプログラミング言語のうちの一つとなった言語です。1
C-sharpとしても知られるC#は、2000年にMicrosoftによって開発された言語のスピンオフです。C#はオブジェクト指向言語で、アクションの代わりにオブジェクトを中心に、ロジックの代わりにデータを中心に構築されます。C#の特徴はJavaと似ており、Windowsのデスクトップアプリケーションとゲームを開発するのに特に有効です。

しかし、C#はWebアプリケーションとモバイルアプリケーションを開発するのにも使えます。C#はC++のようなC派生の言語に似た構文を使っており、あなたがCファミリーの中の別の言語から来たのであれば簡単に習得できます。C#は銀行のトランザクション処理のような大企業のアプリケーションの開発にしばしば使われます。C#は人気のUnityゲームエンジンを使った2Dや3Dのビデオゲームを作るために推奨される言語です。今日では、C#はWindowsプラットフォームにおいてだけでなくLinuxプラットフォームやiOS/Androidプラットフォームでも幅広く使用される、マルチパラダイムな言語です。

主な特徴

  • C#はプラットフォーム非依存でもあり、Linux、Windows、モバイル端末で動作します
  • Microsoftの後ろ盾があり、長年に渡り業界にいるC#はライブラリとフレームワークの大きなエコシステムがあります。ASP.NETはWebアプリケーション開発に使われます(主にWindows上での)
  • 開発者体験という面では、C#はJavaよりはるかに優れています

The-top-5-languages-tested-on-DevSkiller.png
DevSkillerでテストされた言語トップ5

Python

Pythonはおそらくこのリストの中で最もユーザに優しいプログラミング言語です。Pythonの構文は明確で、直感的で、ほとんど英語だとよく言われ、初学者にとって本当に良い選択肢です。Pythonは高レベルで、汎用性が高く、Webアプリケーションやデータ解析、アルゴリズムの開発などに使われます。Pythonは、科学計算やエンジニアリング、数学といったフィールドで頻繁に使われるSciPyやNumPyのようなパッケージも持っています。

Pythonはスクレイピングにしばしば使われ、PHPでコーディングするのに何時間もかかるものが、Pythonだと数分しかかかりません。Pythonは、あなたの時間を消費する日々のタスクを含む特定の作業を自動化するためにも使うことができます。もしバックエンドのWeb開発の例に興味があれば、オープンソースの(Pythonで書かれた)Djangoフレームワークは人気で、学ぶのが簡単で、多機能です。そしてJavaのように、Pythonには多様なアプリケーションがあり、あなたのユースケースのために最も良いプログラミング言語を選択する時に、多様で強力な選択肢となります。今日、Pythonは広く行き渡り、ソフトウェア開発の多くの分野で使用され、そしてその勢いが衰えるようには見えません。

主な特徴

  • Pythonには非常に活発なコミュニティとサポートがあります。たとえあなたがデータサイエンス、業務アプリケーション、AIのどれで働いているのだとしても、常に十分なPythonの組織2やフレームワークが見つかります
  • Pythonには第一級のC++/Cとの統合機能があり、CPU負荷の高いタスクをシームレスにC++/Cにオフロードすることができます。Pythonは、SciPy、Pandas、NumPyなど、統計、Scikit-Learn3、数学、および計算科学のための素晴らしいツールセットも提供します。 結論として、Pythonは機械学習/ディープラーニング/データサイエンスの状況やその他の科学的な領域を支配しています
  • Pythonのウリはその言語設計にあります。それは生産性が高く、エレガントで、シンプルで、その上強力です。Pythonは開発者経験という面で黄金律を設定し、Julia、Goといったモダンな言語に対して多大な影響を与えました

PHP

たとえ多くの論争があるとしても、PHPは2020年で最も需要のあるプログラミング言語リストに入っています。

PHPは幅広く使用されているオープンソースの汎用スクリプト言語で、典型的にはWebアプリケーション開発に適しています。たとえ以前ほどではないとしても、PHPは依然として世界中で最も用いられているプログラミング言語のうちの一つです。PHPはFacebookやYahoo!といった多数の大きな会社によって使用されています。PHPは汎用的で、動的な、基本的にはサーバサイドのWebアプリケーションの開発のために使用されているプログラミング言語です。

PHPはJavaScriptのような新しいWeb言語が実現するまでずっと、ほとんど全てのモダンなWebサイトを構築可能にしました。いくつかの調査によると、PHPがWebの3分の1を支えているとのことです。たとえPHPが以前ほどは注目されていないとしても、PHPは今後何年にもわたって進化を継続し、最も人気のあるプログラミング言語のうちの一つとしての地位を維持するでしょう。

主な特徴

  • 多くの大きな会社がPHPを使用しており、そのための素晴らしいツールのサポートに繋がっています
  • PHPはWebアプリケーション開発に過去25年4に渡って使用されており、強力で安定したPHPフレームワークが数多く市場に存在します
  • PHPは非常に生産性が高いサーバサイドWeb開発プログラミング言語のうちの一つです。結果として、Webアプリケーションを素早く開発するために、IT業界で広く使われています。最も有名なSNSの一つであるFacebookはPHPで開発されました

USA-PHP-versions-popularity-research.png
アメリカにおけるPHPバージョンの人気調査

Ruby

特に、Rubyは人気のあるRuby on Rails Webアプリケーションフレームワークのための基盤として使われます。RubyはC言語で実装され、ガベージコレクタがあります。Rubyは90年代半ばに作られましたが、ここ10年ほどの間に人気を獲得しました。Rubyは非常に動的で、オブジェクト指向言語で、プログラマーが使うための様々な機能を持っています。Rubyの経験が6年以上ある開発者は、現在の採用状況では2倍の面接依頼を受けることが期待できます。

Twitter、Shopify、そして多くのスタートアップがいずれかの段階でRuby on Railsを使ってWebサイトを構築しています。Rubyはまた、素晴らしいハイテク企業との関連性のために選び出すには本当に良い言語です。5
Pythonのように、Rubyは開発者の生産性と幸福を非常に重視しています。Rubyは新しい開発者にとって学習曲線がフラットになる非常に優れた言語でもあります。

主な特徴

  • RubyはTwitter、GitHub、Airbnbのような最大級のソフトウェアプロジェクトで使われ、そして素晴らしいツールとフレームワークの支援があります
  • Rubyそれ自体は破壊的ではありませんが、RubyのWeb開発フレームワークであるRuby on Railsはおそらく最も破壊的で、影響力のあるサーバサイドWeb開発フレームワークです
  • Rubyはプログラミング言語の最高の機能のうちのいくつかをうまく利用してきました: 簡潔さ、動的、ガベージコレクタのあるオブジェクト指向、そして関数型です

  1. [訳注] 一応、書き間違えではありません。なぜC言語の話が出てきたのか謎です。 

  2. [訳注]よくわからなかったのですが、コミュニティとかそういう意味でしょうか… 

  3. [訳注]機械学習のライブラリです。具体的なライブラリが数学、統計、計算科学と並ぶのは非常に違和感があるので、何か誤訳があるかもしれません。 

  4. [訳注]原文では35年となっていますが、PHPは1995年に公開された言語であるため、おそらく誤りです。 

  5. [訳注] よくわからないですが、Rubyを習得していればハイテク企業に入社しやすいとかそういう意味ですかね… 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Js:tokenを取得する為に苦労した事象

今回は、token取得が出来ない事象が発生しその原因と対処を忘れない様に覚書していきます。

事象内容
Payjpでのクレジット決済機能(テスト)を実装中にtokenが取得が出来なかった。
カード決済機能js
jsカード.png

そこでturbolinksをコメントアウトしてみました。
turbolinksは、たまに発火に悪影響を及ぼす事があるみたです。

turbolinks.png

しかし、tokenが取得出来ない事と、その前に実装していた商品出品機能の販売価格のjsが発火しない様になってしまいました。
※販売価格jsはturbolinksを有効にすると発火することは確認出来ました。

そこで、販売価格のjsコードを見直して見た
当初のjsコード
js当初.png

変更後のコード
js変更後.png

コード変更後の動作確認で、turbolinksコメントアウト後の販売価格js、クレジットカード決済のtokenの取得も出来ました。

今回学んだこと
今の動作確認が良くても、後々エラーが発生する事がある。
コードを見直す際は、今記述しているところだけではなく、前後のコードの流れを考え記述する事

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Gatsby.js】単一のjsonファイルを元に複数のページを生成する

Gatsby.jsを使って、jsonファイルから複数のページを生成する方法をまとめます。
Gatsby、情報自体はそこそこ豊富なんですが、日本語の情報が少ないですね。。

記事データをまとめたjsonファイルを作成

$ mkdir src/data
$ touch src/data/articles.json
src/data/articles.json
[
  {
    "slug": "how-to-use-gatsby",
    "title": "【Gatsby.js】「ここだけ押さえれば普通に使える」って知識をまとめてみた",
    "content": "Gatsby.jsでシンプルな静的サイトを作る際に必要な知識だけをまとめました。CMSとの連携等については今回は扱いません。本文テキスト本文テキスト本文テキスト本文テキスト本文テキスト本文テキスト",
    "tags": ["React", "Gatsby"]
  },
  {
    "slug": "learn-react",
    "title": "Reactの学習、今からやるならこうする",
    "content": "Reactの学習を初めて約2週間が経ちました。Railsの時と違って「とりあえずこれをやっておけば良い」ってものが見当たらずなかなか大変でしたが、試行錯誤の上色々と見えてきたのでまとめていきます。",
    "tags": ["React"]
  },
  {
    "slug": "restore-mac",
    "title": "Macの初期化→リストアを良い感じにする",
    "content": "Brewfile、Mackup、Docker、Dropboxを使って良い感じにリストアできる環境を作ったので、まとめます。以下の記事をめちゃくちゃ参考にしました。macOSでの開発環境を全部Docker化したらリストア時間が1時間半になった",
    "tags": ["Docker", "Brewfile", "Mackup"]
  }
]

記事のテンプレートページのベースを作成

$ mkdir src/templates
$ touch src/templates/article.js
src/templates/article.js
import React from "react"

export default () => (
  <>
    <h1>タイトル</h1>
    <p>本文</p>
    <span>タグ1</span>
    <span>タグ2</span>
  </>
)

記事を生成する処理を記述

$ touch gatsby-node.js
gatsby-node.js
const path = require("path")
const data = require("./src/data/articles.json")

exports.createPages = ({ actions }) => {
  const { createPage } = actions
  const template = path.resolve("./src/templates/article.js")
  data.forEach(article_object => {
    const path = `articles/${article_object.slug}`
    createPage({
      path, // 生成されるページのpath
      component: template, // ページのベースとなるテンプレート 
      context: article_object, // GraphQL経由で受け渡すデータのオブジェクト
    })
  })
}

GraphiQLを開き、データを取得するためのクエリを作成

gatsby developで開発サーバーを立ち上げ、ブラウザで http://localhost:8000/ にアクセスしてGraphiQLを開く。
allSitePage > edges > node > contextと順に開いていくと、titlecontenttagsが見つかるのでチェックを入れて実行し、欲しいデータが取得できることを確認。

GraphQLで取得したデータをテンプレートページに埋め込む

作成したクエリで必要なデータを取得し、テンプレートに埋め込みます。
GraphQLで取得したデータはdataというオブジェクトに格納されるので、そこから値を取り出して出力します。

src/templates/article.js
import { graphql } from "gatsby"
import React from "react"

export default ({ data }) => {
  const article = data.allSitePage.edges[0].node.context
  return (
    <>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
      {article.tags.map(tag => (
        <span>{tag}</span>
      ))}
    </>
  )
}

export const query = graphql`
  query {
    allSitePage {
      edges {
        node {
          context {
            content
            tags
            title
          }
        }
      }
    }
  }
`

開発サーバーを再起動して確認

開発サーバーを再起動し、存在しないURLを入力して404ページを表示すると、ページ一覧に今作ったページが追加されていることが確認できます。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

組み合わせ爆発ハラスメントの処方箋

プログラミング初学者向けの内容です。
今のところ Golang, Ruby, Python, JavaScript, TypeScript による処方箋のみ掲載しています。

ある日のこと

知人「店長からさぁ、

   『うちはメニューの数が少ないから、
    コンビ・メニュー作ることにした』

   『とりあえず、
    今あるメニューを組み合わせて、
    単品から全部入りまで
    すべての組み合わせのリスト作ってくれ!』

   って、言われたんだけど…」

俺「え? それって、
  ???があるとしたら、
  ↓みたいなやつ?」

1:?
2:?
3:? ?
4:?
5:? ?
6:? ?
7:? ? ?

知人「そう。そう。それ!それ!」

俺「作れるけど、、
  きっとものすごい数になるよ。
  単品メニューって何種類くらいあんの?」

知人「20種類くらいかなぁ。。
   物好きな店長でしょ?!
   めんどくせぇ。。」

俺「…」

俺「あのさぁ、、
  面倒くさいとかの次元じゃないんだけど。。」

俺「0.1 mm 厚の紙を 26 回折ったら
  富士山より高くなるって知ってる?」

\begin{align}
0.1mm\times2^{26} &= 6,710,886.4 mm\\
&\fallingdotseq 6.7 km
\end{align}

知人「あ、なんか聞いたことあるかも。。」

俺「それと同じなんだけど、、」

\begin{align}
2^{20} - 1 &= 1,048,575 通り\\
&\fallingdotseq 105万通り
\end{align}

俺「105万通りは、
  さすがにメニュー充実しすぎだろ(笑)」

知人「へ〜、そんなになるんだぁ!」

俺「…」

Golang による処方箋

取り急ぎ Golang で書いてみます。

menu.go
package main

import (
        "flag"
        "fmt"
        "strings"
)

func comball(in []string) [][]string {
        n := 1 << len(in)
        out := make([][]string, n)
        for i := 0; i < n; i++ {
                ss := make([]string, 0, len(in))
                for j := 0; j < len(in); j++ {
                        if 1<<j&i != 0 {
                                ss = append(ss, in[j])
                        }
                }
                out[i] = ss
        }
        return out
}

func main() {
        flag.Parse()
        args := flag.Args()
        for i, ss := range comball(args) {
                fmt.Printf("%d:%s\n", i, strings.Join(ss, " "))
        }
}

Go のバージョンです。

version
$ go version
go version go1.15.2 linux/amd64

実行してみます。

実行
$ go run menu.go ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ? > menu.txt

Golang はコンパイルも実行も速くていいですね。
Generics をサポートしていないので string 型専用の関数になってしまい、そこが残念ポイントですが、LL のような感覚で気軽にいろいろ試せます。
(Generics は来年サポートされるようですね)

プログラムは標準出力へ書き出すようにしましたが、そのまま出力するとたぶん大変なことになるので menu.txt という名前のファイルへリダイレクトしました。

ファイルの先頭を見てみます。

ファイルの先頭
$ head menu.txt 
0:
1:?
2:?
3:? ?
4:?
5:? ?
6:? ?
7:? ? ?
8:?
9:? ?

最初の行に「なし」を出すようにしてます。

今度は末尾を見てみます。

ファイルの末尾
$ tail menu.txt 
1048566:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048567:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048568:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048569:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048570:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048571:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048572:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048573:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048574:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048575:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?

1行目の「なし」を除いて、ちゃんと 104 万 8,575 行あります。
ファイルサイズが 56.4 MiB もありますが、、(笑)
(圧縮して 4 Mib くらい)
ひとまず、これで大丈夫そうです。

あと、 Golang はサクッとクロスコンパイルしてシングルバイナリが作れるのがいいですね!
とりあえず、AMD64 互換 の linux と Mac と Windows 用を用意して持ち帰ってもらうことにました。

build.sh
GOOS=linux   GOARCH=amd64 go build -o ./linux-amd64/menu       menu.go
GOOS=darwin  GOARCH=amd64 go build -o ./darwin-amd64/menu      menu.go
GOOS=windows GOARCH=amd64 go build -o ./windows-amd64/menu.exe menu.go

でも、、

せっかくプログラムを書いてあげたのに、結局、彼は「店長に怒られそう…」という理由で、これを使ってくれませんでした。

遠い昔を思い出す

知人は採用してくれませんでしたが、これってテストデータの生成(フラグの組み合わせとか)にも応用できますよ。

昔、入社 1 年目のとき、まるで野球部の球拾いのごとくテスターをやらされた日々を思い出します。

ある日、明確なテスト仕様書もない中で、先輩 SE から無茶振りされました。

「可能な組み合わせを全部テストするなんて当たり前なの!お前バカなの!?」

と怒鳴られました。

遠い昔のことなので、細かいことはあまり良く覚えてませんが、同期の仲間と計算してみると、1 個 1 分でやったとしても、寝ずにやって何十年かかるとか、そんな途方もないオーダーでした。

現実的な解が思い浮かばなかったので、もっと上の先輩に相談しました。
すると、即答で「バカは相手にしなくていいから(笑)!」と言ってくれ、あっさりとこの問題は解決してしまいました。

今考えると完全にパワハラでした。
2〜3日、真剣に悩みましたから(笑)

無茶ぶりした先輩 SE はその後しばらくして会社を辞めていきました。

でも、マシンが高速化し自動テストがあたりまえになった現代では、当時できなかったいろんなことができるようになりました。

あのとき、もし今の環境が手元にあったら、この程度の簡単な処方箋であっさりと解決していたのかも。。
そう考えると感慨深いものがあります。

先輩 SE が後輩を馬鹿呼ばわりすることもなく、彼がさらに上の先輩から馬鹿呼ばわりされることもなかったかもしれません。

ということで、他の言語の例もいくつか載せておきます。

Ruby で調合する

Ruby はあまり書いたことがないので、らしいコードじゃないかもしれません。

プログラムをみる
menu.rb
def comb(arr)
  out = []
  n = 1 << arr.size
  n.times do |i|
    a = []
    arr.size.times do |j|
      if 1 << j & i != 0 
        a << arr[j]
      end
    end
    out << a
  end
  out
end

comb(ARGV).each.with_index(0) do |a, i|
  puts i.to_s + ":" + a.join(" ")
end
実行
$ ruby --version
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux]
$ ruby menu.rb ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ? > menu.txt
$ tail menu.txt 
1048566:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048567:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048568:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048569:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048570:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048571:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048572:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048573:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048574:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048575:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?

まあ、Ruby の場合は組み込み関数を使えば、↓これでもいけますね。
出力順が違いますけど。。

menu2.rb
def comball(arr)
  out = [[]]
  arr.each_with_index { |s, i| out += arr.combination(i+1).to_a }
  out
end

comball(ARGV).each.with_index(0) do |a, i|
  puts i.to_s + ":" + a.join(" ")
end

Ruby って、書く順番がなんか他の言語と違いますよね。
この感覚が気持ちよくて好きです。。

Python で調合する

プログラムをみる
menu.py
import sys

def comball(arr):
    out = []
    n = 1 << len(arr)
    for i in range(n):
        a = []
        for j in range(len(arr)):
            if 1 << j & i != 0:
                a.append(arr[j])
        out.append(a)
    return out

arr = sys.argv
arr.pop(0)
for i, a in enumerate(comball(arr)):
    s = ' '.join(a)
    print('{0}:{1}'.format(i, s))
実行
$ python3 --version
Python 3.6.8
$ python3 menu.py ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ? > menu.txt
$ tail menu.txt 
1048566:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048567:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048568:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048569:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048570:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048571:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048572:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048573:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048574:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048575:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?

end や } が必要ない分、関数本体が短く書けますね。

JavaScript で調合する

プログラムをみる
menu.js
function comball(arr) {
    const out = []
    const n = 1 << arr.length
    for (let i = 0; i < n; i++) {
        const a = []
        for (let j = 0; j < arr.length; j++) {
            if ((1 << j & i) != 0) {
                a.push(arr[j])
            }
        }
        out.push(a)
    }
    return out
}

const arr = process.argv.slice(2)
comball(arr).forEach((a, i) => 
    console.log(i + ":" + a.join(" "))
)
実行
$ node --version
v12.16.1
$ node menu.js ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ? > menu.txt
$ tail menu.txt 
1048566:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048567:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048568:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048569:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048570:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048571:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048572:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048573:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048574:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048575:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?

JavaScript って & より != の方が演算子の優先順位が高いんですよね。
だから括弧が付いてます。
なんか理由があるんですかね。。

TypeScript で調合する

プログラムをみる
menu.ts
function comball<T>(arr: T[]): T[][] {
    const out: T[][] = []
    const n = 1 << arr.length
    for (let i = 0; i < n; i++) {
        const a: T[] = [] 
        for (let j = 0; j < arr.length; j++) {
            if ((1 << j & i) != 0) {
                a.push(arr[j])
            }
        }
        out.push(a)
    }
    return out
}

const arr: string[] = process.argv.slice(2)
comball(arr).forEach((a, i) =>
    console.log(i + ":" + a.join(" "))
)
実行
$ npx ts-node --version
v8.10.1
$ npx ts-node menu.ts ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ? > menu.txt
$ tail menu.txt 
1048566:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048567:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048568:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048569:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048570:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048571:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048572:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048573:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048574:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?
1048575:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ☕ ?

TypeScript は関数シグネチャを見れば何をしそうか分かるところが良いですね。
あと、Generics 使っています。
でも、トランスパイルが遅いところが玉に瑕です。

あとがき

本稿で扱ったプログラミング言語は演算子の使い方もほとんど同じなので、みんな似たコードになりましたが、それでも言語の個性が出ている部分もあって面白かったです。

後で他の言語も追記するかもしれません。
要望があればプログラムの解説も付けるかもしれません。

あと、各言語のエキスパートの方で、もっとカッコいい書き方を知ってるよ! という人は是非教えてください。

それでは!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

セル内改行に対応したcsvの読み込み

前回のあらすじ

何も知らないところからのhtml、CSS

プログラム自体は出来ているが記事にするまでに相当時間が空いた。
前回はレイアウトを作ることができた。
今回はリストをつくるために元のデータを読み込む。

csvを読み込むために。

ローカルファイルのcsvを読み込みたい。
しかし、調べてみるとボタン操作をせずにファイルを読み込むためにはgoogleChromeではできないようだ。
さらに調べるとNode.jsを使えば操作できるようだ。ついでにデスクトップアプリ作成のためにElectronを導入することになる。

今後は
- Node.js
- Electron
が導入されている環境を想定して記事を書いていく。

最終的な表示の都合上、読み込むcsv元データはセル内改行を行っているものを想定する。
Excel→csvに変換したものである。

csvファイルの読み込みに関しモジュールを導入した。
npm で導入できるので詳細は検索して欲しい。

  • モジュール fs :ファイル読み込みのためのモジュール
  • モジュール iconv-lite :文字コード変換のためのモジュール

というわけでカキカキした。

//csvセル内での改行コードに対応(行の最後のセルはセル内改行されていないこと)
var database =[[]];   //データを格納するための変数
const fs = require('fs');
const iconv = require('iconv-lite');
var filename = __dirname + '\\database.csv'   //ファイルパスを記載
const content = fs.readFileSync(filename);  //ファイルの読み込み
var buf = new Buffer.from(content, 'binary'); //バイナリデータにする
var buf2 = iconv.decode(buf,"Shift_JIS");   //文字コード変換
var database_tmp = buf2.split(',');   //,で区切り列ごとに配列に格納
var line_no =0;
var data_no =0;
var line_tmp;
for(var i=0;i<database_tmp.length;i++){
      //行末尾の改行コードを判定し該当すれば行とする
      if(database_tmp[i].indexOf('"')==-1 && database_tmp[i].indexOf('\n')>=0){
         line_tmp = database_tmp[i].split('\n');   //末尾の改行セルを分ける
         database[line_no][data_no]=line_tmp[0];   ///分けたセルを挿入
         line_no = line_no + 1;    //配列の中で計算できないため外で変数計算
         database[line_no]=[];   //行要素に列用の配列を挿入する
         databse[line_no][0]=line_tmp[1];   //行の頭として分割した片割れを挿入
         data_no =1;
      }else{
         database[line_no][data_no] = database_tmp[i].replace('"','');
         data_no = data_no + 1;  
      }
   }
}

これで自分の想定しているデータはセル内改行に対応してした。
が、コメントに書いたようにすべての形には対応していないのでコピペする人は当然だがテストして下さい。
※行の最後のセルはセル内改行されていないこと

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Svelteで「XXX has unused export property 'XXX'...」と出たときの対処方法

はじめに

とあるSvelteライブラリのドキュメントサイトをローカル環境で動かしてたところ(Contribute目的で)ログ上で
XXX has unused export property 'XXX'. If it is for external reference only, please consider using export const answerという警告ログが出ていた。
その時の対処方法を記事にする。

再現

unused export propertyと記載されている通り、プロパティ渡し先のコンポーネントで宣言はしてるけど、使用していないときに起こるようです。

index.svelte

<script>
  import Nested from './Nested.svelte';
</script>

<Nested count={10}/>

Nested.svelte

<script>
    export let count;
</script>

そうすると、、、

Warning

警告ログを確認することができました。

対応

今回の場合まったく使用してないプロパティなので、渡してる部分と宣言してる部分を削除することで警告ログは消える。
もちろん警告ログなので、放置していてもおそらく動作上に問題ないかと思います。(すごく気にはなるかと思いますが)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] 初心者は必見でしょ。ルーティングのエラー解決と解説

どうもこんにちわ

今回は、Reactのルーティングを設定する上でよく目にする

Error: Invariant failed: You should not use <Link> outside a <Router>

というエラーについて、僕自身調べてみて

Typescript中心のエラーの解決法が多いや、、。

と思ったので、シンプルなReactでの説明をしていきたいと思います。
また、解決に至るまでのプロセスで出会った3つの関数?(っていうのかな)たちも
どんどん解説していくので、ぜひ最後まで見ていってください。

まず僕がこのエラーに引っかかった時のコードかこちら、

App.js
import React from "react";
import { Link } from "react-router-dom";
export default function Home() {

  return (
    <div className="container">
      <title>Hands on Mania</title>
      <main className="u-text-center">
        <h1 className="font-family-homemade">Hands on Mania</h1>
        <p className="description">studying</p>  
        <Link to="/Auth/SignIn">signin</Link>
      </main>
    </div>
  );
}

策①   Memoryrouterを使う

最初は、react-router-domから「Memoryrouter」をインポートして

        <Memoryrouter>
          <Link to="/Auth/SignIn">signin</Link>
        </Memoryrouter>

このようにしましょう。との解決策でした

早速試したのですが、、結果は変わらず。。

まぁまぁ焦らず、ゆっくり行こーや。
ここで解決できてたらQiitaの記事にはしてません。

では簡単に解説します。

Memoryrouterについて簡単に調べたところ、

テストおよびReact Nativeのような非ブラウザー環境で役立ちます。

と調べて一番上にあった記事で解説されていたので、
テストコードを書く時。または、ReactNative環境で普通は使うものなのかな?
と思いました。

Memoryrouterと似たような(?)ものにhooksのuseMemoが思い出されたので、
こちらも申し訳程度に紹介。

React公式によれば

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

このような公式で使い、あくまで最適化のために使うそうだ
初心者にはまだ早い領域かもしれないですね?

策② Route と Switch を使う

こちらはgithubに乗っていた解決策だったので、
これはいけるっしょ。と内心ヨユーな表情で思っていたのですが、
あっけなく予想を裏切られました。
(僕のやり方が違う可能性が高い、、。)

方法としては、このように記述するのさっ。というやり方です。

import { Route, Switch } from "react-router";


   <Switch>
      <Route exact path="/Auth/SignIn" />
   </Switch>

しかし、結果は儚くとも同じようなエラー内容。。

Error: Invariant failed: You should not use <Switch> outside a <Router>

だめだー、、。
俺の人生終わったー、、。

諦めないで!
そうなるのはまだ早い。

次で解決策を提示しながら一緒に解説するから、ちょっと落ち着け、モちつけ、、、、

解決策③ (これで解決!)

App.js
import { BrowserRouter as Router, Route } from "react-router-dom";
import SignIn from "./Auth/SignIn";


   <Router>
      <Header />
      <Route exact path="/Auth/SignIn" component={SignIn} />
    </Router>
components/Header.js
import { Link } from "react-router-dom";


            <Link to="/Auth/SignIn">Signin</Link>

今回はこれで解決しました!?

LinkとRouterをつないでいる感じですね

こちら先程の策と比べながら、今回大切にして欲しい3つのポイントを解説していきますね。



①react-routerとreact-router-domが似ているようでちょっと違う件について

まず、今回import したのはreact-router-dom です。

先程はreact-routerでしたね。


これは何が違うねん!

まぁまぁ一回落ち着け、モちつけ、、、、、、(お気に入りのボケです。)

簡単な違いはというと、
react-router-domはBrowserRouterをRouterと一緒に使い、
react-routerは historyをRouterと一緒に使うということです。

詳しい解決や記述方法はreact-routerとreact-router-domこちらの記事を御覧ください!

まぁあまり大きな違いは無いようです。

② as の効果範囲
今回僕が BrowserRouter をimportする際に用いたasですが、
これの影響範囲は、例えばこの記述だと、どこまでだと思いますか?

import { BrowserRouter as Router, Route, Switch } from “react-router-dom”;

正解は、、、

Routerだけです。☆彡

以上。

③ componentsについて

<Route exact path="/Auth/SignIn" component={SignIn} />

まず、exactの記述に関して

pathに指定したルートと全く同じところに移動する感じです。

exactをつけないと違うルートに行ってしまう可能性があるためつけたほうがいいです。



componentの記述について

指定したconponentsを表示します。
今回はSignIn componentsを表示
表示したいcomponentsを import し忘れないように注意。

以上で簡単なルーティングの設定方法の解説を終わりにします。

最後に、
この記事を見る限り、v3 から v4への移行期に、色々と新しいのが出たり、
古いのが消えていたりしているそうなので、念の為、検索する際は確認してみたほうが無難かもしれないですね
react-router v3からv4へのマイグレーション
BrowserRouter

今回の参考文献【React】ルーティング設定方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] ルーティング設置の仕方に悩んでいる方。ルーティングのエラー解決と解説

どうもこんにちわ

今回は、Reactのルーティングを設定する上でよく目にする

Error: Invariant failed: You should not use <Link> outside a <Router>

というエラーについて、僕自身調べてみて

Typescript中心のエラーの解決法が多いや、、。

と思ったので、シンプルなReactでの説明をしていきたいと思います。
また、解決に至るまでのプロセスで出会った3つの関数?(っていうのかな)たちも
どんどん解説していくので、ぜひ最後まで見ていってください。

まず僕がこのエラーに引っかかった時のコードかこちら、

App.js
import React from "react";
import { Link } from "react-router-dom";
export default function Home() {

  return (
    <div className="container">
      <title>Hands on Mania</title>
      <main className="u-text-center">
        <h1 className="font-family-homemade">Hands on Mania</h1>
        <p className="description">studying</p>  
        <Link to="/Auth/SignIn">signin</Link>
      </main>
    </div>
  );
}

策①   Memoryrouterを使う

最初は、react-router-domから「Memoryrouter」をインポートして

        <Memoryrouter>
          <Link to="/Auth/SignIn">signin</Link>
        </Memoryrouter>

このようにしましょう。との解決策でした

早速試したのですが、、結果は変わらず。。

まぁまぁ焦らず、ゆっくり行こーや。
ここで解決できてたらQiitaの記事にはしてません。

では簡単に解説します。

Memoryrouterについて簡単に調べたところ、

テストおよびReact Nativeのような非ブラウザー環境で役立ちます。

と調べて一番上にあった記事で解説されていたので、
テストコードを書く時。または、ReactNative環境で普通は使うものなのかな?
と思いました。

Memoryrouterと似たような(?)ものにhooksのuseMemoが思い出されたので、
こちらも申し訳程度に紹介。

React公式によれば

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

このような公式で使い、あくまで最適化のために使うそうだ
初心者にはまだ早い領域かもしれないですね?

策② Route と Switch を使う

こちらはgithubに乗っていた解決策だったので、
これはいけるっしょ。と内心ヨユーな表情で思っていたのですが、
あっけなく予想を裏切られました。
(僕のやり方が違う可能性が高い、、。)

方法としては、このように記述するのさっ。というやり方です。

import { Route, Switch } from "react-router";


   <Switch>
      <Route exact path="/Auth/SignIn" />
   </Switch>

しかし、結果は儚くとも同じようなエラー内容。。

Error: Invariant failed: You should not use <Switch> outside a <Router>

だめだー、、。
俺の人生終わったー、、。

諦めないで!
そうなるのはまだ早い。

次で解決策を提示しながら一緒に解説するから、ちょっと落ち着け、モちつけ、、、、

解決策③ (これで解決!)

App.js
import { BrowserRouter as Router, Route } from "react-router-dom";
import SignIn from "./Auth/SignIn";


   <Router>
      <Header />
      <Route exact path="/Auth/SignIn" component={SignIn} />
    </Router>
components/Header.js
import { Link } from "react-router-dom";


            <Link to="/Auth/SignIn">Signin</Link>

今回はこれで解決しました!?

LinkとRouterをつないでいる感じですね

こちら先程の策と比べながら、今回大切にして欲しい3つのポイントを解説していきますね。



①react-routerとreact-router-domが似ているようでちょっと違う件について

まず、今回import したのはreact-router-dom です。

先程はreact-routerでしたね。


これは何が違うねん!

まぁまぁ一回落ち着け、モちつけ、、、、、、(お気に入りのボケです。)

簡単な違いはというと、
react-router-domはBrowserRouterをRouterと一緒に使い、
react-routerは historyをRouterと一緒に使うということです。

詳しい解決や記述方法はreact-routerとreact-router-domこちらの記事を御覧ください!

まぁあまり大きな違いは無いようです。

② as の効果範囲
今回僕が BrowserRouter をimportする際に用いたasですが、
これの影響範囲は、例えばこの記述だと、どこまでだと思いますか?

import { BrowserRouter as Router, Route, Switch } from “react-router-dom”;

正解は、、、

Routerだけです。☆彡

以上。

③ componentsについて

<Route exact path="/Auth/SignIn" component={SignIn} />

まず、exactの記述に関して

pathに指定したルートと全く同じところに移動する感じです。

exactをつけないと違うルートに行ってしまう可能性があるためつけたほうがいいです。



componentの記述について

指定したconponentsを表示します。
今回はSignIn componentsを表示
表示したいcomponentsを import し忘れないように注意。

以上で簡単なルーティングの設定方法の解説を終わりにします。

最後に、
この記事を見る限り、v3 から v4への移行期に、色々と新しいのが出たり、
古いのが消えていたりしているそうなので、念の為、検索する際は確認してみたほうが無難かもしれないですね
react-router v3からv4へのマイグレーション
BrowserRouter

今回の参考文献【React】ルーティング設定方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GASでプログラミング入門 Vol.3

GASでプログラミング入門 Vol.3

社内サークルにてエンジニアから非エンジニアの方向けにプログラミングを教えるという活動を行っています。

今回はその教材第3弾です。
前回の記事はこちら

前回の演習問題の解答例

(1). for文を使用して下記のように実行されるプログラムを記述してください

ループ処理1回目
ループ処理2回目
ループ処理3回目
ループ処理4回目
ループ処理5回目

解答例コード

for(let i = 1; i <= 5; i++){
    console.log("ループ処理" + i + "回目");
}

ポイントとしては、ループ処理を行う回数の条件式i <= 5の部分ですね。
今回は5回処理を繰り返したかったので、変数iの値がいくつになるまで繰り返す必要があるのか?についてコードを書く前にきちんと整理しておく必要があります。

(2). 変数strを作成し、"条件式1"という文字列で初期化し、下記のような出力になるプログラムを作成してください。

条件式1の処理を実行

また、変数strの値を"条件式2"に変更した場合は下記のようになるようにしてください。

条件式2の処理を実行

解答例コード

let str = "条件式1";
if (str == "条件式1") {
    console.log("条件式1の処理を実行");
} else if(str == "条件式2") {
    console.log("条件式2の処理を実行");
}

ポイントとしては、変数strの値に応じて、二つのルートにプログラムを分岐させる必要があるという点ですね。
上記解答例では何度実行しても条件式1の処理を実行と表示されますが、演習問題の仕様ではstrの値を条件式2に変更した場合についても考慮しておく必要が示されていますので、上記解答例のstrの値を書き換えて実行もして、確認するようにしておきましょう。
実務では条件式に用いられる変数の値はプログラム内にベタ書きされることはなく、外部からの入力などで受け取ることが多いので、プログラムの動作試験では取りうる可能性のある値の検証は全て行っておくなどします。

なお解答例はあくまで例なので、必ずしも上記のようになっていないといけないわけではありません。

算術演算子について

JavaScriptに限らずの話ですが、プログラミング言語には演算子というものがあります。
まずは算術演算子というものに関して、紹介します。
算術演算子を使用することで、様々な計算をプログラムで行うことが可能です。

加算

console.log(1 + 1);

実行結果

2

減算

console.log(10 - 1);

実行結果

9

乗算

console.log(10 * 10);

実行結果

100

除算

console.log(20 / 10);

実行結果

2

除算の余り(剰余)

console.log(10 % 3);

実行結果

1

べき乗(2のn乗など)

console.log(2 ** 3);

実行結果

8

++について(インクリメント演算子)

前回のfor文の箇所で出てきた++について紹介します。
++はインクリメント演算子と呼ばれるもので、簡単に言えば、算術演算子を短い表記で表現することが可能な演算子です。
まず前回のfor文のi++ですが、算術演算子を用いると下記の式と同じになります。

i = i + 1;

変数iに+1した結果を変数iに格納しています。
インクリメント演算子を用いると、上記の式に比べ簡潔に変数の値を+1するということが表現できていますので、このような場合には通常インクリメント演算子を使用することが一般的です。
なお、インクリメント演算子と似た演算子としてデクリメント演算子というものも存在します。
デクリメント演算子はi--のように記述し、下記の式と同じになります。

i = i - 1;

また、インクリメント、デクリメント演算子には前置型、後置型の2種類があり、ここで紹介したものは後置型になります。
ひとまず入門編の状態では後置型の表記方法を覚えておけば良いかと思います。
どうしても気になる方は「インクリメント演算子 前置 後置」などのキーワードで検索すると解説しているサイトが沢山見つかりますので、参照してみてください。

関係演算子について

続いて関係演算子について紹介します。
関係演算子とは左辺と右辺の値が大きい、小さいなどの関係を評価する演算子です。
前回の講義のfor文の箇所で出てきたi <= 5の部分になります。

小なり演算子

let num = 1;
if ( num < 2 ) {
    console.log("numは2より小さいです");
} else {
    console.log("numは2より小さくないです");
}

実行結果

numは2より小さいです

numの値を2に変更した場合の実行結果

numは2より小さくないです

大なり演算子

let num = 3;
if ( num > 2 ) {
    console.log("numは2より大きいです");
} else {
    console.log("numは2より大きくないです");
}

実行結果

numは2より大きいです

numの値を2に変更した場合の実行結果

numは2より大きくないです

小なりイコール

let num = 2;
if ( num <= 2 ) {
    console.log("numは2以下です");
} else {
    console.log("numは2以下ではないです");
}

実行結果

numは2以下です

numの値を3に変更した場合の実行結果

numは2以下ではないです

大なりイコール

let num = 2;
if ( num >= 2 ) {
    console.log("numは2以上です");
} else {
    console.log("numは2以上ではないです");
}

実行結果

numは2以上です

numの値を1に変更した場合の実行結果

numは2以上ではないです

等価演算子について

let num = 2;
if ( num == 2 ) {
    console.log("numは2です");
} else {
    console.log("numは2ではないです");
}

実行結果

numは2です

numの値を1に変更した場合の実行結果

numは2ではないです

不等価演算子について

let num = 1;
if ( num != 2 ) {
    console.log("numは2ではないです");
} else {
    console.log("numは2です");    
}

実行結果

numは2ではないです

numの値を2に変更した場合の実行結果

numは2です

演習問題

(1). for文を使用して下記のように実行されるプログラムを記述してください

ループ処理5回目
ループ処理4回目
ループ処理3回目
ループ処理2回目
ループ処理1回目

(2). 数値を格納する変数numを宣言して、値を任意の数値で初期化します。
数値の値が4の倍数なら下記のように表示するプログラムを記述してください。

numは4の倍数です。

また、4の倍数ではなかった場合は下記のように表示してください。

numは4の倍数ではありません。

まとめ

いかがでしたでしょうか。
今回は主に演算子について解説していきました。
前回学んだ内容の「逐次」、「反復」、「分岐」と組みわせることで様々な処理を行うことができるので、演算子の種類、記述方法などについて、しっかりと理解しておくことが重要になります。
それではまた次の記事でお会いしましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BFE.dev解答記録 #23. sum()を実装する

https://bfe.dev/ja はFrontEnd版のLeetCode、GAFAの面接を受けるなら練習した方がいいかなと。
以下は自分の練習記録です。

Alt Text

BFE.dev#23. sum()を実装するをみてみよう

問題

以下の条件を満たす前提でsum()を実装してください。

const sum1 = sum(1)
sum1(2) == 3 // true
sum1(3) == 4 // true
sum(1)(2)(3) == 6 // true
sum(5)(-1)(2) == 6 

分析

上記の用件からわかること

  1. sum() は引数を受けるfunctionを返す
  2. sum() では渡した引数を全部足して、1を繰り返す
  3. type coercionで、function == numberは可能。valueOftoStringを実装する必要

Let's code

まずfunctionを返す部分を作りましょう。引数の和を一時的に保存するために、2番目の引数にしたらいい。

function sum(num, currentSum = 0) {
  const newCurrentSum = num + currentSum

  const func = function(arg) {
    return sum(arg, num + currentSum)
  }

  return func
}

最後にvalueOfを入れれば完成

function sum(num, currentSum = 0) {
  const newCurrentSum = num + currentSum

  const func = function(arg) {
    return sum(arg, num + currentSum)
  }

  func.valueOf = () => newCurrentSum
  // below also works
  // func.toString = () => newCurrentSum.toString()

  return func
}

通った!

Alt Text

もし興味あれば、 BFE.devでやってみましょう https://bigfrontend.dev/ja/problem/create-a-sum

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jest-puppeteerでignoreHTTPSErrors:trueを設定する方法

puppeteerにはSSL証明書のエラーを無視するオプション(ignoreHTTPSErrors: true)があります。jest-puppeteerを介して動作させている場合にオプションを設定する渡す方法を調べました。

jest-puppeteer.config.jsを作成してください。

jest-puppeteer.config.js
module.exports = {
  launch: {
    ignoreHTTPSErrors: true
  }
}

参照

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング初めて3ヶ月

はじめまして!
shikichanと言います。

未経験でWEB系企業に転職する為、6月半ばからプログラミングを始めました!
まずは、progateでHTML&CSSを始めました。初学者にはこれがいいそうです。
54798AC7-69BB-4987-999F-81D39159C1C7_1_201_a.jpeg
これは7月途中のものです。ボチボチ受講しています。

1ヶ月半でだいたいlev.200までいけました。そろそろアウトプット出さないといけないと思い、8月からポートフォリオの作成に取り掛かっています。

これからも引き続きよろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS学習その⑥】関数とオブジェクト、そしてコールバック関数

JS学習シリーズの目的

このシリーズは、私ジャックが学んだJavaScriptのメカニズムについてアウトプットも兼ねて、
皆さんと知識や理解を共有するためのものです。
(理解に間違いがあればご指摘いただけると幸いです)

関数とオブジェクトの関係

関数は実行可能なオブジェクトである
まず、非常に大切なコンセプトとして、JavaScriptにおいて関数というのは、あくまでオブジェクトであり、
ただ実行可能である、というのが他のオブジェクトと異なる点です。
それ以外は、通常のオブジェクトと同じ挙動を取ります。

関数がオブジェクトであることの証明

例えば、次のような関数を書きます

main.js
function a() {
    console.log('hello');
}

a.prop = 0;
a.method = function() {
    console.log('method');
}

a();
a.method();
console.log(a.prop);

↓実行をConsoleで見た結果
image.png
関数に対して、オブジェクトに使用するようにプロパティの追加とメソッドの追加を行うと、↑の画像のように、追加できています。
これで関数はあくまで実行可能なオブジェクトであることがお分かりかと思います。

コールバック関数とは

他の関数に引数として渡される関数

main.js
function hello() {
    console.log('hello');
}

function fn(cb) {
    cb();
}

fn(hello); /*hello*/

↑のコードでは、

1.fn()関数の引数としてhelloというオブジェクト[cb]を渡す
2.fn()関数内でhelloという実行可能なオブジェクト[cb()]を実行する

という流れになっています。

おまけ

引数・コールバック関数の補足として
1.JavaScriptの関数というのは、宣言する時に設定する仮引数と呼び出す時の実引数の数は一致している必要はない。
2.コールバック関数は、無名関数で定義することもできる。

main.js
function hello(name) {
    console.log('hello ' + name);
}

function bye() {
    console.log('bye');
}

function fn(cb) {
    cb('Tom');
}

fn(hello); /*hello Tom*/
fn(bye); /*bye*/
fn(function(name) {
    console.log('hello ' + name);
})
/*hello Tom*/

したがって、↑のコードのように書くこともできます。

まとめ

いかがでしたでしょうか。
関数とオブジェクトの関係、そしてコールバック関数はJavaScriptにおいてとても重要なのでしっかり理解しておきましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js~Vuex~Jestでのテスト

はじめに

Vue.jsでVuexを使った時のテストの仕方が全然わからなくて
ちょっと前進したので忘れないうちにメモ
間違いがあったら教えていただけると嬉しいです。

テスト対象

<template>
  <div class="hello">
   <p>{{nowNumber}}</p>
  </div>
</template>

<script>
import {mapState} from 'vuex'
export default {
  name: 'HelloWorld',

  computed: {
    ...mapState(['nowNumber'])
  },
}
</script>

stateの値を表示するだけのコンポーネント

カウント用

インクリメント(足す)

<template>
  <div class="increment">
    <button class="increment__button" @click="incrementButton">足す</button>
  </div>
</template>
<script>
export default {
  methods: {
    incrementButton() {
      this.$store.dispatch("increment");
    },
  },
};
</script>

ボタンを押したらactions経由でmutationsからstateに加算

デクリメント(引く)

<template>
  <div class="decrement">
    <button @click="decrement">引く</button>
  </div>
</template>
<script>
export default {
  methods: {
    decrement() {
      this.$store.dispatch('decrement')
    },
  },
};
</script>

リセット

<template>
  <div class="reset">
    <button @click="reset">リセット</button>
  </div>
</template>
<script>
export default {
  methods: {
    reset() {
      this.$store.dispatch("reset");
    },
  },
};
</script>

基本的にこの3つはほぼ同じでメソッド名とかが違うくらい

Vuex(Store)

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export const state = {
  nowNumber: 0
}

export const mutations = {
  increment(state) {
    state.nowNumber++
  },
  decrement(state) {
    if (state.nowNumber <= 0) return
    state.nowNumber--
  },
  reset(state) {
    state.nowNumber = 0
  }
}

export const actions = {
  increment({ commit }) {
    commit('increment')
  },
  decrement({ commit }) {
    commit('decrement')
  },
  reset({ commit }) {
    commit('reset')
  }
}

export default new Vuex.Store({
  state,
  mutations,
  actions
})

ストアの部分はこんな感じです

そしてこれをテストする

テストコード

import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import { cloneDeep } from 'lodash'
import { actions, mutations } from '../../store/index.js'

const state = {
  nowNumber: 0
}

const initStore = () => {
  return cloneDeep({
    state,
    mutations,
    actions
  })
}

let store
let localVue
beforeEach(() => {
  localVue = createLocalVue()
  localVue.use(Vuex)

  store = new Vuex.Store(initStore())
})
describe('store', () => {

  it('dispatch incremetnt', () => {
    expect(store.state.nowNumber).toBe(0)
    store.dispatch('increment')
    expect(store.state.nowNumber).toBe(1)
  })
  it('dispatch decrement', () => {
    store.state.nowNumber = 1
    expect(store.state.nowNumber).toBe(1)
    store.dispatch('decrement')
    expect(store.state.nowNumber).toBe(0)
    store.dispatch('decrement')
    expect(store.state.nowNumber).toBe(0)
  })
  it('dispatch reset', () => {
    store.state.nowNumber = 5
    expect(store.state.nowNumber).toBe(5)
    store.dispatch('reset')
    expect(store.state.nowNumber).toBe(0)
  })
  it('dispatch まとめテスト', () => {
    store.dispatch('increment')
    expect(store.state.nowNumber).toBe(1)
    store.dispatch('increment')
    expect(store.state.nowNumber).toBe(2)
    store.dispatch('decrement')
    expect(store.state.nowNumber).toBe(1)
    store.dispatch('reset')
    expect(store.state.nowNumber).toBe(0)

  })
})

この場合だとストアをクローンしてそれを実行してテストしている感じになります。
なのでAPIとか使った物だとこれだと毎回通信してしまうと思うのでダメですね
ここは次の課題です
ひとまずこれでテストしたら無事に全部PASSしました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript] 高階関数で break する方法の考察

概要

  • 高階関数の forEachfor 文と異なり、処理途中で break できません。そこで、代替コードで break できる様にする方法を考えてみました。

  • 書く動機となった元記事
    なぜ我々は頑なにforを避けるのか

実装

  • 高階関数 find
findによるfor-breakの代替コード
;[1, 3, 5, 7].find(
  it => {
    console.log(it)
    return it === 5    // Same as `if (it === 5) break`
  }
)

// 1
// 3
// 5
  • 高階関数 reduce
reduceによるfor-breakの代替コード
;[1, 3, 5, 7].reduce(
  (bye, it) => {
    if (bye) return bye
    console.log(it)
    return it === 5  // Same as `if (it === 5) break`
  },
  false
)

// 1
// 3
// 5

まとめ

  • 本来の findreduce の使い方ではないため、可読性にかなり難があります。
  • これらは本来、代入文の右辺として使われます。右辺に書かれてないなら「本来の使い方ではない」ので、for-break の代替で使われてるのかと気づけそうです。

  • 「他にもっと好い方法があります!」と云う方は、コメント欄で教えて下さい!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インクリメンタル検索(Incremental Search)についてまとめてみる

インクリメンタル検索とは?

以下の動画のように、検索文字列を入力後に検索候補を表示する検索のことをインクリメンタル検索といいます。
ezgif.com-video-to-gif (1).gif

インクリメンタル検索のポイント

インクリメンタル検索は、即座に検索結果を表示せずに遅延して(=入力が確定したあとに)表示することがポイントです。
理由は、以下の動画を見て頂くとわかりやすいのですが、即座に検索結果に表示すると
1、開発者にとって、余計なサーバーへのリクエスト数が多くなる。
2、ユーザーにとって、入力の度に検索結果が変わって不快に思うことがある。
といったデメリットがあるからです。

2に関しては、例えばuniqloという単語を検索したいのにも関わらず、
uを押したら、検索結果表示
nを押したら、検索結果表示
iを押したら、検索結果表示
・・・・
と文字ごとに検索結果が表示されて、ユーザーが欲しくない情報が表示されてしまいます。

ezgif.com-video-to-gif.gif

なので、遅延して検索結果を表示することによって、
1、開発者にとって、余計なサーバーへのリクエスト数が減る。
2、ユーザーにとって、確定した単語(もしくは文字列)を表示することができる。
といったメリットが生まれます。
ezgif.com-video-to-gif (1).gif

JQueryによるインクリメンタル検索実装例

一定時間入力がない(以下の例だと500ms)場合は検索が確定しているので、そのタイミングで検索結果を表示します。

  addIncrementalSearchEvent() {
    const self = this;
    $('#js-incrementalSearch').on('input', function() {
      let enteringSearchWord = $('#js-incrementalSearch').val();
      let requestSearchWord = function() {
        setTimeout(async function() {
          let searchWord = $('#js-incrementalSearch').val();
          if (enteringSearchWord === searchWord) {
            // サーバーサイドにリクエストを投げる。
            if (!error) {
             // エラー処理
            }
          }
        }, 500);
      };
      requestSearchWord();
    });
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelDB.com~操作方法と勘所を書いた〜(2020/09/24メジャーアップデートに対応)

◇LaravelDB.com ?

Laravelのデータベース設計(ER図)するだけで、Migrationファイルがポンって作成できるFreeの「 テーブル設計&Migration作成 」 ツールです。

image.png

メイン機能

  • Migrationの自動生成
  • チームメンバーとの共有可能
  • CRUDコード(β版)の自動生成
  • CRUDコード(β版)でのValidation自動生成(テーブル設計に合わせて自動で生成)

用途

用途は開発者によってバラバラですね。
・Migrationのみ使う(ほとんどの人はここですかね)
・CRUDファイル全部(数%くらいの人)
・コード生成後、一部(Validationとか)をコピーして使う(結構な割合でいます)
・テーブル設計の共有(Team開発では使ってるようです)

◇操作方法(マニュアル)

1.テーブル作成

2.カラム作成


※使いやすくするポイント!! 「列を追加」ボタンを連打で必要な数だけ先に作ると便利!!

3.2020-09-24アップデートから使用可能

「 ENUM, UNSIGNED ] に対応、新規プロジェクトより表示されるようになります

以下はENUM、Doubleの入力してる例です。

「 UNIQUE, INDEX ] に対応、新規プロジェクトより表示されるようになります

外部キーの設定

外部キー制約を使用する場合( ※1対他、多対多などを分ける機能は無い)

4.ER図の保存

コントロールパネル[Data:セーブ/ロード]から移動します。

「クラウドに保存」or「ブラウザに保存」を選択できます。

この例では「クラウドに保存」を選択。

Save/Load機能 機能詳細
クラウドへ保存 アカウントに紐づきクラウドに保存します。
クラウドデータ一覧 保存したクラウド側のデータ一覧
ブラウザに保存 使用中のブラウザ「LocalStorage」に一時保存
ブラウザデータ一覧 localStorageに保存したデータ一覧

上記の「データ一覧」選択後、「 -- Data List -- 」の選択肢が表示されます。
そこに表示されるデータ項目を選択するとER図を復元します。

5.Migrationを生成

[ER図から出力]内のBuild [Migration]ボタンをクリックしてダウンロードします!


Zipファイルを解凍してダウンロードファイルを確認。

【ER図をTeamでシェア】

テーブル設計をシェアする機能のことです。

1 [送信側:シェアData]作成


見せたい相手にIDを渡しておいて、変更があれば「Create a [Share ID]」ボタンを押すと毎度データ更新されることを知っておきましょう!

2 [送信側:シェアData] IDをコピー

ここでコピーしたIDを相手に知らせます。

3 [受信側:シェアData]読み込み

相手は送られてきた[シェアData]IDを貼り付けます。

4 「Read」ボタンをクリックしてデータを受信表示しましょう。

3.シェアデータの複製が完了!!

受信側にデータが入りました!受信側もそのデータを活用できるようになります。
チーム・メンタリング等のケースでも利用可能です。

※受信後は「別名を付けて保存しておくと良いでしょう!」

◇ β版のCRUD/リレーションを使用したい場合(まだまだ開発段階の未知の機能)

<重要>この機能はテーブル名の末尾に「s」が無いとうまく動作しません。
※ENUMは必須・未入力のみ出力。
※intは型・必須・未入力のみ出力、sizeが未対応。
※2020-09-24Migrtion機能にアップデートが入ったので、β版機能に影響があるかもしれません。

  1. まずは、複数のテーブルを簡単に作り「外部キーの接続」をして準備してください!!
    その後、右メニュー「 [Data]セーブ/ロード 」クリックすると以下画面が表示されます。

  2. BUILD [CRUD CODE] をクリック!!CRUD ファイル一式が生成されます

【ポイント】
  • Relation(Beta)にチェックを入れておくとリレーションします。

  • BUILD [MIGRATION] でもリレーション(QueryBuilder)がコメントで生成されますよ。

【このツールでのリレーションのポイント】

  • JOINしてるテーブルの全ての項目を最初は表示します(同項目名が存在する場合、片方のみ表示します)。
  • CRUDのコードが生成されたら、仕様に合わせてHTMLテーブルの項目を削除してください。
  • リレーションは上記画像のように「チェック」を入れないと生成しないようにしています(Beta版のため)。
  • Controller内にコードが生成されてますので、確認しておくと良いでしょう!
  • 外部キーはこのツールでは非推奨としています(理由:Migrationの実行順序が重要でエラーになりやすい為) プロトタイプ(ベースになるコードを生成してくれる)には十分すぎる機能ですね。

◇Validation → テーブル設計に合わせて生成

Validationって地味に面倒ですよね、完璧では内容ですが、少しの手直しで使用できるなら便利そうです!!

例でざっくりテーブルをER図で書いてみました。
以下"t_gsusers"テーブルを中心に見ていきます。

生成されたコントローラーのcreate(),edit()にはこういったvalidationが挿入されます。

TGsusersController
            $this->validate($request, [
                "name" => "required|max:128", //string('name',128)
                "lid" => "required|max:128", //string('lid',128)
                "lpw" => "required|max:128", //string('lpw',128)
                "m_department_id" => "required|integer", //integer('m_department_id')
                "m_position_id" => "required|integer", //integer('m_position_id')
                "m_prefectures_id" => "required|integer", //integer('m_prefectures_id')
                "m_kanri_id" => "required|integer", //integer('m_kanri_id')
                "m_lifeflg_id" => "required|integer", //integer('m_lifeflg_id')
                "m_test_id" => "required|integer", //integer('m_test_id')

            ]);

{{old('name')}} → 入力項目を補完(CRUD:β版を使用した場合に生成されます)

validationで未入力等ではじかれた場合、入力した文字を消さずに表示します。

ベータ版のCRUD/リレーションの利用シーン

上記のようにβ版ではありますが、全てを利用するというよりは、コードを生成して、必要な部分だけ使うのも良いと思います。(実際にそういったケースを耳にしました〜)

◇LaravelDB.com 対応カラム一覧

tinyIncrements
mediumIncrements
smallIncrements
bigIncrements
increments
mediumInteger
smallInteger
bigInteger
tinyInteger
integer
unsignedInteger (2020-09-24対応)
unsignedTinyInteger (2020-09-24対応)
unsignedSmallInteger (2020-09-24対応)
unsignedMediumInteger (2020-09-24対応)
unsignedBigInteger (2020-09-24対応)
decimal
double
float
enum (2020-09-24対応)
geometryCollection
geometry
jsonb
json
char
longText
mediumText
text
multiLineString
lineString
string
multiPoint
multiPolygon
point
polygon
binary
nullableTimestamps
timestamps (2020-09-24対応)
softDeletes (2020-09-24対応)
dateTime
timestamp
time
year
date

◇そもそも、なんで作ったの?

私は学校でプログラミング(サービスを作る基本)を教えていて、テーブル設計している学生がその場にいて
「テーブル設計して、そこからMigrationファイル作成して、コードをイチから書いて・・・」普通の作業ではあるんですが、何故か「その時は疑問を感じました」、ER図書いたらMigrationファイル生成したら便利だよね~。
何割かの開発者は喜んでくれるのでは?と思ったのがキッカケでした。
特に「テーブル設計は保存可能」なので、前回作ったテーブル設計から新しい設計を複製できるのは嬉しい機能です。設計してMigration or CRUDコード書き出してができるので、「設計して→コード書いて」という往復作業が無くなるので、是非使ってほしいと思います。

今後の展開

2020-09-24以降からは「Migration」を軸にアップデートしていきます
なぜ、↑そう思ったか?
良かれと思った機能が意外に余計だった。。。よくあるパターンですね。
テーブル名に"t_","m_"とかトランザクション・マスターテーブルなどがテーブル名で分かるようにした場合に、自動で「timestamps」「softDeletes」などを挿入する機能が逆に「解りにくくさせていた」という事があります。今回のアップデートで廃止いしたのでご安心ください(余計なことはしません(^^))。
やはりシンプルがベストなんですよね。今はいかに「シンプルにするか」だけ考えてアップデートを考えています。
※必要な機能はどうやって複雑にせずに追加するか?など、悩みが楽しくてしょうがありません。
※CRUD(β版)機能のアップデートはどうなるか未定(Laravel7までは動作確認ずみ)
※Laravel8移行のLTSの仕様で検討予定ですかね~~~かなり変わってるように思ったので。

◇LaravelDB.com サイト

laravelDB.com

◇Twitterアカウント

LaravelDB.com

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelDB.com~操作方法と勘所を書いた〜2020/09/24メジャーアップデート版

◇LaravelDB.com ?

Laravelのデータベース設計(ER図)するだけで、Migrationファイルがポンって作成できるFreeの「 テーブル設計&Migration作成 」 ツールです。

image.png

メイン機能

  • Migrationの自動生成
  • チームメンバーとの共有可能
  • CRUDコード(β版)の自動生成
  • CRUDコード(β版)でのValidation自動生成(テーブル設計に合わせて自動で生成)

用途

用途は開発者によってバラバラですね。
・Migrationのみ使う(ほとんどの人はここですかね)
・CRUDファイル全部(数%くらいの人)
・コード生成後、一部(Validationとか)をコピーして使う(結構な割合でいます)
・テーブル設計の共有(Team開発では使ってるようです)

◇操作方法(マニュアル)

1.テーブル作成

2.カラム作成


※使いやすくするポイント!! 「列を追加」ボタンを連打で必要な数だけ先に作ると便利!!

3.2020-09-24アップデートから使用可能

「 ENUM, UNSIGNED ] に対応、新規プロジェクトより表示されるようになります

以下はENUM、Doubleの入力してる例です。

「 UNIQUE, INDEX ] に対応、新規プロジェクトより表示されるようになります

外部キーの設定

外部キー制約を使用する場合( ※1対他、多対多などを分ける機能は無い)

4.ER図の保存

コントロールパネル[Data:セーブ/ロード]から移動します。

「クラウドに保存」or「ブラウザに保存」を選択できます。

この例では「クラウドに保存」を選択。

Save/Load機能 機能詳細
クラウドへ保存 アカウントに紐づきクラウドに保存します。
クラウドデータ一覧 保存したクラウド側のデータ一覧
ブラウザに保存 使用中のブラウザ「LocalStorage」に一時保存
ブラウザデータ一覧 localStorageに保存したデータ一覧

上記の「データ一覧」選択後、「 -- Data List -- 」の選択肢が表示されます。
そこに表示されるデータ項目を選択するとER図を復元します。

5.Migrationを生成

[ER図から出力]内のBuild [Migration]ボタンをクリックしてダウンロードします!


Zipファイルを解凍してダウンロードファイルを確認。

【ER図をTeamでシェア】

テーブル設計をシェアする機能のことです。

1 [送信側:シェアData]作成


見せたい相手にIDを渡しておいて、変更があれば「Create a [Share ID]」ボタンを押すと毎度データ更新されることを知っておきましょう!

2 [送信側:シェアData] IDをコピー

ここでコピーしたIDを相手に知らせます。

3 [受信側:シェアData]読み込み

相手は送られてきた[シェアData]IDを貼り付けます。

4 「Read」ボタンをクリックしてデータを受信表示しましょう。

3.シェアデータの複製が完了!!

受信側にデータが入りました!受信側もそのデータを活用できるようになります。
チーム・メンタリング等のケースでも利用可能です。

※受信後は「別名を付けて保存しておくと良いでしょう!」

◇ β版のCRUD/リレーションを使用したい場合(まだまだ開発段階の未知の機能)

<重要>この機能はテーブル名の末尾に「s」が無いとうまく動作しません。
※ENUMは必須・未入力のみ出力。
※intは型・必須・未入力のみ出力、sizeが未対応。
※2020-09-24Migrtion機能にアップデートが入ったので、β版機能に影響があるかもしれません。

  1. まずは、複数のテーブルを簡単に作り「外部キーの接続」をして準備してください!!
    その後、右メニュー「 [Data]セーブ/ロード 」クリックすると以下画面が表示されます。

  2. BUILD [CRUD CODE] をクリック!!CRUD ファイル一式が生成されます

【ポイント】
  • Relation(Beta)にチェックを入れておくとリレーションします。

  • BUILD [MIGRATION] でもリレーション(QueryBuilder)がコメントで生成されますよ。

【このツールでのリレーションのポイント】

  • JOINしてるテーブルの全ての項目を最初は表示します(同項目名が存在する場合、片方のみ表示します)。
  • CRUDのコードが生成されたら、仕様に合わせてHTMLテーブルの項目を削除してください。
  • リレーションは上記画像のように「チェック」を入れないと生成しないようにしています(Beta版のため)。
  • Controller内にコードが生成されてますので、確認しておくと良いでしょう!
  • 外部キーはこのツールでは非推奨としています(理由:Migrationの実行順序が重要でエラーになりやすい為) プロトタイプ(ベースになるコードを生成してくれる)には十分すぎる機能ですね。

◇Validation → テーブル設計に合わせて生成

Validationって地味に面倒ですよね、完璧では内容ですが、少しの手直しで使用できるなら便利そうです!!

例でざっくりテーブルをER図で書いてみました。
以下"t_gsusers"テーブルを中心に見ていきます。

生成されたコントローラーのcreate(),edit()にはこういったvalidationが挿入されます。

TGsusersController
            $this->validate($request, [
                "name" => "required|max:128", //string('name',128)
                "lid" => "required|max:128", //string('lid',128)
                "lpw" => "required|max:128", //string('lpw',128)
                "m_department_id" => "required|integer", //integer('m_department_id')
                "m_position_id" => "required|integer", //integer('m_position_id')
                "m_prefectures_id" => "required|integer", //integer('m_prefectures_id')
                "m_kanri_id" => "required|integer", //integer('m_kanri_id')
                "m_lifeflg_id" => "required|integer", //integer('m_lifeflg_id')
                "m_test_id" => "required|integer", //integer('m_test_id')

            ]);

{{old('name')}} → 入力項目を補完(CRUD:β版を使用した場合に生成されます)

validationで未入力等ではじかれた場合、入力した文字を消さずに表示します。

ベータ版のCRUD/リレーションの利用シーン

上記のようにβ版ではありますが、全てを利用するというよりは、コードを生成して、必要な部分だけ使うのも良いと思います。(実際にそういったケースを耳にしました〜)

◇LaravelDB.com 対応カラム一覧

tinyIncrements
mediumIncrements
smallIncrements
bigIncrements
increments
mediumInteger
smallInteger
bigInteger
tinyInteger
integer
unsignedInteger (2020-09-24対応)
unsignedTinyInteger (2020-09-24対応)
unsignedSmallInteger (2020-09-24対応)
unsignedMediumInteger (2020-09-24対応)
unsignedBigInteger (2020-09-24対応)
decimal
double
float
enum (2020-09-24対応)
geometryCollection
geometry
jsonb
json
char
longText
mediumText
text
multiLineString
lineString
string
multiPoint
multiPolygon
point
polygon
binary
nullableTimestamps
timestamps (2020-09-24対応)
softDeletes (2020-09-24対応)
dateTime
timestamp
time
year
date

◇そもそも、なんで作ったの?

私は学校でプログラミング(サービスを作る基本)を教えていて、テーブル設計している学生がその場にいて
「テーブル設計して、そこからMigrationファイル作成して、コードをイチから書いて・・・」普通の作業ではあるんですが、何故か「その時は疑問を感じました」、ER図書いたらMigrationファイル生成したら便利だよね~。
何割かの開発者は喜んでくれるのでは?と思ったのがキッカケでした。
特に「テーブル設計は保存可能」なので、前回作ったテーブル設計から新しい設計を複製できるのは嬉しい機能です。設計してMigration or CRUDコード書き出してができるので、「設計して→コード書いて」という往復作業が無くなるので、是非使ってほしいと思います。

今後の展開

2020-09-24以降からは「Migration」を軸にアップデートしていきます
なぜ、↑そう思ったか?
良かれと思った機能が意外に余計だった。。。よくあるパターンですね。
テーブル名に"t_","m_"とかトランザクション・マスターテーブルなどがテーブル名で分かるようにした場合に、自動で「timestamps」「softDeletes」などを挿入する機能が逆に「解りにくくさせていた」という事があります。今回のアップデートで廃止いしたのでご安心ください(余計なことはしません(^^))。
やはりシンプルがベストなんですよね。今はいかに「シンプルにするか」だけ考えてアップデートを考えています。
※必要な機能はどうやって複雑にせずに追加するか?など、悩みが楽しくてしょうがありません。
※CRUD(β版)機能のアップデートはどうなるか未定(Laravel7までは動作確認ずみ)
※Laravel8移行のLTSの仕様で検討予定ですかね~~~かなり変わってるように思ったので。

◇LaravelDB.com サイト

laravelDB.com

◇Twitterアカウント

LaravelDB.com

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelDB.com~操作方法と勘所を書いた〜(基本操作編)

◇LaravelDB.com ?

Laravelのデータベース設計(ER図)するだけで、Migrationファイルがポンって作成できるFreeの「 テーブル設計&Migration作成 」 ツールです。

image.png

メイン機能

  • Migrationの自動生成
  • チームメンバーとの共有可能
  • CRUDコード(β版)の自動生成
  • CRUDコード(β版)でのValidation自動生成(テーブル設計に合わせて自動で生成)

用途

用途は開発者によってバラバラですね。
・Migrationのみ使う(ほとんどの人はここですかね)
・CRUDファイル全部(数%くらいの人)
・コード生成後、一部(Validationとか)をコピーして使う(結構な割合でいます)
・テーブル設計の共有(Team開発では使ってるようです)

◇操作方法(マニュアル)

1.テーブル作成

2.カラム作成


※使いやすくするポイント!! 「列を追加」ボタンを連打で必要な数だけ先に作ると便利!!

3.2020-09-24アップデートから使用可能

「 ENUM, UNSIGNED ] に対応、新規プロジェクトより表示されるようになります

以下はENUM、Doubleの入力してる例です。

「 UNIQUE, INDEX ] に対応、新規プロジェクトより表示されるようになります

外部キーの設定

外部キー制約を使用する場合( ※1対他、多対多などを分ける機能は無い)

4.ER図の保存

コントロールパネル[Data:セーブ/ロード]から移動します。

「クラウドに保存」or「ブラウザに保存」を選択できます。

この例では「クラウドに保存」を選択。

Save/Load機能 機能詳細
クラウドへ保存 アカウントに紐づきクラウドに保存します。
クラウドデータ一覧 保存したクラウド側のデータ一覧
ブラウザに保存 使用中のブラウザ「LocalStorage」に一時保存
ブラウザデータ一覧 localStorageに保存したデータ一覧

上記の「データ一覧」選択後、「 -- Data List -- 」の選択肢が表示されます。
そこに表示されるデータ項目を選択するとER図を復元します。

5.Migrationを生成

[ER図から出力]内のBuild [Migration]ボタンをクリックしてダウンロードします!


Zipファイルを解凍してダウンロードファイルを確認。

【ER図をTeamでシェア】

テーブル設計をシェアする機能のことです。

1 [送信側:シェアData]作成


見せたい相手にIDを渡しておいて、変更があれば「Create a [Share ID]」ボタンを押すと毎度データ更新されることを知っておきましょう!

2 [送信側:シェアData] IDをコピー

ここでコピーしたIDを相手に知らせます。

3 [受信側:シェアData]読み込み

相手は送られてきた[シェアData]IDを貼り付けます。

4 「Read」ボタンをクリックしてデータを受信表示しましょう。

3.シェアデータの複製が完了!!

受信側にデータが入りました!受信側もそのデータを活用できるようになります。
チーム・メンタリング等のケースでも利用可能です。

※受信後は「別名を付けて保存しておくと良いでしょう!」

◇ β版のCRUD/リレーションを使用したい場合(まだまだ開発段階の未知の機能)

<重要>この機能はテーブル名の末尾に「s」が無いとうまく動作しません。
※ENUMは必須・未入力のみ出力。
※intは型・必須・未入力のみ出力、sizeが未対応。
※2020-09-24Migrtion機能にアップデートが入ったので、β版機能に影響があるかもしれません。

  1. まずは、複数のテーブルを簡単に作り「外部キーの接続」をして準備してください!!
    その後、右メニュー「 [Data]セーブ/ロード 」クリックすると以下画面が表示されます。

  2. BUILD [CRUD CODE] をクリック!!CRUD ファイル一式が生成されます

【ポイント】
  • Relation(Beta)にチェックを入れておくとリレーションします。

  • BUILD [MIGRATION] でもリレーション(QueryBuilder)がコメントで生成されますよ。

【このツールでのリレーションのポイント】

  • JOINしてるテーブルの全ての項目を最初は表示します(同項目名が存在する場合、片方のみ表示します)。
  • CRUDのコードが生成されたら、仕様に合わせてHTMLテーブルの項目を削除してください。
  • リレーションは上記画像のように「チェック」を入れないと生成しないようにしています(Beta版のため)。
  • Controller内にコードが生成されてますので、確認しておくと良いでしょう!
  • 外部キーはこのツールでは非推奨としています(理由:Migrationの実行順序が重要でエラーになりやすい為) プロトタイプ(ベースになるコードを生成してくれる)には十分すぎる機能ですね。

◇Validation → テーブル設計に合わせて生成

Validationって地味に面倒ですよね、完璧では内容ですが、少しの手直しで使用できるなら便利そうです!!

例でざっくりテーブルをER図で書いてみました。
以下"t_gsusers"テーブルを中心に見ていきます。

生成されたコントローラーのcreate(),edit()にはこういったvalidationが挿入されます。

TGsusersController
            $this->validate($request, [
                "name" => "required|max:128", //string('name',128)
                "lid" => "required|max:128", //string('lid',128)
                "lpw" => "required|max:128", //string('lpw',128)
                "m_department_id" => "required|integer", //integer('m_department_id')
                "m_position_id" => "required|integer", //integer('m_position_id')
                "m_prefectures_id" => "required|integer", //integer('m_prefectures_id')
                "m_kanri_id" => "required|integer", //integer('m_kanri_id')
                "m_lifeflg_id" => "required|integer", //integer('m_lifeflg_id')
                "m_test_id" => "required|integer", //integer('m_test_id')

            ]);

{{old('name')}} → 入力項目を補完(CRUD:β版を使用した場合に生成されます)

validationで未入力等ではじかれた場合、入力した文字を消さずに表示します。

ベータ版のCRUD/リレーションの利用シーン

上記のようにβ版ではありますが、全てを利用するというよりは、コードを生成して、必要な部分だけ使うのも良いと思います。(実際にそういったケースを耳にしました〜)

◇LaravelDB.com 対応カラム一覧

tinyIncrements
mediumIncrements
smallIncrements
bigIncrements
increments
mediumInteger
smallInteger
bigInteger
tinyInteger
integer
unsignedInteger (2020-09-24対応)
unsignedTinyInteger (2020-09-24対応)
unsignedSmallInteger (2020-09-24対応)
unsignedMediumInteger (2020-09-24対応)
unsignedBigInteger (2020-09-24対応)
decimal
double
float
enum (2020-09-24対応)
geometryCollection
geometry
jsonb
json
char
longText
mediumText
text
multiLineString
lineString
string
multiPoint
multiPolygon
point
polygon
binary
nullableTimestamps
timestamps (2020-09-24対応)
softDeletes (2020-09-24対応)
dateTime
timestamp
time
year
date

◇そもそも、なんで作ったの?

私は学校でプログラミング(サービスを作る基本)を教えていて、テーブル設計している学生がその場にいて
「テーブル設計して、そこからMigrationファイル作成して、コードをイチから書いて・・・」普通の作業ではあるんですが、何故か「その時は疑問を感じました」、ER図書いたらMigrationファイル生成したら便利だよね~。
何割かの開発者は喜んでくれるのでは?と思ったのがキッカケでした。
特に「テーブル設計は保存可能」なので、前回作ったテーブル設計から新しい設計を複製できるのは嬉しい機能です。設計してMigration or CRUDコード書き出してができるので、「設計して→コード書いて」という往復作業が無くなるので、是非使ってほしいと思います。

今後の展開

2020-09-24以降からは「Migration」を軸にアップデートしていきます
なぜ、↑そう思ったか?
良かれと思った機能が意外に余計だった。。。よくあるパターンですね。
テーブル名に"t_","m_"とかトランザクション・マスターテーブルなどがテーブル名で分かるようにした場合に、自動で「timestamps」「softDeletes」などを挿入する機能が逆に「解りにくくさせていた」という事があります。今回のアップデートで廃止いしたのでご安心ください(余計なことはしません(^^))。
やはりシンプルがベストなんですよね。今はいかに「シンプルにするか」だけ考えてアップデートを考えています。
※必要な機能はどうやって複雑にせずに追加するか?など、悩みが楽しくてしょうがありません。
※CRUD(β版)機能のアップデートはどうなるか未定(Laravel7までは動作確認ずみ)
※Laravel8移行のLTSの仕様で検討予定ですかね~~~かなり変わってるように思ったので。

◇LaravelDB.com サイト

laravelDB.com

◇Twitterアカウント

LaravelDB.com

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScript: メソッドを「メソッド定義」で生やした場合と「フィールド宣言」で生やした場合の違い

この投稿では、JavaScriptでclass構文を用いる際に、メソッドを「メソッド定義」でプロトタイプメソッドとして生やした場合と、「フィールド宣言」を用いてフィールドとして生やした場合の違いについて説明します。

メソッド定義とフィールド宣言

JavaScriptでは、ECMAScript 2015からクラス宣言の構文が導入されました。

class Foo {
}

これと同時に、プロトタイプメソッドを定義するための「メソッド定義」の構文も追加されました。

class Foo {
  method() {
  }
}

また、「フィールド宣言」という新しい構文が提案されており、今後ECMAScriptに導入されることが有力視されています。

class Foo {
  a = 0 // フィールド宣言
}

// 上は下と同じ意味

class Foo {
  constructor() {
    this.a = 0
  }
}

TypeScriptでは、この新しいフィールド宣言にも対応しており、コンパイルするとコンストラクタによるフィールド初期化に変換してくれます:

class Foo {
  a: number = 0 // フィールド宣言
}

// target=ES2020でのコンパイル結果↓
class Foo {
    constructor() {
        this.a = 0; // フィールド宣言
    }
}

ちなみに、TypeScriptでは上のような変形をしますが、Node.js v12.4以降や最近のブラウザでは、フィールド宣言の文法をそのまま解釈できるようになっています。

本稿で比較するもの

インスタンスにメソッドを持たせる際、TypeScriptでもJavaScriptでもメソッド定義とフィールド宣言の2つの書き方がありえます。

メソッドを持たせるには、メソッド定義を使うのが普通です:

class A {
  name = 'Aクラス'

  x(): void {
    console.log(this.name + 'のxメソッドが呼ばれました。')
  }
}

const a = new A()
a.x()
//=> Aクラスのxメソッドが呼ばれました。

一方、あまり一般的ではないですが、フィールド宣言を用いてメソッドを生やすこともできるにはできます:

class A {
  name = 'Aクラス'
  x = (): void => {
    console.log(this.name + 'のxメソッドが呼ばれました。')
  }
}
const a = new A()
a.x()
//=> Aクラスのxメソッドが呼ばれました。

どちらの方法でメソッド定義しても、a.x()のように呼び出せます。また、thisで自分のインスタンスにアクセスできる点も同じです。

では、メソッド定義とフィールド宣言の違いは全くないのでしょうか? この2つ事例別に比較していきたいと思います。

相違1: super

まず、大きな違いとして、理由は後述しますが、メソッド定義とフィールド宣言では、superで継承元のメソッドを呼べる呼べないの違いがあります。

メソッド定義では、メソッドをオーバライドした場合に、superで親のメソッドを呼び出すことができます:

class A {
  x(): void {
    console.log('Aクラスのxメソッドが呼ばれました。')
  }
}

class B extends A {
  x(): void {
    console.log('Bクラスのxメソッドが呼ばれました。')
    super.x()
  }
}

const b = new B()
b.x()
//=> Bクラスのxメソッドが呼ばれました。
//=> Aクラスのxメソッドが呼ばれました。

一方のフィールド宣言では、superを用いるとコンパイルは通るものの、実行時エラーが発生するケースがあります:

class A {
  x = (): void => {
    console.log('Aクラスのxメソッドが呼ばれました。')
  }
}

class B extends A {
  x = (): void => {
    console.log('Bクラスのxメソッドが呼ばれました。')
    super.x()
  }
}

const b = new B()
b.x()
//=> TypeError: (intermediate value).x is not a function

(オーバーライドではなく、親クラスのメソッド定義によるメソッドは、子クラスのフィールド宣言メソッドからでもsuperで呼び出すことはできます)
class A {
  x = () => {
    console.log('Aクラスのxメソッドが実行されました')
  }
  y() {
    console.log('Aクラスのyメソッドが実行されました')
  }
}

class B extends A {
  x = () => {
    super.y()
  }
  y() {
    throw new Error('これは呼ばれるべきでない')
  }
}

const b = new B()
b.x()
//=> "Aクラスのyメソッドが実行されました" 

こうした違いがあるのは、メソッド定義で生やしたメソッドが、プロトタイプに入るのに対し、フィールド宣言で生やしたメソッドはインスタンスだけのプロパティになるためです。

class C {
  method1() {} // メソッド定義
  method2 = () => {} // フィールド宣言
}

console.log(typeof C.prototype.method1) //=> "function"
console.log(typeof C.prototype.method2) //=> "undefined" ← フィールド宣言はプロトタイプにない

相違2: 自分が所有するプロパティ・列挙可能プロパティ

2つ目の違いとして、メソッドが自分が所有するプロパティ、かつ、列挙可能プロパティになるかどうかという違いがあります。

まず、メソッド定義の場合は、自分の所有するプロパティにも、列挙可能プロパティにもなりません:

class A {
  x(): void {
  }
}

const a = new A()
const 自分のプロパティか = a.hasOwnProperty('x')
console.log(自分のプロパティか) //=> false
const 列挙可能プロパティか = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(a), 'x')?.enumerable
console.log(列挙可能プロパティか) //=> false

一方のフィールド宣言によるメソッドは、自分の所有するプロパティになり、かつ、列挙可能プロパティにもなります:

class A {
  x = () => void {
  }
}

const a = new A()
const 自分のプロパティか = a.hasOwnProperty('x')
console.log(自分のプロパティか) //=> true
const 列挙可能プロパティか = Object.getOwnPropertyDescriptor(a, 'x')?.enumerable
console.log(列挙可能プロパティか) //=> true

この違いは、Object.keysObject.valuesObject.entriesなどによる列挙可能プロパティを取り出す操作や、スプレッド演算子による操作に実行結果の違いが現れます。

class C {
  method1() {} // メソッド定義
  method2 = () => {} // フィールド宣言
}

const c = new C()

// 列挙可能プロパティを取り出す操作
const keys = Object.keys(c)
console.log(keys)
//=> ["method2"] ← フィールド宣言によるメソッドの名前

// スプレッド演算子による操作
const c2 = { ...c }
console.log(typeof c2.method1) //=> "undefined"
console.log(typeof c2.method2) //=> "function" ← フィールド宣言によるメソッド

相違3: メモリ使用量

相違1で、メソッド定義は関数がプロトタイプに入るのに対し、フィールド宣言はそうでないということを説明しましたが、これはメモリ使用量にも差が生まれる原因になります。

メモリ使用量を比べるために、メソッド定義版のクラスと、フィールド宣言版のクラスの2つのJavaScriptファイルを用意して、それぞれ100万インスタンス生成して、比較してみました。使用した実行環境はNode.js 14.4.0です。

まず、メソッド定義版のファイルです:

test1.js
class A {
  x() {}
}

const list = []
for (let i = 0; i < 1_000_000; i++) {
  list.push(new A())
}

const used = process.memoryUsage();
for (let key in used) {
  console.log(`${key} ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`);
}

実行結果は以下のようになりました:

$ node test1.js
Using v14.4.0
rss 76.83 MB
heapTotal 64.46 MB
heapUsed 41.14 MB
external 0.73 MB
arrayBuffers 0.01 MB

次に、フィールド宣言版のファイルです:

test2.js
class A {
  x = () => {}
}

const list = []
for (let i = 0; i < 1_000_000; i++) {
  list.push(new A())
}

const used = process.memoryUsage();
for (let key in used) {
  console.log(`${key} ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`);
}

実行結果は次のようになりました:

$ node test2.js
rss 161.05 MB
heapTotal 136.34 MB
heapUsed 109.49 MB
external 0.73 MB
arrayBuffers 0.01 MB

メモリ使用量をまとめると次の表のとおりになりました:

メソッド定義版 フィールド宣言版
rss 76.83 MB 161.05 MB
heapTotal 64.46 MB 136.34 MB
heapUsed 41.14 MB 109.49 MB
external 0.73 MB 0.73 MB
arrayBuffers 0.01 MB 0.01 MB

以上の結果からも明らかなように、メソッド定義と比べて、フィールド宣言のほうはメモリを多く使うことが分かります。

このひとつの原因は、メソッド定義で生やしたメソッドは、1つしか関数オブジェクトが作られないのに対して、フィールド宣言で生やしたメソッドは、newされるごとに新しい関数オブジェクトが作られるためと考えられます。新しい関数オブジェクトが生成されているかは、同一性を比較することで分かります:

class C {
  method1() {}
  method2 = () => {}
}

const c1 = new C()
const c2 = new C()

// メソッド定義によるメソッドの同一性
console.log(c1.method1 === c1.method1) //=> true

// フィールド宣言によるメソッドの同一性
console.log(c1.method2 === c2.method2) //=> false

メソッド定義で生やしたmethod1はインスタンスが異なっていても、同じ関数オブジェクトになっていますが、フィールド宣言で生やしたmethod2は、名前と実装が同じでも、インスタンスが異なると、別の関数オブジェクトになっています。

まとめ

本稿で行った比較をまとめると、次のようになります。

フィールド宣言で生やしたメソッドは、メソッド定義と比べて、

  • メソッドオーバーライドではsuperが使えない場合がある。
  • プロトタイプに入らない。
  • インスタンス自身が所有するプロパティになる。
  • 列挙可能なプロパティになる。
  • 複数インスタンスを生成する場合、メモリを多く使う。

以上が、メソッドをメソッド定義で生やした場合と、フィールド宣言で生やした場合の違いです。

特段理由がない限り、クラス構文でインスタンスメソッドを生やすときは、メソッド定義の構文を使ったほうが良さそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Bolt を使って Slack でヘルプデスクチームとのやりとりを効率化するアプリをつくってみよう

Slack アプリ開発へようこそ!

この記事では、ショートカット、モーダルのようなインタラクティブな機能を Bolt for JavaScript を使ってどのように実装すればよいかを解説します。

なお、この記事で紹介するのは、BIT VALLEY 2020 のための用意されたデモアプリです。

https://github.com/seratch/bit-valley-2020-slack-bolt-app

BIT VALLEY での講演は YouTube で公開されていますので、こちらからご覧ください。

YouTube

この記事で実装するアプリ

ここで扱うサンプルプロジェクトは、ヘルプデスクチームが提供するアプリを想定したサンプルです。シンプルではありつつも、Slack プラットフォームの最新の機能をうまく活用しているので、これをベースに拡張することで実際に使えるアプリに進化させることができるでしょう。

以下のアニメーションで、どのようなアプリなのかをみてみましょう。

ショートカットでモーダルを起動

エンドユーザーは、Slack の最下部の入力ボックスからショットカットを起動し、モーダルウィンドウを開くことができます。

複数画面を行き来できるモーダル

エンドユーザーは、このモーダル上でまずカテゴリを選択します。ユーザーがカテゴリを選択すると、モーダルは即座に自分自身の表示状態を切り替えて、選択されたカテゴリに対応した入力フォームを表示します。「カテゴリ選択に戻る」ボタンを押すと、一つ前の画面に戻ることもできます。

カスタムの入力チェック

このモーダルを送信すると、Slack の UI が標準で持っている必須入力チェックなどに加えて、サーバーサイドがカスタムの入力チェック処理を行います。ここでは件名の桁数と希望納品日が未来日かどうかのチェックをしています。

通知を送信

データ送信を受け付けたら、ヘルプデスクチームのチャンネルや関係者の DM にメッセージを送信することができます。

ホームタブを更新

また、ホームタブというユーザーごとにカスタムの情報を表示できるエリアに、このユーザーのこれまでの申請履歴を表示しています。

使われている Slack プラットフォームの機能

このアプリは以下のような Slack プラットフォームの最新の機能を使って実装されています。

  • ブロックキット: ブロックキットはリッチなユーザーインターフェースを実装するための Slack の UI フレームワークです。Slack アプリの開発者の視点で見るとブロックキットは予め決められた JSON のデータ構造です。アプリ側がこれに従うことで Slack がデスクトップ・モバイルに最適な形でレンダリングします。
  • ショートカット: グローバルショートカットは Slack のどこからでも呼び出せる処理です。ユーザーは、最下部のメッセージ投稿 UI、または、最上部の検索メニューから呼び出すことができます。
  • モーダル: モーダルはブロックキットを使って構成するポップアップウィンドウです。ユーザーから入力内容を収集したり、情報を整理された形で表示したりするために利用されます。また、複数のステップの遷移を実装したり、複数モーダルを重ねたりすることもできます。
  • ホームタブ: ユーザーごとに個別の情報を表示するためのエリアです。自分のタスクや予定を表示したり、ダッシュボードのようなものを表示するために利用されます。

Bolt for JavaScript

Bolt for JavaScript (ボルト) は、公式のフルスタックな Slack アプリ開発フレームワークです。全ての新しいプラットフォーム機能がサポートされており、また Slack アプリで必要となる非機能要件に予めフレームワーク側で対応しています:

  • Slack からのリクエストを検証: Slack アプリのエンドポイントは公開された URL である必要があるため、受信したリクエストが本当に Slack からのリクエストかどうかを検証することはセキュリティのために重要です。Bolt を使うと Signing Secret の値を設定しておくだけで、この検証が自動的に有効になります。
  • リクエストを適切に分岐: Slack からのリクエストを適切なリスナー関数に実行させます。Web アプリフレームワークのルーティング機能のようなインターフェースで簡単にこの分岐を設定することができます。
  • ペイロードの形式の差異を吸収: 歴史的経緯から Slack API のペイロードの形式は機能によって若干異なりますが、Bolt はそれらを適切にパーズした上で、その内容をミドルウェアやリスナー関数に統一的な形式で引き渡します。
  • 無限ループに陥らないように予め制御: イベント API を使っていると、ボットユーザー自身の発言に再度反応して無限ループしてしまうという失敗がありがちですが、Bolt を使っていると予めループしないよう制御してくれます。
  • 複数ワークスペース対応: 開発用ワークスペース以外にもインストールできるアプリとして提供するために必要な OAuth フローの実装や、各リクエストごとに適切なトークンを選択するロジックを簡単に実装できる仕組みを提供しています。

このように Bolt を使うことで、アプリの本質的なロジック以外の様々なことへの対応がシンプルになります。

このアプリを実装するための全ステップ

プロジェクトをセットアップする

git clone git@github.com:seratch/bit-valley-2020-slack-bolt-app.git
cd bit-valley-2020-slack-bolt-app/
node --version # Node v10.13 かそれよりも新しいバージョンが必須です
npm i # 依存パッケージをインストール
code . # Visual Studio Code を使ってプロジェクトを開きます

Slack アプリの設定を作る

https://api.slack.com/apps にブラウザでアクセスして、新しい Slack アプリの設定をつくります。

このブラウザで、Slack ワークスペースにログインしているか確認し、その上で先ほどの URL から Slack アプリを作ります。

必要な Bot Token Scopes を設定

アプリを作ったら OAuth & Permissions というページに遷移し、Bot Token Scopes というセクションまでスクロールします。そして、以下の権限を追加してください。

  • commands は、新しいショートカットの追加に必要です。
  • chat:writechat.postMessage API というメッセージ投稿の API を使うために必要です。
  • chat:write.public は、任意のパブリックチャンネルにボットユーザーを招待することなく chat.postMessage API を実行するために必要です。
  • im:writeconversations.open API という新しい DM を開始するための API を使うために必要です。

ボットユーザーに適切な名前をつける

インストールする前に App Home ページで、ボットユーザーにわかりやすい名前をつけるとよいでしょう。 Basic Information の最下部でアプリのアイコンや説明をなどをカスタマイズすることもできます。

ホームタブ機能を有効にする

デフォルトではホームタブの機能は有効になっていないので、 App Home ページで有効にしておいてください。

アプリをワークスペースにインストールする

「Development Slack Workspace」で使用する限りにおいては、OAuth フローを実装しなくても OAuth アクセストークンを発行して、アプリをそのワークスペースで有効化することができます。

Install App ページから、インストールボタンをクリックし、OAuth フローを完了させてください。戻ってきた画面で Bot User OAuth Access Token として表されているアクセストークンをアプリ起動時に指定します。

もし、複数ワークスペースにインストール可能なアプリを実装することに興味があれば、Bolt for JavaScript のドキュメントを参考にしてみてください。

Bolt アプリを立ち上げる

ローカルで Bolt アプリを起動するには、以下のステップに従ってください:

  • プロジェクトのルートディレクトリに .env ファイルを作って、必要な情報を設定
  • Node.js 10.13 かそれより新しいバージョンを使っているか確認 (node --version)
  • npm install を実行して依存ライブラリをインストール
  • npm run local を実行して、アプリを起動
  • ngrok をインストールしていなければインストール - https://ngrok.com/
  • 別のターミナルで ngrok http 3000 を実行して公開エンドポイントを立ち上げる

.env を配置して Bot Token と Signing Secret を設定

SLACK_BOT_TOKEN=xoxb-111-111-xxx
SLACK_SIGNING_SECRET=xxx

ローカルアプリを起動

node --version # v10.13.0 以上
npm install
npm run local # 起動すると http://localhost:3000/slack/events でリクエストを受け付けます

ngrok を Slack からのリクエストをフォワードするために起動

ngrok をまだインストールしていなければ、ウェブサイトからダウンロードして設定します。以下のステップで、適切に設定できたか確認します。

# ローカルでアプリが立ち上がっていることを確認
curl -I -XPOST http://localhost:3000/slack/events # HTTP/1.1 401 Unauthorized が返ってくるはず

# 別のターミナルで実行
./ngrok http 3000

# ngrok の有償プランを使っているなら以下のように固定のサブドメインを指定できます
./ngrok http 3000 --subdomain {whatever-you-want}

# 再度、公開エンドポイントからリクエストをして、同じように 401 が返ってくるか確認
curl -I -XPOST https://{your random subdomain here}.ngrok.io/slack/events # HTTP/1.1 401 Unauthorized が返ってくれば OK

Request URL を設定して、ショートカットを追加

  • https://api.slack.comc/apps にアクセスしてアプリを選択
  • Interactivity & Shortcuts ページへ移動
  • Interactivy 機能が無効になっているのを有効にする
  • https://{your random subdomain here}.ngrok.io/slack/events を Request URL として設定
  • Global のショートカットを Callback ID: new-helpdesk-request で作成
  • 最下部の Save Changes ボタンを押すのを忘れずに

リスナー関数を実装する

このアプリでは以下のリスナー関数を実装しています。

小技: Block Kit のプレビューツール

ブロックキットビルダー というブラウザから使えるブロックキットのプレビューツールがあります。実際にアプリに blocks を組み込む前に、このツールを使って表示を調整すると効率的に UI を改善できるでしょう。

まとめ

  • https://api.slack.com/apps から Slack アプリの設定を行います
    • そのブラウザで Slack ワークスペースにログインしている必要があります
  • 必要な権限を設定する
    • "OAuth & Permissions" ページで Bot Token Scopes を設定します
    • Interactivity, Events Subscriptions, Home tab のような機能を有効にします
  • Development Slack Workspace にアプリをインストールします
    • "Bot User OAuth Access Token" を取得します
  • Bolt アプリをローカルで作成します
    • SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET という環境変数、または .env ファイルを設定します
    • app.start() で Web サーバープロセスを起動します
    • デフォルトでは POST http://localhost:3000/slack/events で全てのリクエストを受けます
  • 公開されたエンドポイントを設定して、Slack からのリクエストを受け付けられるようにします
    • ngrok かその他類似のソリューションを使います
  • 必要なリスナー関数を実装します
    • ngrok を使っているなら http://localhost:4040 を確認すると捗るでしょう
    • 標準出力にエラーメッセージが出ていることもあります
    • Bolt for JS のドキュメントは全て日本語化されています
  • Slack アプリ開発を楽しんでください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む