20200211のJavaScriptに関する記事は20件です。

GE〇オンラインレンタルの55円キャンペーンメールを受信したらGoogleカレンダーに登録するGoogle Apps Script

作成動機

なんとなくやってみたかった

コード

Code.gs
function createEvents(mail) {
  var calendar = CalendarApp.getDefaultCalendar();
  var title = mail['subject'];
  var startTime = new Date(mail['date']);
  var endTime = new Date(mail['date']);
  var location = '';

  if(mail['duedate']) {
    endTime = new Date(mail['duedate']);
  }

  if(mail['location']) {
    location = mail['location'];
  }

  var option = {
    description: mail['body'],
    location: location
  }

  console.log("add event detail")
  console.log("title = " + title)
  console.log("start = " + startTime)
  console.log("end = " + endTime)
  console.log("body = " + mail['body'])
  console.log("location = " + location)

  //return calendar.createEvent(title, startTime, endTime, option);
  calendar.createEvent(title, startTime, endTime);
}

function extractDueDate(target,pattern) {

  var regexp = new RegExp(pattern,'i')
  var match_result = target.match(regexp);

  if( match_result.length > 0 ) {
    return match_result
  } else {
    return "" 
  }

}

function fetchContactMail(filterPattern) {
 var now_time= Math.floor(new Date().getTime() / 1000) ;
 var time_term = now_time - ((60 * get_interval) + 3);

 var strTerms = '(is:unread after:'+ time_term + ')';

 var myThreads = GmailApp.search(strTerms);
 var myMsgs = GmailApp.getMessagesForThreads(myThreads);
 var valMsgs = [];

 for(var i = 0; i < myMsgs.length;i++){

   var contents = myMsgs[i][0].getFrom() + myMsgs[i][0].getSubject() + myMsgs[i][0].getPlainBody()

   regexp = new RegExp(filterPattern,'i')

   if(! contents.match(regexp) )
   {
     continue
   }

   var mailcontents = {
     "date": myMsgs[i][0].getDate()
     , "from" : myMsgs[i].slice(-1)[0].getFrom()
     , "subject" : myMsgs[i].slice(-1)[0].getSubject()
     , "body" : myMsgs[i].slice(-1)[0].getPlainBody()
   }
   valMsgs[i] = mailcontents
 }

 return valMsgs;
}

function main() {
 newmail = fetchContactMail("旧作全品55円")
 if(newmail.length > 0){
   for(var i = newmail.length-1; i >= 0; i--){
     //send_line(newmail[i])
     var dueDates = extractDueDate(newmail[i]['body'],"【期間限定】(.*?)年(.*?)月(.*?)日.*?\\)(.*?)まで")

     console.log(dueDates[1])
     console.log(dueDates[2])
     console.log(dueDates[3])
     console.log(dueDates[4])

     newmail[i]['duedate'] = new Date(dueDates[1] + "/" + dueDates[2] + "/" + dueDates[3] + " " + dueDates[4])

     createEvents(newmail[i])
   }
 }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptは書く順番が大切

Vue.jsを書いていて、どうしても動かずハマってしまった部分がありました。
FirebaseをWebアプリに連携しようとして、 main.js に以下のように記述していました。

<script>
import { firestorePlugin } from 'vuefire'
    import firebase from 'firebase'
    import 'firebase/firestore'

    Vue.use(firestorePlugin)

    export const db = firebase.firestore()
    export const auth = firebase.auth()

    Vue.config.productionTip = false

    firebase.initializeApp({                
      apiKey: "",
      authDomain: "",
      databaseURL: "",
      projectId: "",
      storageBucket: "",
      messagingSenderId: "",
      appId: ""
    })

</script>

scriptは上から下に向けて実行されるため、これだと db のインスタンスを作成する場合にfirebaseの認証がされていないことになります。
そのため、 db のインスタンスが作成されず、表示することができませんでした。
正しくは、 firebase.initializeAppdb のインスタンス作成より前に書き、

<script>
import { firestorePlugin } from 'vuefire'
    import firebase from 'firebase'
    import 'firebase/firestore'

    Vue.use(firestorePlugin)

   firebase.initializeApp({                
      apiKey: "",
      authDomain: "",
      databaseURL: "",
      projectId: "",
      storageBucket: "",
      messagingSenderId: "",
      appId: ""
    })

    export const db = firebase.firestore()
    export const auth = firebase.auth()

    Vue.config.productionTip = false



</script>

とすることで解決します。
またひとつ前進しました。

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

チャットアプリでメッセージを送信しようとすると

def create
@message = @group.messages.new(message_params)
if @message.save
respond_to do |format|
format.json
end
else
@messages = @group.messages.includes(:user)
flash.now[:alert] = 'メッセージを入力してください。'
render :index
end
end

一見問題なさそうなコード

解決したこと↓

@message = @group.messages.new(message_params)
if @message.save
respond_to do |format|
# format.html{ redirect_to group_messages_path(@group)}
format.json
end

保存されたメッセージをフォーマットに載せるまではよかったのだが
それを表示さっせるjson形式がうまく表示までしてくれていなかったようで
htmlのリダイレクトにて@groupにてパスをしてインデックス処理をしたら表示した!

しかし、本当の問題はそこではない・
jsのファイル(vsコード)が
message.js(削除済み)!!となっていた

原因は各機能のブランチをマージしていなかった!!

以上解決!!!

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

React hooksを試してみた

はじめに

現在進行形でReactを勉強中な状態ですが、昨年、React hooksてなんか界隈で盛り上がってた気がするなぁ、という事で、どういうものなのかキャッチアップしつつ、テキストとしても残しました。

個人的なメモレベルです。あしからず。。。
以下、公式ドキュメントを参考にしてます。
フックの導入

Componentの簡単なおさらい

Reactのコンポーネント定義には、クラスコンポーネント関数コンポーネントが存在します。

クラスコンポーネントは、state(状態)を持つことが可能で、ライフサイクルメソッドを実装できたりと、細かな描画制御に耐えうるコンポーネントを作成したいときに利用されていました。

関数コンポーネントは、Hooksを利用できるようになる前は、state(状態)を持つことが不可能で、渡された値をただ描画するだけのコンポーネントでした。(頑張れば、状態を制御することも出来そうですが。。。)

なので、Hooks登場以前は、コンポーネントが状態を持たないことが分かっている場合は、よりシンプルに実装できる関数コンポーネントで実装し、状態を持つ必要が出てきたタイミングで、クラスコンポーネントにリファクタして、という事が現場では行われていたのではないかと思います。

全機能を実現できるなら、最初からクラスコンポーネントで良いじゃん!...と一瞬思いましたが、アプリケーション開発で、クラスコンポーネントにメソッドを詰め込んでいく状況を想像すると、見通しが悪くなりそうだな...と感じます。

React Hooksとは

これまでは、渡された値をただ描画するだけだった関数コンポーネントの機能を拡張してくれるのがHooksです。(クラスコンポーネントでしかstateを管理できなかったが、Hooksの登場により、関数コンポーネントでも実現できるようになった)

関数コンポーネントとは、要は、関数なので、クラスコンポーネントよりも、シンプルにかつ、Hooksの機能により状態を持たせることも出来るようになります。

では、実際に書いてみます。

まずはuseStateというメソッドをreactからインポートします。

useStateをimport
import React, { useState } from 'react';

このuseStateは引数に初期値を与える事ができます。
useStateは何を返すのかなぁとconsole.logで確認すると、
以下の場合、数字の0と関数が入っていることが分かります。

useStateの戻り値をチェック
import React, { useState } from 'react';

const App = () => {
    const state = useState(0);
    console.log({state});
};

スクリーンショット 2020-02-11 19.53.38.png
配列に初期値と関数が入ってることが分かります。
useStateは常に二つの要素を返します。

上記を踏まえて、useStateの記法を確認しましょう。
ES6の分割代入を使って、それぞれの要素を受け取ります。
1つ目は初期値、二つ目は初期値を操作する関数を受け取ります。

useStateの記法
//もし、sizeだったらsetSizeと書きます。これは慣習的なものです。
const [count, setCount] = useState(0);

これで関数コンポーネントでcountのstateを操作する準備が出来ました。
以下、定番のカウントコンポーネントです。

setCountは、クラスコンポーネントでおなじみのsetStateと同じように、
値の状態を変更する事によって、自身のコンポーネントの再描画を指示します。
(ここではクリックイベントで状態を変えて、再描画させています)

reactのチュートリアルで定番のカウントコンポーネントで検証
import React, { useState } from 'react';

const App = () => {

    const [count, setCount] = useState(0);

    const increment = () => setCount(count + 1); 
    const decrement = () => setCount(count - 1);

    //setCountの引数には関数も与えることができる
    //setCountの引数には現時点の状態が返ってくる。
    //その値に基づいて、複雑な処理をしたいという要件で使えるテクニック
    const increment2 = () => setCount((previousCount) => previousCount + 1)

    return(
        <>
            <h1>count: { count }</h1>
            <button onClick={ increment }>プラス1</button>
            <button onClick={ increment2 }>プラス1</button>
            <button onClick={ decrement }>マイナス1</button>
        </>
    )
}

export default App;

所感

今回、ホントに基本的な部分を試してみただけですが、useEffectなども便利そうです。
徐々にReactも面白くなってきたかも。

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

初心者によるプログラミング学習ログ 236日目

100日チャレンジの236日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

236日目は

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

はじめてのVue.js

はじめてこういう記事を書くのでお手柔らかにお願いします!!!

今回はVue.jsを使ってHelloWorldの出力までしていきましょう!!
今後回数を重ねてもっと深堀りしていく記事を書いていきますのでよろしくお願いします!

1. Vue.jsを使ってみよう!

Vue.jsを使うにあたって、scriptタグでライブラリを読み込みます。

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

これでVue.jsを使用することができるようになったので、次に進みます!

2. インスタンスを作ろう!

Vueは、Vue関数でVueインスタンスを作成することによって起動されます、これを実行するためには?
JavaScriptの特性として、new演算子を使えばインスタンスを作ることができますのでやってみましょう!

インスタンスが分からないという方はこちら見れば理解できるかも・・・?→https://wa3.i-3-i.info/word1118.html

var vm = new Vue({

})

これで準備ができました!

3.データを作ろう!

先ほどのインスタンス、あれを作る時に引数にオプションを渡すことでデータを定義することができます!

ここでは試しにお決まりの、Hello World!が入ったHelloWorldって名前のデータを定義してみましょう!

var vm = new Vue({
  data:{ HelloWorld: 'Hello World!'}
})

こちらdataオプションというものを使って、インスタンスの中のデータを定義しました!
ほぼ主要のオプションだと思うので是非覚えてください!
data: {プロパティ名: 値}

4.HTMLテンプレートを作ってみよう!

では早速先ほどHelloWorldに格納したHello World!を表示するための準備をしましょう!
その為には、HTMLテンプレートを作る必要があります。
Vue.jsでは({{ }}) ←を使ってインスタンスのデータを参照することができます。
では実際にやってみましょう!

index.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <div id="test">
        <p>{{HelloWorld}}</p>
    </div>
    <script>
        var vm = new Vue({
            data: {
                HelloWorld: 'Hello world!'
            }
        });
    </script>

これでひとまずHTMLテンプレートの作成は終了です。
次にテンプレートとインスタンスを紐付けしていく方法を紹介します!

5.テンプレートとインスタンスを紐付けよう!

先ほどの順序でやったのに、あれ?HelloWorld!表示されないんだけど・・・ってなったと思います。
それは何故かというとテンプレートとインスタンスがくっついてなかったからなんです!バグやミスではありません!

そもそも、Vueの処理って、Vインスタンスを作ってどの要素と繋げるか(このことをマウントと呼ぶ)をしないと始まりません。
このマウントするためのオプションがel(elementの略)で指定した要素がマウントの対象になるわけです。
elを使ってHelloWorld!を表示させていきましょう~

var vm = new Vue({
            el: '#test',
            data: {
                HelloWorld: 'Hello world!'
            }
        });

このようにしたらブラウザを更新して確認してみてください!
HelloWorld!と表示されているはずです。

またこのelオプションはCSS、セレクタ、DOM要素を指定することができます。今回はidのtestを持つ要素を指定しました!

6.最後に

ここまでお疲れさまでした!
拙いかもしれませんが、できる限り分かりやすいように書いてみました!
こうして自分でアウトプットすることで頭に残る感覚がありますね・・・!
これを機にこれからもQiitaの執筆をしていきたいと思います´▽`

ここまで読んでいただきありがとうございました!:relaxed:

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

javascriptで使えそうな自分用の関数をまとめてみた。

はじめに

こちらはネットに落ちているものや自分が学習したものを改良して作ったものになります。
至らないコードもありますがご了承ください。

配列から要素を排除する関数

function unique(array) {
  return array.reduce((prev, number) => {
    if (prev.find(check_num => check_num === number) === undefined) {
      prev.push(number);
    }
    return prev;
  }, []);
}
const duplicationArray = [1,2,3,3,4,4,5,7,6,2,6]; 
console.log(unique(duplicationArray));
// [1, 2, 3, 4, 5, 7, 6] 

流れをざっくりと説明

prevの中に配列の要素が見当たらなければprevにpush。
prevに入っている値と同じものがあったらpushせずにそのままprevを返す

2重配列からオブジェクトの配列に変換

function ObjInArray(ArrayInArray, callback) {
  return ArrayInArray.map(callback);
}
const points = [
  [4, 5],
  [10, 1],
  [20, 5]
];
pointsArray = ObjInArray(points, ([x, y]) => { return {x, y};})
console.log(pointsArray);
// 0: {x: 4, y: 5}
// 1: {x: 10, y: 1}
// 2: {x: 20, y: 5}

流れをざっくりと説明

ぱっとみただけでは難しいと思われる。
アロー関数、分割代入。オブジェクトリテラルの機能を使って省略して記述しています。
省略しないとcallback関数の中身を展開すると下記のようになります。
分かりやすさでいうとこちらの方がわかると思います。

追記(2020/2.11)

@ttatsfさんにレビューをしてもらいました!
より良いコードがコメントにありますのでご確認ください!!

pointsArray = ObjInArray(points, function(value) { 
                const x = value[0];
                const y = value[1];
                return {x: x, y: y};
              });

console.log(pointsArray);
// 0: {x: 4, y: 5}
// 1: {x: 10, y: 1}
// 2: {x: 20, y: 5}

追記(2020/2.11)

こちらも@ttatsfさんにレビューをしてもらいました!
ありがたい。。コメントをご確認ください!!

条件に指定した要素以外で配列を新しく作る

function reject(array, callback){
  return array.filter(value => !callback(value));
}

const nums = [10, 20, 15, 40];
console.log(reject(nums, value => value < 20))
// [20, 40]

const foods = ["", "野菜", "白ご飯", "デザート"];
console.log(reject(foods, value => value === "デザート"))
// ["肉", "野菜", "白ご飯"]

流れをざっくりと説明

filterでは配列要素を繰り返す中で条件に一致した『true』となるもので新しく配列を作成するのですが、そのコールバック関数の真偽値を『!』でひっくり返したことで『false』となる要素で新しい配列を構成するようにしています。

文字列型にして逆順にして返すメソッドを追加

function reverseString(str) {
  return str.split("").reverse().join("");
}

const name = "tanaka";
console.log(reverseString(name))
// akanat

流れをざっくりと説明

reverse()を使うために一旦配列にする。
reverse()で反転させる。
そしてjoinで文字列にして結合させる。

最後に

色々まとめてみました。
もっとこう書くべきとか、実務ではこんなことを使うよというのがあれば是非教えてください。
よろしくお願いいたします。

追記(2020/2.11)

コメントにいただいたObjInArrayの処理をアロー関数から通常の処理に戻して自分で内部処理がわかるようにメモとして残しています。。

function labelValues(...labels){
  return function(xs){
    return xs.map(function(e){
      return labels.reduce(function(acc, label, i) {
        return ({...acc,[label]: e[i]})
      }, {})
    });
  }
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

で、結局パスパラメータ、クエリパラメータ、リクエストボディってどう違ってどう作るのが正しいの?

事の発端

基本フロントのお仕事をしているのですが、RESTfulAPIの設計をしてね、と言われてRESTfulAPIについて色々調べることがありました。
パスパラメータ、クエリパラメータ、リクエストボディの使い分けについてヘ〜〜〜と思ったので、その結果を備忘として残したいと思います。
(かなり基礎的な情報なので、初心者向けです。)

この記事で書くこと

  • パスパラメータ、クエリパラメータ、リクエストボディってHTTPリクエストで言うどれのこと?
  • パスパラメータ、クエリパラメータ、リクエストボディにはどういう情報を持たせればいいの?

この記事で書かないこと(この記事を読む上での前提知識)

ここでいう大体OKとは、この記事を読むにあたって大体OKという意味であり、
アーキテクチャ理解を指しているわけではないので悪しからず。

パスパラメータ、クエリパラメータ、リクエストボディってどれのこと?

まずパスパラメータ、クエリパラメータ、リクエストボディとは何かについて書いていきます。

パスパラメータ(Pathparameter),クエリパラメータ(queryparameter)

URIで送るものがパスパラメータとクエリパラメータです。

https://example.com/pathparameter/{pathparameter}?queryparameter1=hogehoge&queryparameter2=fugafuga

例を見て貰えばわかるのですが、
URIでドメインの後、?の前に来るものがパスパラメータです。
そして、?の後に来るのがクエリパラメータです。

リクエストボディ(requestBody)

URIではなく、JSONで送るものです。

{
  hoge_name: fugafuga,
  description: hogefugahoge,
}

例えば、こんな感じになります。

パスパラメータ、クエリパラメータ、リクエストボディにはどういう情報を持たせればいいの?

パスパラメータ

まず、パスパラメータなのですが、ここには 特定のリソースを識別するために必要な情報 を入れます。

例えば↓のようなuserを束ねるgroupというテーブルがあり、そこから特定のグループ(グループ1)に紐づくユーザーを取得したいとします。

groups_table

group_id group_name description
1 hoge hogeのグループです。
2 piyo piyoのグループです。

users_table

user_id user_name gruop_id description
1 hoge 1 hogeのグループに属しています。
2 fuga 1 hogeのグループに属しています。
3 piyo 1 hogeのグループに属しています。
4 inu 1 hogeのグループに属しています。
5 neko 2 piyoのグループに属しています。

この場合、groupId=1特定のリソース識別するために必要な情報 なので設計と実際に叩くAPIは以下のようになります。

設計
https://example.com/groups/{group_id}
実際に叩くAPI
https://example.com/groups/1

(エンドポイントのgroupsについては好みです。)

クエリパラメータ

次に、クエリパラメータなのですが、ここには 特定のリソースを操作して取得する際に必要な情報 を入れます。

先ほどのテーブルから、特定のグループ(グループ1)に紐づくユーザーを3件、user_idの降順で取得したいとします。

この場合、3件user_idの降順 という条件は 特定のリソースを操作して取得する際に必要な情報 なので設計と実際に叩くAPIは以下のようになります。

設計
https://example.com/groups/{group_id}?sort=boolean&limit=number
実際に叩くAPI
https://example.com/groups/1?sort=false&limit=3

(sortは昇順がfalse,降順がtrueという設定です。)
その他では、 検索、フィルタ などに関する条件がクエリパラメータとして扱われるようです。

リクエストボディ

最後に、リクエストボディなのですが、ここには 追加、更新する際の内容 を入れます。

先ほどのテーブルから、特定のグループ(グループ1)を更新したいとします。

この場合、更新する内容 という条件はまんま 追加、更新する際の内容 なので設計と実際に叩くAPIは以下のようになります。

設計(URI)
https://example.com/groups/{group_id}
設計(JSON)
{
  group_name: "string",
  group_description: "string"
}
実際に叩くAPI
https://example.com/groups/1
リクエストするJSON
{
  group_name: "hogehogehoge",
  group_description: "hogehogehogeのグループです"
}

まとめ

  • パスパラメータ
    • URIでドメインの後、?の前に来るやつ
    • 特定のリソースを識別するために必要な情報
  • クエリパラメータ
    • URIで?の後に来るやつ
    • 特定のリソース操作して取得する際に必要な情報
  • リクエストボディ
    • URIではなく、JSONで送るやつ
    • 追加、更新する際の内容

こんな感じで心がけるとわかりやすい設計ができるかもしれません。
オラーリージャパンのWebAPI theGoodPartsを読み中なので読破したらまた付け加えるかもしれません。

読んでくださってありがとうございました。
認識違いなどありましたら教えてくださると嬉しいです!

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

で、結局APIのパスパラメータ、クエリパラメータ、リクエストボディってどう違ってどう設計するが正しいの?

事の発端

基本フロントのお仕事をしているのですが、RESTfulAPIの設計をしてね、と言われてRESTfulAPIについて色々調べることがありました。
パスパラメータ、クエリパラメータ、リクエストボディの使い分けについてヘ〜〜〜と思ったので、その結果を備忘として残したいと思います。
(かなり基礎的な情報なので、初心者向けです。)

この記事で書くこと

  • パスパラメータ、クエリパラメータ、リクエストボディってHTTPリクエストで言うどれのこと?
  • パスパラメータ、クエリパラメータ、リクエストボディにはどういう情報を持たせればいいの?

この記事で書かないこと(この記事を読む上での前提知識)

ここでいう大体OKとは、この記事を読むにあたって大体OKという意味であり、
アーキテクチャ理解を指しているわけではないので悪しからず。

パスパラメータ、クエリパラメータ、リクエストボディってどれのこと?

まずパスパラメータ、クエリパラメータ、リクエストボディとは何かについて書いていきます。

パスパラメータ(Pathparameter),クエリパラメータ(queryparameter)

URIで送るものがパスパラメータとクエリパラメータです。

https://example.com/pathparameter/{pathparameter}?queryparameter1=hogehoge&queryparameter2=fugafuga

例を見て貰えばわかるのですが、
URIでドメインの後、?の前に来るものがパスパラメータです。
そして、?の後に来るのがクエリパラメータです。

リクエストボディ(requestBody)

URIではなく、JSONで送るものです。

{
  hoge_name: fugafuga,
  description: hogefugahoge,
}

例えば、こんな感じになります。

パスパラメータ、クエリパラメータ、リクエストボディにはどういう情報を持たせればいいの?

パスパラメータ

まず、パスパラメータなのですが、ここには 特定のリソースを識別するために必要な情報 を入れます。

例えば↓のようなuserを束ねるgroupというテーブルがあり、そこから特定のグループ(グループ1)に紐づくユーザーを取得したいとします。

groups_table

group_id group_name description
1 hoge hogeのグループです。
2 piyo piyoのグループです。

users_table

user_id user_name gruop_id description
1 hoge 1 hogeのグループに属しています。
2 fuga 1 hogeのグループに属しています。
3 piyo 1 hogeのグループに属しています。
4 inu 1 hogeのグループに属しています。
5 neko 2 piyoのグループに属しています。

この場合、groupId=1特定のリソース識別するために必要な情報 なので設計と実際に叩くAPIは以下のようになります。

設計
https://example.com/groups/{group_id}
実際に叩くAPI
https://example.com/groups/1

(エンドポイントのgroupsについては好みです。)

クエリパラメータ

次に、クエリパラメータなのですが、ここには 特定のリソースを操作して取得する際に必要な情報 を入れます。

先ほどのテーブルから、特定のグループ(グループ1)に紐づくユーザーを3件、user_idの降順で取得したいとします。

この場合、3件user_idの降順 という条件は 特定のリソースを操作して取得する際に必要な情報 なので設計と実際に叩くAPIは以下のようになります。

設計
https://example.com/groups/{group_id}?sort=boolean&limit=number
実際に叩くAPI
https://example.com/groups/1?sort=false&limit=3

(sortは昇順がfalse,降順がtrueという設定です。)
その他では、 検索、フィルタ などに関する条件がクエリパラメータとして扱われるようです。

リクエストボディ

最後に、リクエストボディなのですが、ここには 追加、更新する際の内容 を入れます。

先ほどのテーブルから、特定のグループ(グループ1)を更新したいとします。

この場合、更新する内容 という条件はまんま 追加、更新する際の内容 なので設計と実際に叩くAPIは以下のようになります。

設計(URI)
https://example.com/groups/{group_id}
設計(JSON)
{
  group_name: "string",
  group_description: "string"
}
実際に叩くAPI
https://example.com/groups/1
リクエストするJSON
{
  group_name: "hogehogehoge",
  group_description: "hogehogehogeのグループです"
}

まとめ

  • パスパラメータ
    • URIでドメインの後、?の前に来るやつ
    • 特定のリソースを識別するために必要な情報
  • クエリパラメータ
    • URIで?の後に来るやつ
    • 特定のリソース操作して取得する際に必要な情報
  • リクエストボディ
    • URIではなく、JSONで送るやつ
    • 追加、更新する際の内容

こんな感じで心がけるとわかりやすい設計ができるかもしれません。
オラーリージャパンのWebAPI theGoodPartsを読み中なので読破したらまた付け加えるかもしれません。

読んでくださってありがとうございました。
認識違いなどありましたら教えてくださると嬉しいです!

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

ScrollReveal+CSSアニメーションでWebページをちょっとだけオシャレにしてみる

この記事は「納期1日だけどオシャレなアニメーションもつけてイイ感じにして:innocent:」と言われた人間が、頑張って工夫したことをまとめたものです。

タイトルにあるScrollRevealとは

ScrollRevealはスクロールアニメーション系のJavascriptライブラリです。
jQuery不要、CSS不要で手軽にスクロールアニメーションを実装できます。

事前準備

まずHTMLでscrollreveal.jsを読み込みます。

HTML
<script src="/js/scrollreveal.min.js"></script>

そして、アニメーションさせたい要素に任意のclassを設定します。

HTML
<div class="anime">アニメーションさせたい要素</div>

ここではclass="anime"を使うことにしました。

やりたいこと

  • ページをスクロールすると要素がフワっと出てくる
  • 要素がフワッと出てきた後で、要素内のアイコンが動く

やりたいこと① ページをスクロールすると要素がフワっと出てくる

「事前準備」で用意したアニメーションさせたい要素に対してアニメーションを実装します。

Javascript
ScrollReveal().reveal('.anime');

1行書いただけ。お手軽です。

もう少し動きを変えたい場合はオプションも設定できます。

Javascript
ScrollReveal().reveal('.anime',{
    duration: 1000, //アニメーションの長さ
    delay: 500      //アニメーションの遅延
});

試しに動かすとこんな感じ↓


See the Pen
ScrollReveal-sample01
by tricolorebox (@tricolorebox)
on CodePen.


やりたいこと② 要素がフワッと出てきた後で、要素内のアイコンが動く

さて、こまった!

スクロールで要素が出てきた後で、アクセントとしてアイコン画像を動かしたいのですが(※イメージとしてはauトップページ)細かいアニメーションには対応していません:disappointed_relieved:

アイコンのアニメーションをよく観察すると...?

アイコンは背景画像として読み込まれているようです。
CSSアニメーションでアイコンのスプライト画像をパラパラ漫画のように1コマずつ動かしていました。これを組み合わせられないかな:thinking:

アニメーション終了に任意のclassを付与する

ここで先ほどのScrollRevealの話に戻ります。
ScrollRevealではafterRevealというオプションでコールバックを設定できます。
これにより、アニメーション後に任意の関数を実行することが可能です。
これを利用してアニメーションが終わったら、is-visibleというclassを付与します。

Javascript
ScrollReveal().reveal('.anime',{
    afterReveal: function (el){
        el.classList.add('is-visible');
    }
});

このような書き方になります。

CSSでアイコン用のアニメーションを設定する

最後にアイコン用のアニメーションを設定します。
繋がったアイコン画像を読み込んでアニメーションを設定します。
今回は1コマ96x96として、用意したのはこんな感じのイラスト↓
sample_anim.png

そしてCSSでis-visibleのclassがついた要素の子要素にアイコンのアニメーションを設定します。

HTML
<div class="anime">
    <div class="icon"></div>
    <div class="inner">アニメーションさせたい要素</div>
</div>
CSS
.icon{
    background:url(/img/sample_anim.png) no-repeat 0 0;
    width:96px;
    height:96px;
    margin:0 auto;
}
.is-visible .icon{
    animation: parapara 1s steps(8) 3;
    /*1秒間8コマのアニメーションを3回繰り返す*/
}
@keyframes parapara {
  to {
    background-position: -768px 0;
    /*アイコン幅96x8コマ=768px*/
  }
}

どうでしょうか、ちょっと可愛くなったかな...

See the Pen ScrollReveal-sample02 by tricolorebox (@tricolorebox) on CodePen.

最後に

アニメーション部分だけのまとめになりましたが、実際お仕事ではスマホ用・PC用で別々にスクロールアニメーションを設定しました。
アニメーションのタイミング等があとで調整しやすいライブラリを選べると自分も楽できますよ:v_tone2:

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

javascript 配列とオブジェクト(連想配列)の違いと使い方

javascriptの配列(Array, Set, Map)とオブジェクトが似ているけど扱い方が違って混乱したので使い方をまとめました。
リストっぽいなにかを使いたいけど使い分けがわからない人はどうぞ。

配列

基礎

・[]で囲まれているのは配列
・値をリスト状に保持する
・Array, Set, Mapがある(配列って扱いでいいのか)

Array

・単純に値をリストとして保持する
(key valueのような組み合わせはない)

var arr= []
arr = [1,3,"aaa","aaa"]
値の出し入れ
var arr= []
arr = [1,3,"aaa"]
//最後尾に追加
arr.push("bbb")  // [1,3,"aaa","bbb"]
//最初に追加
arr.push("ccc")  // ["ccc",1,3,"aaa","bbb"]
//値の取り出し
var firstElement = arr[0]  // "ccc"
var therdElement = arr[2]  // "3"

//値の数を知る
arr.length // 5
for文
var arr = [1,3,"aaa"]
for (var val of arr) {
  console.log(val); // 1,3,'aaa'
}
メソッド

よく使われるメソッドの使い方をいつくか

var arr = [1,3,6,1]

//各要素を取り出してなにかする
arr.forEach(element => console.log(element)); //elementは取り出した各要素

//各要素に対してなにかした結果を新しい配列にする
var newArr = arr.map(element => {
    //処理内容
    element + 1;
}); 
// newArr = [2,4,7,2]

//要素の中から条件に当てはまる要素を抽出して新しい配列にする
var newArr = arr.filter(element => {
    element > 5;
});
// newArr = [6]

//各要素に対して処理を行った結果を一つの値にまとめる
var num = arr.reduce((acc, curr) => { 
//arrに対してfor文を回していると考えると説明しやすい。
//accはfor文が回るたびに出力される結果(戻り値)、currはその時のarr内の要素の値
//やってることは1 + 3 + 6 + 1
    return acc + curr 
});
// num = 11

Set

・値の重複ができない。

var set= new Set();
var set= new Set([1,3,"aaa"]);
値の出し入れ
var set= new Set();
//追加
set.add(1); 
set.add(5);
set.add(5); // すでに5があるので無効
set.add(3);
// Set {1,5,3}

//値があるかを調べる
mySet.has(5); //true

//値の削除
mySet.delete(5); // 集合から 5 を削除

//値の数を知る
set.size; // 2

Setはインデックスを持たないので、Arrayのようにset[0]のような値の取り出しができない。
(なので、forで取り出すか、set.has(5)のように値があるか確かめる方法を使う)

for文
var set= new Set([1,3,"aaa"]);
for (var val of set) {
  console.log(val); // 1,3,'aaa'
}
メソッド

よく使われるメソッドの使い方をいつくか

var set= new Set([1,3,"aaa"]);
//各要素を取り出してなにかする
arr.forEach(element => console.log(element)); //elementは取り出した各要素

Array のように.map().filter()はない。

蛇足

Arrayは値の重複ができて、Setは値の重複ができないので、Arrayの中から値の重複をなくしたい場合に下記のようにする。

var arr= [1,2,4,5,5,7,3,1,4,3]
var noRepert = new Set(arr)  //Set { 1, 2, 4, 5, 7, 3 }

Map

・KeyとValueを持っている
・keyには何でも(配列でもファンクションでも文字列でも)指定できる。
・keyは重複できない。
後述のObjectと似てるが、keyがなんでもいいのが一番の違いだと思ってる。

var map = new Map();
var map = new Map([["key1", 1000],["key2", 2000]]);;
値の出し入れ
var map= new Map();
//追加
map.set("key1", "val1");
var a = "key2"
var b = "val2"
map.set(a, b);
var func = function(){console.log("hi")}
var func = "val3"

//値の取得
map.get(a) // "val2"
map.get("key2") // "val2"
map.get(function(){console.log("hi")}) //val3

//値の削除
map.delete("key2") 

//値の数を知る
map.size; // 2
for文
for (var [key, value] of map) {
  console.log(key + ' = ' + value); //
}
メソッド
var map= new Map([["key1", 1000],["key2", 2000]]);
//各要素を取り出してなにかする
map.forEach((value, key) =>{
  console.log(`[${key}] = ${value}`); //[key1] = 1000 ,[key2] = 2000
}); 

Array のように.map().filter()はない。

オブジェクト(Object)

基礎

・{}で囲まれているのはオブジェクト
・連想配列って名前で呼ばれることがある
・keyとvalueを持っている
・keyは重複できない

obj={}
obj = {
   "key1":"val1",
   "key2":"val2"
}

値の出し入れ

obj = {}
//値を入れる
obj.key1 = "val1"
obj["key2"] = "val2"
console.log(obj)  // { key1: 'val1', key2: 'val2-rewrite' }

//値を上書きする
obj["key1"]= "val1-rewrite"
obj.key2 = "val2-rewrite"
console.log(obj)  // { key1: 'val1-rewrite', key2: 'val2-rewrite' }

//値を取り出す
var str = obj.key2
console.log(str)  //val2-rewrite

//keyを削除する
delete obj.key2;
console.log(obj)  // { key1: 'val1-rewrite'}

メソッド

obj = {
   "key1":"val1",
   "key2":"val2"
}
//オブジェクトの要素数を取得
Object.keys(obj).length
//keyの一覧を取得
Object.keys(obj) //key1, key2
//valueの一覧を取得
Object.values(obj) //val1, val2
//プロパティ(key)があるかを調べる
obj.hasOwnProperty('key3') //false

for 文

オブジェクトのfor文は for(var key in obj)です。

obj = {
   "key1":"val1",
   "key2":"val2"
}
for(var key in obj){
   console.log(key) // key1, key2
   console.log(obj[key]) // val1, val2
}

蛇足

Objectは配列ではないので、.map()とか forEach()とか使えない。
そこで、Object.keys(obj).map()のようにObjectからkeyだけを配列として取り出せば配列っぽい扱いができる。

obj = {
   "key1":"val1",
   "key2":"val2"
}
Object.keys(obj).forEach(key =>{
   console.log(obj[key]) //val1 , val2
})

最後に

使い分け的にはこうなる。
単純に値をリスト保持したい
 → Array
値の重複を許さず、値を保持したい
 → Set

Key,Valueという形で値を持ちたい
 Object または Map

objectとMapはどちらもkey, valueを保持できるようなので使い分けに悩が、下記によれば
 Object→keyは Stringとsymbol(雑にいうと変数的なもの)のみ
 Map→どんな値でも(functionや配列でも)keyになる。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map

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

webpack入門して仲良くなりたい~開発環境編~

仲良くなりたい

  • 基本編
  • 開発環境編 ←今ココ!!
  • Loaders編 近日公開予定
  • Plugins編 近日公開予定

Loadersのことを書こうと思っていたんですが、開発をもっと楽にできそうなのでその設定をここにまとめます

前回のファイル構成をここに載せておきます

 webpack-friendly-basic
 ├ ─ ─ node_ modules
 ├ ─ ─ package.json
 ├ ─ ─ package-lock.json
 ├ ─ ─ dist
 │     ├ ─ ─ bundle.js
 │     └ ─ ─ index.html
 ├ ─ ─ src
 │     ├ ─ ─ js
 │     │     └ ─ ─ app.js
 │     └ ─ ─ modules
 │           ├ ─ ─ add.js
 │           ├ ─ ─ subtract.js 
 │           ├ ─ ─ multiply.js 
 │           └ ─ ─ divide.js       
 └ ─ ─ webpack.config.js

webpack-dev-server

npm install -D webpack-dev-server@3.10.3

このパッケージはwebpackを使った開発で使うと便利なやつです
これを入れることで開発がラクになるので入れます
とりあえず実行してみます

npx webpack-dev-server

image.png

実行すると画像のように、プロジェクトの実行ページのURLがあるのでアクセスすると

image.png

こんなページだと思います。
バンドルしたファイルを確認するindex.html/distにあるのでそれをクリックするとこんな感じだと思います。

image.png

これでは、なにも楽ではないです。

次はビルドしたときに自動でこのブラウザを開くようにします。

npx webpack-dev-server --open

--openをつけることで自動でブラウザの立ち上げを行います。
手間が少し減りました。

自動リビルド

次はバンドル対象のファイルに変更があった場合に自動でリビルドがかかるようにします。
そのためにwebpack.config.jsの設定を変えます。

webpack.config.js
const path = require("path");
const outputPath = `${__dirname}/dist`;

// モジュールどうやって出力するか記述します
module.exports = {
  // 実行環境設定
  mode: "development",
  // エントリー ポイントを決めます
  entry: "./src/js/app.js",
  output: {
    // どこにバンドルファイルを出力するか決まます
    path: outputPath,
    // 出力先のファイル名を決めます
    filename: "bundle.js"
  },
   // 追加設定
  devServer: {
    contentBase: outputPath
  }
};

この設定をすることで、ブラウザが開いた時の場所の設定ができます。
webpack.config.jsの変更を反映させます

npx webpack

自動ビルドする開発環境でブラウザを開きます

npx webpack-dev-server --open

ブラウザが開かれて、開発者ツールを確認して
Live Reloading enabled.と表示されていれば成功です。

image.png

この状態で、バンドルされたファイルのapp.jsmodulesのファイルの変更が自動で反映されます。
ここで注意しなければならないのは
バンドルの対象ではないindex.htmlの変更は反映されません

さらに、/distのディレクトリ構成に注意が必要でした

 dist
 ├ ─ ─ js
 │     └ ─ ─ bundle.js 
 └ ─ ─ index.html

このように、bundle.jsindex.htmlの階層が同列でない場合はなぜか効きませんでした。

 dist
 ├ ─ ─ bundle.js
 └ ─ ─ index.html

これなら効きます

package.json

最後にwebpack-dev-server --openというコマンドを短くします
package.jsonを開いてscriptsに変更を加えます

package.json
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack",
    "dev": "webpack-dev-server --open"
  }

startdevを加えました
この設定を行うことで、ターミナルでの実行時のコマンドを変えることができます。

npx webpack

npm start

npx webpack-dev-server --open

npm run dev

ラクになった

自分的にはこれで結構楽になったんじゃないかなと思います。

次こそローダーについて書きます。

何か、ご指摘があれば教えてくれると嬉しいです。

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

JointJSで組織図を書いてみる

概要

フロー図をブラウザ上でかけないか調べていて見かけたので使ってみる。

JointJS

調べてみるとJointJSというものがヒットしたので、使ってみる。

やってみたコードは以下のgithubに登録しています

docker準備

npmコマンドを使うのでdockerで node.js を用意します。

コンソール
$ docker-compose build

インストール

JointJSをインストールします。

コンソール
$ docker-compose run --rm node npm init
$ docker-compose run --rm node npm install --save jointjs 

jquerybackbone.js , loadash に依存しているようです

デモページ作成

デモページにある組織図のコードを動かしてみる。

※ ブラウザでアクセスする

src/demo/org.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>ORG-組織図</title>

    <link rel="stylesheet" type="text/css" href="../node_modules/jointjs/dist/joint.min.css">
    <style type="text/css">
        h1, p, div { padding:0; margin:0; }
        h1 { font-size:14px; }
        #paper { border:solid 1px #ccc; background-color: #eee; margin-top: 10px; }
    </style>
</head>
<body>
    <h1><a href="https://resources.jointjs.com/demos/org">ORG-組織図</a></h1>
    <p>jointjsを動かしてみる!!</p>

    <div id="paper"></div>
    <script src="../node_modules/jquery/dist/jquery.min.js"></script>
    <script src="../node_modules/lodash/lodash.min.js"></script>
    <script src="../node_modules/backbone/backbone-min.js"></script>
    <script src="../node_modules/jointjs/dist/joint.min.js"></script>
    <script>
      var graph = new joint.dia.Graph();

      var paper = new joint.dia.Paper({
        el: $('#paper'),
        width: 800,
        height: 600,
        gridSize: 1,
        model: graph,
        perpendicularLinks: true,
        restrictTranslate: true
      });

      var member = function(x, y, rank, name, image, background, textColor) {

        textColor = textColor || "#000";

        var cell = new joint.shapes.org.Member({
          position: { x: x, y: y },
          attrs: {
            '.card': { fill: background, stroke: 'none'},
            image: { 'xlink:href': '../images/'+ image, opacity: 0.7 },
            '.rank': { text: rank, fill: textColor, 'word-spacing': '-5px', 'letter-spacing': 0},
            '.name': { text: name, fill: textColor, 'font-size': 13, 'font-family': 'Arial', 'letter-spacing': 0 }
          }
        });
        graph.addCell(cell);
        return cell;
      };

      function link(source, target, breakpoints) {

        var cell = new joint.shapes.org.Arrow({
          source: { id: source.id },
          target: { id: target.id },
          vertices: breakpoints,
          attrs: {
            '.connection': {
              'fill': 'none',
              'stroke-linejoin': 'round',
              'stroke-width': '2',
              'stroke': '#4b4a67'
            }
          }

        });
        graph.addCell(cell);
        return cell;
      }

      var bart = member(300, 70, 'CEO', 'Bart Simpson', 'male.png', '#30d0c6');
      var homer = member(90, 200, 'VP Marketing', 'Homer Simpson', 'male.png', '#7c68fd', '#f1f1f1');
      var marge = member(300, 200, 'VP Sales', 'Marge Simpson', 'female.png', '#7c68fd', '#f1f1f1');
      var lisa = member(500, 200, 'VP Production' , 'Lisa Simpson', 'female.png', '#7c68fd', '#f1f1f1');
      var maggie = member(400, 350, 'Manager', 'Maggie Simpson', 'female.png', '#feb563');
      var lenny = member(190, 350, 'Manager', 'Lenny Leonard', 'male.png', '#feb563');
      var carl = member(190, 500, 'Manager', 'Carl Carlson', 'male.png', '#feb563');

      link(bart, marge, [{x: 385, y: 180}]);
      link(bart, homer, [{x: 385, y: 180}, {x: 175, y: 180}]);
      link(bart, lisa, [{x: 385, y: 180}, {x: 585, y: 180}]);
      link(homer, lenny, [{x:175 , y: 380}]);
      link(homer, carl, [{x:175 , y: 530}]);
      link(marge, maggie, [{x:385 , y: 380}]);
    </script>
</body>
</html>

動作確認

ブラウザでアクセスしてみる。
例) http://localhost:63342/jointjs-leaning/src/demo/org.html

demo.png

まとめ

カンタンに図が作れるようです。
デモを見てみる限りでは、フロー図のサンプルはあまり気に入ったものがなかったので、次はFlowyを試してみたいと思います。

参考サイト

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

戦車ラジコン:大砲の照準を作る

もう少し、戦車ラジコン をいじります。

M5Cameraで映した画像のどこかにQRコードがあればロックオンとしていましたが、今回は、GamePadの十字キーでちゃんと照準を合わせないと、ロックオンしないようにします。

こっちが照準が敵にあっていない状態。この状態ではまだ攻撃できません。
照準は青い丸です。

image.png

こっちが照準があっている状態。青四角で囲われていますので、この状態で攻撃できます。

image.png

毎度の通り、GitHubに上げておきました。
 https://github.com/poruruba/obniz_motor

以下からもページを参照できます。
 https://poruruba.github.io/obniz_motor/

仕組み

照準合わせには、DUALSHOCK 4の十字キーを使いました。
画像の再描画タイミングごとに、十字キーの押下状態と経過時間をチェックし、照準を描画しています。
そして、検出したQRコードの青四角を描画するときに、照準の位置との関係を確認し、描画するかどうかを判断します。

実装

check_direction関数で、十字キーの押下状態と経過時間のチェックを実装しています。

前回のチェック時に引き続き押下されていれば、移動フラグを立てます。
一緒に前回チェック時からの経過時間を返します。

start.js
        check_direction: function(gamepad){
            var direction = {};
            if( !this.direction.prev ){
                this.direction.prev = performance.now();
                return direction;
            }
            var now = performance.now();
            direction.diff = now - this.direction.prev;
            this.direction.prev = now;

            if( gamepad.buttons[12].pressed ){
                if( this.direction.up )
                    direction.up = true;
                else
                    this.direction.up = true;
            }else{
                this.direction.up = false;
            }
            if( gamepad.buttons[13].pressed ){
                if( this.direction.down )
                    direction.down = true;
                else
                    this.direction.down = true;
            }else{
                this.direction.down = false;
            }
            if( gamepad.buttons[14].pressed ){
                if( this.direction.left )
                    direction.left = true;
                else
                    this.direction.left = true;
            }else{
                this.direction.left = false;
            }
            if( gamepad.buttons[15].pressed ){
                if( this.direction.right )
                    direction.right = true;
                else
                    this.direction.right = true;
            }else{
                this.direction.right = false;
            }

            return direction;
        },

配列buttonと十字キーの割り当ては、DUALSHOCK 4の場合以下の通りでした。
・12:上
・13:下
・14:左
・15:右

チェック関数の呼び出し側は、押下があった方向に、現在照準位置を移動させます。
そして、照準位置を中心に円を描きます。

start.js
    var direction = this.check_direction(gamepad);
//  console.log(direction);
    if( direction.up )
        this.aiming_y -= direction.diff * AIMING_DURATION;
    if( direction.down )
        this.aiming_y += direction.diff * AIMING_DURATION;
    if( direction.left )
        this.aiming_x -= direction.diff * AIMING_DURATION;
    if( direction.right )
        this.aiming_x += direction.diff * AIMING_DURATION;

    if( this.aiming_x > this.qrcode_canvas.width)
        this.aiming_x = this.qrcode_canvas.width;
    else if( this.aiming_x < 0 )
        this.aiming_x = 0;
    if( this.aiming_y > this.qrcode_canvas.height)
        this.aiming_y = this.qrcode_canvas.height;
    else if( this.aiming_y < 0 )
        this.aiming_y = 0;

    this.qrcode_context.beginPath();
    this.qrcode_context.arc(this.aiming_x, this.aiming_y, 10, 0 * Math.PI / 180, 360 * Math.PI / 180);
    this.qrcode_context.closePath();
    this.qrcode_context.stroke();

AIMING_DURATIONは、経過時間に対する照準位置の移動量です。大きくすると早く移動します。

次は、QRコードを検出したときの青四角の描画処理です。
これまでは、QRコードを検出したときには無条件に青四角を描画していましたが、今回は、照準位置がQRコードの範囲内にないと描画されないようにします。

start.js
    var pos = code.location;
    this.qrcode_context.beginPath();
    this.qrcode_context.moveTo(pos.topLeftCorner.x, pos.topLeftCorner.y);
    this.qrcode_context.lineTo(pos.topRightCorner.x, pos.topRightCorner.y);
    this.qrcode_context.lineTo(pos.bottomRightCorner.x, pos.bottomRightCorner.y);
    this.qrcode_context.lineTo(pos.bottomLeftCorner.x, pos.bottomLeftCorner.y);
    this.qrcode_context.lineTo(pos.topLeftCorner.x, pos.topLeftCorner.y);
    this.qrcode_context.closePath();
    if( this.qrcode_context.isPointInPath(this.aiming_x, this.aiming_y) ){
        this.lockon = true;
        this.qrcode_context.stroke();
    }else{
        this.lockon = false;
    }

canvasのcontext.isPointInPath() を呼び出しているのがみそです。
指定されたX、Y座標が、直前で作ったパスの範囲内であるかどうかを確認します。

以上

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

2020年オープンソースWebクローラー10選

Webクローラーとはインターネット上に公開されているテキスト・画像・動画などの情報を自動で収集し、データベースに保管するプログラムのことです。さまざまなウWebクローラーがビッグデータのブームで重要な役割を果たし、人々がデータを簡単にスクレイピングできるようにしています。

さまざまなWebクローラーの中には、オープンソースのWebクローラーフレームワークがたくさんあります。オープンソースのWebクローラーを使用すると、ユーザーはソースコードまたはフレームワークに基づいてプログラミングでき、スクレイピング支援のリソースも提供され、データ抽出が簡単になります。この記事では、おすすめのオープンソースWebクローラーを10選紹介します。

1. Scrapy

言語: Python

Scrapyは、Pythonで最も人気のあるオープンソースのWebクローラーフレームワークでです。Webサイトからデータを効率的に抽出し、必要に応じて処理し、好みの形式(JSON、XML、CSV)で保存するのに役立ちます。ツイスト非同期ネットワークフレームワーク上に構築されており、リクエストを受け入れてより速く処理できます。Scrapyプロジェクトを作って大規模なクローリング・スクレイピングを効率的かつ柔軟に作ることができます。

特徴:
- 高速、強力
- 詳細なドキュメントがある
- コアに触れることなく新しい機能を追加できる
- コミュニティと豊富なリソースがある
- クラウド環境で実行できる

2. Heritrix

言語: JAVA

Heritrixは、拡張性が高く、JavaベースのオープンソースWebクローラーの一種で、Webアーカイブ用に設計されます。robot.txt除外ディレクティブとメタロボットタグを非常に尊重し、通常のWebサイトアクティビティを中断させる可能性のない、測定された適応ペースでデータを収集します。オペレータによるクローリングの制御と監視のために、Webブラウザでアクセス可能なWebベースのユーザーインターフェイスを提供します。

特徴:
- 交換可能なプラグ対応のモジュール
- Webベースのインターフェース
- robot.txtおよびメタロボットタグを尊重する
- 優れた拡張性

3. Web-Harvest

言語: JAVA

Web-Harvestは、Javaで作られたオープンソースのWebクローラーです。指定されたページからデータを収集できます。そのために、主にXSLT、XQuery、正規表現などの技術と技術を活用して、HTML / XMLベースのWebサイトのコンテンツを操作またはフィルタリングします。抽出機能を強化するために、Javaライブラリをカスタマイズすることで簡単に補完できます。

特徴:
- データ処理および制御フローのための強力なテキストおよびXML操作プロセッサ
- 変数を保存および使用するための変数コンテキスト
- 実際のスクリプト言語をサポートし、Webクローラーに簡単に統合できる

4. MechanicalSoup

言語: Python

MechanicalSoupは、Webサイトとのやりとりを自動化するためのPythonライブラリです。MechanicalSoupはPythonの巨人Requests (HTTPセッション用)とBeautifulSoup(ドキュメントナビゲーション用)で構築された同様のAPIを提供します。自動的Cookieを保存して送信し、リダイレクトに従い、リンクをたどり、フォームを送信することができます。データを単にスクレイピングするのではなく、人間の行動をシミュレートしようとする場合、MechanicalSoupは非常に便利です。

特徴:
- 人間の行動をシミュレートする機能
- かなり単純なWebサイトを高速でスクレイピングできる
- CSSおよびXPathセレクターをサポート

5. Apify SDK

言語: JavaScript

Apify SDKは、JavaScriptで構築された最高のWebクローラーの1つです。スケーラブルなスクレイピングライブラリにより、ヘッドレスChromeおよびPuppeteerでのデータ抽出およびWeb自動化ジョブの開発が可能になります。RequestQueueやAutoscaledPoolなどの独自の強力なツールを使用すると、複数のURLから開始して、他のページへのリンクを再帰的にたどり、それぞれシステムの最大容量でスクレイピングタスクを実行できます。

特徴:
- 大規模&高性能でスクレイピングできる
- 検出を回避するためのプロキシのプールがある
- CheerioやPuppeteerなどのNode.jsプラグインをサポート

6. Apache Nutch

言語: JAVA

Apache NutchはJavaで作られたオープンソースのWebクローラフレームワークです。高度なモジュールアーキテクチャを備えており、開発者はメディアタイプの解析、データ取得、クエリ、クラスタリング用のプラグインを作成できます。プラグ可能なモジュラーであるNutchは、カスタムの実装に拡張可能なインターフェースも提供しています。

特徴:
- 高度な拡張性
- txtルールに従う
- 活気のあるコミュニティと積極的な発展
- プラグ可能な解析、プロトコル、ストレージ、およびインデックス付け

7. Jaunt

言語: JAVA

JauntはJAVAに基づき、Webスクレイピング、Web自動化、およびJSONクエリ用に設計されています。Webスクレイピング機能、DOMへのアクセス、および各HTTP要求/応答の制御を提供する高速で超軽量のヘッドレスブラウザーを提供しますが、JavaScriptはサポートしていません。

特徴:
- 個々のHTTPリクエスト/レスポンスを処理する
- REST APIと簡単に接続できる
- HTTP、HTTPS、および基本認証をサポート
- DOMおよびJSONでのRegExクエリ対応

8. Node-crawler

言語: JavaScript

Node-crawlerは、Node.jsに基づいた強力で人気のある実稼働Webクローラーです。 Node.jsで完全に記述されており、ノンブロッキングI / Oをサポートしているため、クローラーのパイプライン操作メカニズムに非常に便利です。同時に、DOMの迅速な選択をサポートし(正規表現を書く必要はありません)、クローラー開発の効率を向上させます。

特徴:
- レート制御
- URLリクエストに優先度がある
- 構成可能なプールサイズと再試行
- サーバー側DOMおよびCheerio(デフォルト)またはJSDOMによる自動jQuery挿入

9. PySpider

言語: Python

PySpiderは、Python書かれた強力なWebクローラフレームワークです。使いやすいWeb UIと、スケジューラ、フェッチャー、プロセッサなどのコンポーネントを備えた分散アーキテクチャを備え、複数のクロールを簡単に追跡できるようになりました。MongoDBやMySQLなど、データストレージ用のさまざまなデータベースをサポートします。

特徴:
- ユーザーフレンドリーなインターフェイス
- RabbitMQ, Beanstalk, Redis, と Kombu のメッセージキュー
- 分散アーキテクチャ

10. StormCrawler

言語: JAVA

StormCrawlerは、Apache Stormを使用して分散Webクローラーを構築するためのオープンソースSDKです。このプロジェクトはApacheライセンスv2の下にあり、ほとんどがJavaで書かれた再利用可能なリソースとコンポーネントのコレクションで構成されています。取得および解析するURLがストリームとして提供される場合の使用に最適ですが、特に低遅延が必要な大規模な再帰クロールにも適したソリューションです。 .

特徴:
- 拡張性が高く、大規模な再帰的クロールに使える
- 追加のライブラリが簡単に拡張できる
- クロールの待ち時間を短縮する優れたスレッド管理

まとめ

オープンソースのWebクローラーは非常に強力で拡張可能ですが、開発者に限定されています。Octoparseのようなスクレイピングツールはたくさんあり、コードを書かなくてもデータを簡単に抽出できます。プログラミングに詳しくない場合は、これらのツールがより適切で、スクレイピングが簡単になります。

元記事:https://www.octoparse.jp/blog/10-best-open-source-web-crawler

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

ブラウザでWebcamを扱えるテストツールを作った話。キャプチャも可。

サマリ

ブラウザでアクセスするだけで、Webcamの動画ストリーミングにアクセスできるツールを作成しました。任意のタイミングでキャプチャし、PNG画像として保存することもできます。
screenShot.png

成果物

こちらにアクセスするだけで使えます。ソースコードもOSSでどうぞ。

開発モチベーション

日頃開発をしていると、机の上の状況をPC上で画像として貼り付けたくなる事が多々あります。当然スマホで撮影することが多いのですが、PC側で扱うにはクラウドサービスを経由したり、Directな無線通信やケーブルで接続したりと転送に少し手間がかかります。
そんな時Webcamが常設されている環境であればキャプチャが出来るはずですが、気軽にキャプチャできるツールが無かったので、作成した次第です。

使い方

  1. PCにWebcamを接続します。
  2. このツールを開いてください. Google Chromeを強く推奨します。
  3. 自動的に以下のダイアログがでてくるので、カメラを使うことを許可してください。(「許可」を押します。)
    permission.png
  4. 動画のストリーミングが始まった後にCAPTUREボタンを押すと、その時点の画像データがPNGファイルとして即ダウンロードされます。

SWのポイント

HTML5 ビデオストリーミングの開始

10数行で実現出来る。いい世の中になりまりました。

index.html
<video autoplay="true" id="video"></video>
index.js
// On Streaming
const startStreamingVideo = () => {
  const video = document.querySelector( "#video" );
  if( navigator.mediaDevices.getUserMedia ){
    navigator.mediaDevices.getUserMedia( { video: true } )
    .then( ( stream ) => {
        video.srcObject = stream;
    } );
  }
}
startStreamingVideo();

静止画のキャプチャ

少しトリッキーな事をしています。
CAPUTREボタンが押されたら、見えないcanvasにその時点のデータを描画します。その後canvasの内容をDataURL形式に変換しlinkと紐付け、ダウンロード可能にした状態で最後にlinkclick()します。
このあたりは、こちらの記事を参考にさせていただきました。

index.html
<div id="hiddenContainer">
  <a id="hiddenLink"></a>
  <canvas id="hiddenCanvas" width="500" height="375"></canvas>
</div>
index.js
const btCapture = document.getElementById( 'btCapture' );

btCapture.addEventListener( 'click', () => {

  // Capture: draw to hidden canvas
  const hiddenCanvas = document.getElementById( 'hiddenCanvas' );
  const ctx = hiddenCanvas.getContext('2d');
  const WIDTH = 500;
  const HEIGHT = 375;
  ctx.drawImage( video, 0, 0, WIDTH, HEIGHT );

  // Download: load DataURL and convert to png
  const link = document.getElementById( 'hiddenLink' );
  link.href = hiddenCanvas.toDataURL();
  // document.getElementById('hiddenCanvas').src = hiddenCanvas.toDataURL();
  link.download = getYYYYMMDD_hhmmss( true ) + ".png";
  link.click();

});

btCapture.disabled = false;

JavascriptでYYYYMMDD_hhmmss

なんか毎回こんな関数をつくっているような気がしますが、今回は下記の通りで実装。
こちらを参考にさせていただきました。

index.js
const getYYYYMMDD_hhmmss = ( isNeedUS ) => {

  const now = new Date();
  let retVal = '';

  // YYMMDD
  retVal += now.getFullYear();
  retVal += padZero2Digit( now.getMonth() + 1 );
  retVal += padZero2Digit( now.getDate() );

  if( isNeedUS ){ retVal += '_'; }

  // hhmmss
  retVal += padZero2Digit( now.getHours() );
  retVal += padZero2Digit( now.getMinutes() );
  retVal += padZero2Digit( now.getSeconds() );

  return retVal;

}

// padding function
const padZero2Digit = ( num ) => {
  return ( num < 10 ? "0" : "" ) + num;
}

所感と考察

  • Videoデータの取り扱い、楽になったものだなぁ。
  • Captureした後のデータサイズは決め打ちにしてしまっているのですが、videoのMAX解像度を知ることができると画像もキレイに保存できて良いなと。ただ、ちょっと調べたくらいでは出来なかったので今回は断念しています。
  • 今回の本筋とは関係ないのですが、本ツールはVSCodeのLiveServerを使って実装しました。めちゃめちゃ楽でQoD爆上がり。詳しくはこちらの記事をどうぞ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue composition-api の テスタビリティを考える

はじめに

Vue composition-api を触っている人も多くいると思うが、これの利用方法についてテスト容易性の観点から考える。

View と ロジックを分離するからテストしやすい

単一ファイルコンポーネント (SFC) には、コンポーネントの表示に関するTemplete (HTML) と CSS そして、ロジックに関する Script (Javascript) が含まれている。
これは、興味を1ファイルに閉じ込めることができるため、直感的にわかりやすく、好きな人が多いと思う。

その一方で、ロジック部分をテストするだけでも vue-test-utils でコンポーネントをマウントするか、this コンテキストを明示して method をコールする必要があった。

import options from "./test-target.vue"

/**
 * this.count++ するようなメソッドの試験
 */
const state1 = {
  count: 1
}
options.methods.increment.bind(state1).call()

厳密には、メソッドなどのロジックは別ファイルに分けることができたが、リアクティブな値は this から値を参照する必要があった。

一方で composition-api はリアクティブな値を含めてコンポーネントと独立してロジックを記述することができる。

import { ref, computed } from "@vue/composition-api"
export default (initValue = 0)=> {
  const _count = ref(initValue)
  const count = computed(()=> _count)
  const increment = ()=> _count.value++
  return { count, increment }
}

テストもしやすい

テスト例
import composition from "./composition"
describe("test", ()=> {
   test("increment", ()=> {
      const {count, increment} = composition(100)
      expect(count.value).toBe(100)
      increment()
      expect(count.value).toBe(101)
   })
})

テスト容易性を高めるための提案

ここまでを踏まえて、テスト容易性を高める提案を2つする。

1. Vue コンポーネントでは依存性の注入のみを行う

composition は setup メソッドで実行して、そこからコンポーネントで使う値を return するが、この setup メソッドには原則としてロジックを書かないことにする。
これによって、もともとテストしにくい Vue コンポーネントにロジックを書かないようになる。
更に依存関係をコンポーネントで解決し、必要な値は composition に対して引数として注入するようになる。
これによってさらに composition のテスト容易性が高まる。

<template>
  <div>Hoge</div>
</template>
<script>
import { inject } from "@vue/composition-api"
import composition from "./composition"
export default {
  props: {
    foo: {
      type: String
    }
  },
  setup(props, { emit }){
     const state = inject('state')
     // props, inject や emit などを解決して注入
     return composition(props, emit, state)
  }
}
</script>

テストをするときは、モックオブジェクトなどを注入すればいい

テスト例
import composition from "./composition"
describe("test", ()=> {
   test("composition", ()=> {
      const mockProps = { foo: "foo" }
      const mockEmit = jest.fn()
      const mockState = { state: "value" }
      const cmp = composition(mockProps, mockEmit, mockState)
      // テストの記述...
   })
})

2. ライフサイクル・ウォッチャー 登録関数も注入する

ライフサイクル登録関数は @vue/composition-api から公開されている。
だが、ライフサイクル登録関数は setup 内で実行される必要があるため、これを composition で使用すると composition 単体で試験するのが難しくなる。

なので、あえて Vue コンポーネントから注入する。

composition例
export default (onMounted)=> {
  const count = ref(0)
  // initialize
  onMounted(()=> count.value = 1)
  return { count }
}

テストするときはモックを注入する

test例
import composition from "./composition"
describe("test", ()=> {
   test("onMounted", ()=> {
      const onMounted = jest.fn()
      const { count } = composition(onMounted)
      expect(count.value).toBe(0)
      onMounted.mock.calls[0][0]()
      expect(count.value).toBe(1)
   })
})

ウォッチャー登録関数はライフサイクル登録関数と違い、 setup で実行する必要はないが、コールバックの処理についてはテストが難しくなることが多いので、登録関数を外から注入する。

composition例
export default (watch)=> {
  const count = ref(0)
  const count2 = ref(0)
  const  = watch(()=> count.value, (newVal)=> {
    count2.value = newVal * 2
    stopWatch()
  })
  const increment = ()=> count.value++
  return { increment, count, count2 }
}

watch 停止関数を含めてモックする

test例
import composition from "./composition"
describe("test", ()=> {
   test("watch", ()=> {
      const watchMock = jest.fn()
      const watchStopMock = jest.fn()
      mock.mockReturnValue(watchStopMock);
      const { count, count2 } = composition(watchMock)
      expect(count2.value).toBe(0)
      // watch コールバックを実行
      watchMock.mock.calls[0][1](3)
      // 実行内容の検証
      expect(watchStopMock).toHaveBeenCalled();
      expect(count2.value).toBe(6)
   })
})

ところで Component の試験はどうするの?

そもそも、template に宣言的に記述してあるイベントハンドラやディレクティブに対してどれほど試験が必要でしょう?
レビューだけでは不十分でしょうか?

仮に、テンプレートの内部に値の変換などのロジックが含まれているようならそれを composition に移すべきです。

その上で、表示に対しての検証が必要であれば、それは Storybook などで確認すれば良いと思います。
変更の検知が必要なら、snapshot テストなどをすれば良いと思います。

テストツールとしての Storybook の利用については以下に考えを記述しています。
https://qiita.com/sterashima78/items/8db32368289e4859480b

更にその先の試験となれば Cypress などで E2E の試験をすることになると思います。

まとめ

composition のテスト容易性を高めたければ、 reactive value の生成とその変更関数のみを composition 内で実装し、それ以外は外から注入するようにしよう。

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

webpack入門して仲良くなりたい~基本編~

仲良くなりたい

開発をしていて、見て見ぬふりをしていたwebpack
仲良くなるために何者なのか知ります。

At its core, webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles.

引用: https://webpack.js.org/concepts/

英語はわかめなのでGoogleに頼みます。

・・・

要するに、仲良くなると素敵な人です。

webpackってなに

モジュールバンドラ

webpackはモジュールバンドラとしての役割を果たします。
モジュールはアプリケーション内のファイル(jsファイルとかcssファイルとか)
バンドラはまとめる人

JavaScriptアプリケーション内には多くのモジュールから成り立っています。それをバラバラに人間が管理してるとめんどくさいのでwebpackのようなモジュールバンドラが活躍します。素敵です。

利点

  • リクエスト数の抑制
  • ファイルの依存関係を考えなくていい

この2つが大きな利点として考えられます。
この利点を持つため気兼ねなくファイル分割を行うことができます。
モジュールの再利用性保守性テストの易化を高めることに集中できます。

リクエスト数の抑制

モジュールをまとめることの利点はブラウザに描画するときのリクエストの数が減ることでパフォーマンスの向上を助けます。

料理で調味料を入れるときに塩や砂糖を1粒ずつ、しょうゆを1滴ずつ入れたくない

まとめたほうが効率がいい!!

ファイルの依存関係

ファイル間の依存関係を考えなくていいってことはどんな順番でファイルを読み込ませるのか考えなくてよくなります。

どの順番でscriptタグ埋め込むんだ??

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="js/W.js"></script>
  <script src="js/K.js"></script>
  <script src="js/B.js"></script>
  <script src="js/C.js"></script>
  <script src="js/P.js"></script>
  <script src="js/A.js"></script>
  <script src="js/E.js"></script>
</body>
</html>

この作業は控えめに言って地獄です…
それを解決してくれる!
依存関係を把握してくれるのがwebpack!!

遊ぶ準備

使うもの

  • npm
  • webpack
  • webpack-cli
npm init -y
npm install --save-dev webpack@4.41.5 webpack-cli@3.3.10 

バージョンは最新を使いますが、一応バージョンを指定します
webpackは開発の時に必要なものなので--save-devで入れます

基本ファイル構成

 webpack-friendly-basic
 ├ ─ ─ node_ modules
 ├ ─ ─ package.json
 ├ ─ ─ package-lock.json
 ├ ─ ─ dist
 │     ├ ─ ─ bundle.js           //アウトプット
 │     └ ─ ─ index.html
 ├ ─ ─ src
 │     ├ ─ ─ js
 │     │     └ ─ ─ app.js        //エントリーポイント 
 │     └ ─ ─ modules
 │           ├ ─ ─ add.js
 │           ├ ─ ─ subtract.js 
 │           ├ ─ ─ multiply.js 
 │           └ ─ ─ divide.js       
 └ ─ ─ webpack.config.js         //webpack設定ファイル

webpack.config.js

webpackの設定を行うファイルです。

webpack.config.js
const path = require("path");
const outputPath = `${__dirname}/dist`;

// モジュールどうやって出力するか記述します
module.exports = {
  // 実行環境設定
  mode: "development",
  // エントリー ポイントを決めます
  entry: "./src/js/app.js",
  output: {
    // どこにバンドルファイルを出力するか決まます
    path: outputPath,
    // 出力先のファイル名を決めます
    filename: "bundle.js"
  }
};

mode

このプロパティで使うのは下記の2つだと思います

  • development
    • 開発するときはこれ。デバッグしやすくてリロードが早かったりするので
  • production
    • 本番環境ではこれ。出力ファイルを圧縮したり、不要なコード(console系とか)削除などできる

noneというプロパティもありますが、いまいち使い道がわかりませんでした…

entry

ファイルの依存関係を解析するファイルの指定を行います。様々なファイルを呼び出す最初のファイル指定してください。

output

  • path
    • バンドルした結果ファイルをどのディレクトリに出力するか指定
  • filename
    • バンドルしたファイル名

バンドルに必要なファイル

modules

バンドル対象となるファイルを準備します。

add.js
export function add(a, b){
   console.log(a + b);
}
subtract.js
export function subtract(a, b){
   console.log(a - b);
}
multiply.js
export function multiply(a, b){
   console.log(a * b);
}
divide.js
export function divide(a, b){
   console.log(a / b);
}

entryファイル

app.js
import { add } from "../modules/add";
import { subtract } from "../modules/subtract";
import { multiply } from "../modules/multiply";
import { divide } from "../modules/divide";

add(1, 2);
subtract(2, 1);
multiply(3, 2);
divide(10, 2);

distディレクトリ

ここに、モジュールがバンドルされたか確認するためのhtmlファイルを用意します。
bundle.jsはbuild時に作成されるので気にしなくてよい

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <p>webpack Friendly!!</p>
    <script src="./bundle.js"></script>
  </body>
</html>

動かしてみる

buildする

buildするには以下のコマンドを実行させてください

npx webpack

npxは実行するパッケージのpathを指定しなくても勝手に探して実行してくれるみたい
以下のような画面が出たらバンドルが成功しています。

image.png

ブラウザでindex.htmlを確認してみます

image.png

chromeの開発者ツールを開いて、ネットワークを見てみると読み込まれているファイルはindex.htmlbundle.jsです。app.jsmodules配下のファイルらをバンドルしたbundle.jsが読み込まれています。

少しわかった気がする

ここまでは本当に基本的な部分だと思います。
次は、ローダーについて書きます。

何か、ご指摘があれば教えてくれると嬉しいです。

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

Knexあれこれ(雑多メモ)

はじめに(次の見出しまで飛ばしてOK)

最近仕事に対する楽しみ方を意識するようになり、今のつまらない仕事が続くのも嫌だったので、6月で辞めますと退職連絡を済ませてきた。
コーディングしてる最中はプライベートでも仕事でも楽しいので、コードをびっしり書くような会社に転職したいと思ったが、個人での活動実績はない。
とりあえず就活に繋がる作品を何か作ってみようと思い立ちこれ1本で大体何とか済ませちゃうNode.jsの勉強を1週間前に始めた。
が、DB処理があまり好きになれなかった。

適当に買った参考書に書いてたDB処理は以下のようなもの

sql.js
connection.query('SELECT * FROM hoge ', function (error, results, fields) { //取得結果に対する処理 });

新卒配属された闇プロジェクトのシステム構成がこれに近い形式で実装されていたことが大体の原因ではあるが。

便利なnpmパッケージ Knex

PHP/Laravelあたりに言語/フレームワークを変更しようかなとこっそり考えつつ、何かいいnpmパッケージはないものかと探してみた。
あった(Knex公式)
記事を書いてくれている方もいた

個人的に良いなと思った点

  • チェーン形式で処理を書ける
  • マイグレーションにも対応してる

軽く記事を流し読みるするだけでも結構便利に思えたのでリファレンス読みつつ色々試してみた。
で、ちょっと一々リファレンスを見に行くのもあれなのでちょっとメモを残そうと思いこの記事を書き始めた。正直公式リファレンスのは読みづらかった
割と参考記事とかぶってる点が多いので、多分knex回りを調べてこの記事を開いてくれた方は↑の参考記事を見ることをお勧めします。

マイグレーションのあれこれメモ(本題)

実行前準備

初期構築 マイグレーションをするためのアレコレの準備

$ knex init 

マイグレーション実行に当たる必要となる設定

module.exports = {
  // 開発環境の設定(デフォルトで参照される設定 NODE_ENVを書き換えればデフォルトではなくなる)
  development: {
    // データベースの種類
    client: 'mysql', 

    // DB接続設定
    connection: {
      host     : '127.0.0.1',
      user     : 'root',
      password : '',
      database : 'node_app'
    },
    // コネクションプールの設定
    pool: {
      min: 2,
      max: 10
    },
    // マイグレーション設定
    migrations: {
      // マイグレーションファイルの配置先(knexfile.jsからの相対)
      directory:'./db/migrations',
      // マイグレーションを管理するテーブル名 (マイグレーションの実行と同時にDBに作成される)
      tableName: 'knex_migrations'
    }
  },
  // 開発環境とは異なる環境のマイグレーション実行設定定義 (本番環境,検証環境の差別化等)
  staging: { ... },
  production: { ... },
};

マイグレーションファイルの作成

<タイムスタンプ>_<ファイル名>.jsというファイルが、migrations.directoryで設定したパス配下に配置される

$ knex migrate:make <ファイル名>

マイグレーションファイルの中身の設定

細かく解説すると助長になるので、コメント参照

20200208161057_items.js
// 後述する実行コマンドで呼び出されるメソッド
exports.up = function(knex, Promise) {
  // connection.databaseで設定しているスキーマに引数で渡したテーブルがあるかチェック
  return knex.schema.hasTable('テーブル名')
    // hasTableのチェーンメソッド 判定結果を引数existsに渡して無名関数をコールバック
    .then(function(exists) {
      if (!exists) {
           // 接続先のスキーマに指定した名前でテーブルを作成する
          return knex.schema.createTable('テーブル名', 
            // 作成したテーブルにカラムを作成する
            function(table) {
               // テーブルの要素設定 別途記載
              table.increments('id').primary();
              table.string('name', 100);
              table.integer('price');
          });
      }else{
          return new Error("The table already exists. 2");
      }
  });
};

// 後述する切り戻しコマンドで呼び出されるメソッド
exports.down = function(knex, Promise) {
  return knex.schema.hasTable('items').then(function(exists) {
      if (exists) {
          // 指定したテーブルを削除する
          return knex.schema.dropTable('items');
      }
  });
};

接続先データベース/スキーマに対して実行するメソッド各種 (使いそうな奴だけ)

function(knex, Promise) {
    // テーブル作成メソッド コールバック内でカラム設定を忘れずに
    knex.schema.createTable(tableName, callback)

    // 指定したテーブル名を変更する
    knex.schema.renameTable(from, to)

    // 指定したテーブルを削除する           
    knex.schema.dropTable(tableName)

    // 指定したテーブルが存在しないか確認する 上例のように.then()でチェーンするとスマート
    knex.schema.hasTable(tableName)

    // 指定したテーブルが存在すれば、そのテーブルを削除する. has,dropをチェーンしなくて済む
    knex.schema.dropTableIfExists(tableName)

    // 指定したテーブルに関する変更処理を実行する
    knex.schema.table(tableName, callback)
};

接続先のデータベース/スキーマが持つテーブルに対して実行するメソッド各種

createTableや、table関数のコールバック内で利用する

knex.schema.createTable('posts', function(table) {
    // 指定したカラムを削除する
    table.dropColumn(name)

    // 指定した複数のカラムを削除
    table.dropColumns(*columns)

    // 指定したカラム名を変更
    table.renameColumn(from, to)

    // オートインクリメント形式のカラムを追加
    table.increments(name)

    // 文字列型のカラムを追加 オプション引数で長さも指定できる
    table.string(name, [length])

    // 数値型のカラムを追加
    table.integer(name)
});

主キーとか外部キーとかindexとかの使い方

カラム設定の関数にチェーンする

knex.schema.createTable('posts', function(table) {
    // 主キーの設定
    table.increments(name).primary()

    // knexインスタンスがMySQLとPostgreSQLの場合のみ使える
    table.index(columns, [indexName], [indexType])

    // 外部キーの設定 table.foreign(カラム名).references('参照テーブル.カラム名')
    table.string(name)
    table.foreign(name).references(table.name) 
});

実行手順

未実行のマイグレーションファイルのupメソッドを、タイムスタンプの古い順に全ファイル実行

$ knex migrate:latest

envオプション: 実行対象の環境を指定してマイグレーションを実行する

$ knex migrate:latest --env production

直前に実行した全てのマイグレーションファイルのdownメソッドを、タイムスタンプの新しい順に実行する

Latestでバッチ実行したなら、すべてのファイルをタイムスタンプの新しい順に実行する。
Upで個別実行したなら、そのマイグレーションファイルのみを実行する

$ knex migrate:rollback

allオプション:過去に実行した全てのマイグレーションファイルのdownメソッドを実行する

$ knex migrate:rollback --all

タイムスタンプが最も古い未実行のマイグレーションファイルのUpメソッドを実行する

$ knex migrate:up 

マイグレーションファイルを指定してそのUpメソッドを実行する

$ knex migrate:up <ファイル名>.js

タイムスタンプが最も新しい実行済のマイグレーションファイルのdownメソッドを実行する

$ knex migrate:down

マイグレーションファイルを指定してそのdownメソッドを実行する

$ knex migrate:down <ファイル名>.js

マイグレーションファイルを一覧表示する

実行済みのものと未実行のものに分割してリスト表示してくれる

$ knex migrate:list
Using environment: development
Found 1 Completed Migration file/files.
20200208145923_items.js 
Found 1 Pending Migration file/files.
20200208161057_items.js 

作成日の異なるマイグレーションファイルがある場合のmigrate:latestの挙動

タイムスタンプの古い方から実行される

/project
|--migration
|  |--20200208161057_items.js
|  |--20200208161542_items.js
20200208161057_items.js
exports.up = function(knex, Promise) {
  console.log('items oldest');
}
20200208161542_items.js
exports.up = function(knex, Promise) {
  console.log('items lastest');
}
実行結果
$ knex migrate:latest
Using environment: development
items oldest
items latest

作成日の異なるマイグレーションファイルがある場合のmigrate:rollbackの挙動

タイムスタンプの新しい方から実行される

/project
|--migration
|  |--20200208161057_items.js
|  |--20200208161542_items.js
20200208161057_items.js
exports.up = function(knex, Promise) {
  console.log('items oldest');
}
20200208161542_items.js
exports.up = function(knex, Promise) {
  console.log('items lastest');
}
実行結果
$ knex migrate:rollback
Using environment: development
items latest
items oldest

オプション引数類は一例で上げてるだけになります。詳細は公式へ

migrate:latestの後にもう1回migrate:latestをした場合のmigrate:rollbackの挙動

上述した通り、migrate:rollback は直前の1回の実行を戻す
2回に分けた場合は2回実行するか、--allオプション を使う

ぼやき

結構前からあるパッケージなのにknexの日本語記事少ないってことは、
割とNode.jsを書いてる人からは不人気なのだろうか。

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

ジェネレーターで関数モナドとStateモナドを模倣してみた

ジェネレーターを DSL のように使って関数モナドと State モナドを模倣してみました。記述をそれっぽく見せることに重点を置いたため、bind や return を正確に実装したわけではありません。

関数モナド(のようなもの)

See the Pen Function Monad by 七誌 (@7shi) on CodePen.

State モナド(のようなもの)

See the Pen State Monad by 七誌 (@7shi) on CodePen.

この記事には Python 版があります。

実装

実装を並べると、関数モナドと State モナドの差分が分かりやすいです。

関数モナド State モナド
function functionMonad(g) {
  return state => {
    let it = g(), result, value;
    while (!(result = it.next(value)).done) {
      value = result.value(state);
    }
    return result.value;
  };
}
function stateMonad(g) {
  return state => {
    let it = g(), result, value;
    while (!(result = it.next(value)).done) {
      [value, state] = result.value(state);
    }
    return [result.value, state];
  };
}

関数モナドでは state は固定で value のみが更新されますが、State モナドでは両者をセットで扱い更新されます。

これを踏まえてState モナドで使う getput を見れば、挙動が分かりやすいと思います。

let get = state => [state, state];
let put = newState => oldState => [, newState];

サンプル

以前書いた次の記事から引用しました。

Haskell と JavaScript と Python を並べて比較します。

関数モナド

test に対する引数が、yield の右のラムダ式に引数として与えられます。

Haskell JavaScript Python

test = do
  a <- (+ 1)
  b <- (* 2)
  return (a, b)

main = do
  print (test 3)
  print (test 5)
実行結果
(4,6)
(6,10)
let test = functionMonad(
  function*() {
    let a = yield x => x + 1;
    let b = yield x => x * 2;
    return [a, b];
  });

log(test(3));
log(test(5));
実行結果
[4,6]
[6,10]
@function_monad
def test():
  a = yield lambda x: x + 1
  b = yield lambda x: x * 2
  return (a, b)


print(test(3))
print(test(5))
実行結果
(4, 6)
(6, 10)

State モナド

State モナドはコンテキストとして状態を持っており、呼び出す際に初期値を与えます。状態の取得は get、更新は put で行います。

Haskell JavaScript Python

test = do
  a <- get
  put (a * 2)
  b <- get
  return (a, b)


postInc = do
  x <- get
  put (x + 1)
  return x


test2 = do
  a <- postInc
  b <- postInc
  return (a, b)

main = do
  print (evalState test  3)
  print (evalState test  5)
  print (evalState test2 3)
  print (evalState test2 5)
実行結果
(3,6)
(5,10)
(3,4)
(5,6)
let test = stateMonad(
  function*() {
    let a = yield get;
    yield put(a * 2);
    let b = yield get;
    return [a, b];
  });
let postInc = stateMonad(
  function*() {
    let x = yield get;
    yield put(x + 1);
    return x;
  });
let test2 = stateMonad(
  function*() {
    let a = yield postInc;
    let b = yield postInc;
    return [a, b];
  });

log(test (3)[0]);
log(test (5)[0]);
log(test2(3)[0]);
log(test2(5)[0]);
実行結果
[3,6]
[5,10]
[3,4]
[5,6]
@state_monad
def test():
  a = yield get
  yield put(a * 2)
  b = yield get
  return (a, b)

@state_monad
def postInc():
  x = yield get
  yield put(x + 1)
  return x

@state_monad
def test2():
  a = yield postInc
  b = yield postInc
  return (a, b)


print(test (3)[0])
print(test (5)[0])
print(test2(3)[0])
print(test2(5)[0])
実行結果
(3, 6)
(5, 10)
(3, 4)
(5, 6)

リストモナド

同じ方式でリストモナドを実装できないか考えたのですが、多重ループとなる場合に変数の値を変えて同じコードを何度も実行する必要があり、実現する方法が思いつかずに断念しました。

※ もしイテレーターがコピーできれば実現できそうです。

【参考】How would we copy an iterator?

ジェネレーターの中で普通に for で多重ループを書けば同じことはできます。

Haskell JavaScript Python




test = do
  x <- [1, 2]
  y <- [3, 4]
  [x, y]



main =
  print test
実行結果
[1,3,1,4,2,3,2,4]
function listMonad(g) {
  return Array.from(g());
}
let test = listMonad(
  function*() {
    for (let x of [1, 2]) {
      for (let y of [3, 4]) {
        yield x; yield y
      }
    }
  });

log(test);
実行結果
[1,3,1,4,2,3,2,4]
def list_monad(g):
  return list(g())

@list_monad
def test():
  for x in [1, 2]:
    for y in [3, 4]:
      yield x; yield y




print(test)
実行結果
[1, 3, 1, 4, 2, 3, 2, 4]

for による方法は次の記事に書きました。

リストモナドの挙動は .flatMap で再現できます。

> [1, 2].flatMap(x => [3, 4].flatMap(y => [x, y]))
[ 1, 3, 1, 4, 2, 3, 2, 4 ]

参考

出力の log() は次の実装を使っています。

Haskell のモナドは次の記事を参考にしてください。

次の記事に触発されました。

ジェネレーターを DSL として使う発想は、co が async/await を模倣していたのにヒントを得ました。

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