TRCとopenBDを利用した新刊一覧を表示するサイトを作った
こちらのニュースを今日知りました。
数日前にアナウンスがあったようですが、見落としていました。
せっかくなので、無料かつ飽きない程度の実装で「新刊一覧」サイトを作りました。
サイトはこちらです。 https://trc-opendata-viewer.herokuapp.com/
よろしければご覧ください。
そんなアクセスされないと思いますが、無料枠なのでアクセスが多いとダメかもしれません。
サイトの概要
上記サイトにある「TRC新刊図書オープンデータ」を利用しています。
zipのtsvに書かれているISBNの一覧を元に表示しているだけです。数件ISBNがないデータがありますが、それは表示されません。
書影にはopenBDを使わせてもらっています。
よって、openBDに書影がない本は書影部分が表示されません。
(残念なことにファーストビューの本は全部ない)
検索等、凝った仕組みはありません。現状、追加開発する予定もありません。
それらを作り込もうとするとTRCのデータだけではダメで色々面倒だからです。
タイトルや書籍名の絞込実装ぐらいは出来ますが、個人的にその必要性をまだ感じていません。
(具体的な名前が分かっていればググるので、一覧でどうこうする必要がない)
zipの更新
zipは毎週更新されるようですが、現状はまだ1つしかないので自動更新は考えていません。
(ローカルのzipを読んでいます)
この辺りはどうしようか考え中です。
こちらの非公式アーカイブを利用させて頂こうかなと思っていますが、ひとまず様子見です。
https://github.com/takahashim/trc_opendata
現状の使い勝手
正直、自分でも使っていくかどうか微妙。
なんとなく書影を眺めているのは楽しいけど、リピーターになるにはもっとUIが洗練されないといけないと思う。
利用者としてページングが面倒なのだけど、インスタみたいなローディング方式にするのも微妙かなあと思ったり。どうなんだろう。
PHPのnull[0]はエラーにならないしnoticeも出ない
背景
null変数に添字アクセスするところで、落ちると思っていたら落ちなくてびっくりした。
環境
OSX 10.11.5
PHP 7.0.14 (cli) (built: Apr 1 2017 23:41:23) ( NTS )
Copyright © 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright © 1998-2016 Zend Technologies
再現
まずは普通の配列に対して、存在しない添字アクセスをするとどうなるか。
<?php $a = []; var_dump($a[1]);
上記コードを実行すると、以下のようにnoticeを出力する。
Notice: Undefined offset: 1 in /Users/kanno/workspace/sandbox/piece/2017/06/25-1036.php on line 4 PHP Notice: Undefined offset: 1 in /Users/kanno/workspace/sandbox/piece/2017/06/25-1036.php on line 4 NULL
ではnullに対して添字アクセスしてみる。
<?php var_dump(null[0]); var_dump(null[1]);
上記コードを実行するとnoticeが出ない。というかエラーにすらならない。
NULL NULL
わお…。
ちなみに勝手に配列になったりしないかどうかも確認したが、さすがにそれは大丈夫だった。
<?php $a = null; var_dump($a[1]); var_dump($a);
NULL NULL
ちなみにforeachもいけちゃうのかと思いきや、これはwarningだった。
<?php foreach (null as $x) { }
Warning: Invalid argument supplied for foreach() in /Users/kanno/workspace/sandbox/piece/2017/06/25-1036.php on line 2 PHP Warning: Invalid argument supplied for foreach() in /Users/kanno/workspace/sandbox/piece/2017/06/25-1036.php on line 2
count(null)
は0だから普通のfor文ならいけちゃうけど。
<?php for ($i=0; $i < count(null); $i++) { }
Gaucheで指定文字を指定回数繰り返す書き方
背景
Gaucheで指定した文字を指定した回数だけ繰り返したい。
Rubyでいう"a" * 5
、PHPでいうstr_repeat('a', 5)
のようなこと。
irb(main):002:0> "a" * 5 => "aaaaa"
目的は受け入れテストにて「x文字以上ならエラー」というケースでx文字を簡単に用意したかったから。
(Vimですぐ出来るけど、練習のためにGaucheのやり方を調べた)
結論
1文字の繰り返しでいいならmake-string
。
gosh> (make-string 5 #\a) "aaaaa"
文字列で繰り返したいならリストを作ってからapplyする方法っぽい。
gosh> (apply string-append (make-list 5 "Vim")) "VimVimVimVimVim"
ちなみにClojureでも似たような感じっぽい。
user=> (apply str (repeat 3 "str"))
追記: 他の書き方も教えて頂いた。
文字列ポートを使えばこんな風にもかけますね。 文字列の繰返しに限って言えば多くの場合はリストを作ってから apply の方が効率的だと思いますが。 pic.twitter.com/d4lgfSgXiE
— 齊藤敦志@仕事ください (@SaitoAtsushi) 2017年6月24日
調べ方
まずはGaucheのドキュメントをrepeat
とかcycle
とかのキーワードで探したが辿り着けず。
GaucheというよりSchemeでの書き方が分かればいいはず、ということでググっていたら以下に辿り着いた。
色々な言語での実装例があって面白かった。 Repeat a string
jQueryのonでハンドラを複数登録するのとaddEventListenerで複数登録するのは微妙に挙動が違う
背景
jQueryのonにハンドラを複数登録するのと、addEventListener
で複数登録するのは微妙に挙動が違う。
それについて調べる必要があったのでメモ。
環境
Chrome バージョン 58.0.3029.110 (64-bit)
再現
formのsubmitを押した時のハンドラを複数登録する。
- 1つ目: alert出すだけ
- 2つ目: 例外を発生
- 3つ目: submit本来の挙動を停止(結果として遷移しなくなる)
サンプルコード - addEventListener版
<!DOCTYPE html> <html lang="ja"> <head></head> <body> <form action="" id="form"> <button type="submit">submit</button> </form> <script type="text/javascript"> var form = document.getElementById('form'); form.addEventListener('submit', function() { alert(1); }); form.addEventListener('submit', function() { alert(2); throw new Error("doooon"); }); form.addEventListener('submit', function(e) { alert(3); e.preventDefault(); e.stopPropagation(); }); </script> </body> </html>
実行結果は以下のようになる。
- alert(1)が表示
- alert(2)が表示
- 例外が発生し、Webコンソールに出力される
- alert(3)が表示
- 何も遷移しない
つまり各ハンドラは独立して実行される。
サンプルコード - jQuery版
バージョンは3.2.1と2.2.4で確認した。
<!DOCTYPE html> <html lang="ja"> <head> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> </head> <body> <form action="" id="form"> <button type="submit">submit</button> </form> <script type="text/javascript"> $("#form") .on("submit", function () { alert(1); }) .on("submit", function () { alert(2); throw new Error("doooon"); }) // このハンドラは呼ばれない .on("submit", function (e) { alert(3); // 本来はreturn falseだけでいいけどテストとして一応書いておく e.preventDefault(); e.stopPropagation(); return false; }); </script> </body> </html>
実行結果は以下のようになる。
- alert(1)が表示
- alert(2)が表示
- 例外が発生しているが、Webコンソールには何も表示されていない(というか見えない)
- 遷移する
つまり3つ目のハンドラは呼ばれていない。
原因
jQueryのon
で登録するハンドラはそのままaddEventListener
に渡しているわけではなく、内部的にtype毎に1つの関数でラップしている。
このラップした関数が実際のイベント発火時に処理されるわけだが、ここでは登録されたハンドラをループして処理している。
そのため、登録したハンドラが途中で落ちると後続のハンドラは動かない。実際のハンドラ呼び出しでは例外をキャッチしていない。
またハンドラの呼び出しは以下のようになっていて、false
を返すとpreventDefault()
とstopPropagation()
を呼ぶ。
ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || handleObj.handler ).apply( matched.elem, args ); if ( ret !== undefined ) { if ( ( event.result = ret ) === false ) { event.preventDefault(); event.stopPropagation(); } }
例外が発生すると当然preventDefault
などには到達しないので、デフォルトの挙動で動く。よって、上記のサンプルではsubmitがデフォルトの通り遷移した。
ちゃんと動作確認をしないと、期待通りに動いているのか、途中で例外が起きたけどデフォルトの挙動で上手くいっているように見えたか分からない。
おわりに
LaravelでクエリビルダーやEloquentを使ったupdateの戻り値の違い
背景
Laravelアプリケーションでレコードをupdateするやり方が色々あり、戻り値の違いについて調べた。
結論
表にまとめた。
書き方 | 戻り値の型 | 更新対象がない時の戻り値 |
---|---|---|
\DB::table(‘samples’)->where($where)->update($values) | int(件数) | 0 |
\App\Sample::where($where)->update($values) | int(件数) | 0 |
\App\Sample::where($where)->first()->update($values) | bool(成否) | 実行不可 |
\App\Sample::find($id)->update($values) | bool(成否) | 実行不可 |
詳細
それぞれのupdate
呼び出し結果をvar_dump
してみる。
<?php // 初期データ \DB::table('samples')->insert([ 'id' => 1, 'name' => 'alice' ]); echo PHP_EOL; echo "レコードと差分がない値で更新する場合" . PHP_EOL; $exists_where = [ 'id' => 1 ]; $same_values = [ 'name' => 'alice' ]; // クエリビルダーで更新(変更なし) var_dump(\DB::table('samples')->where($exists_where)->update($same_values)); // Modelのwhere->updateで更新(変更なし) var_dump(\App\Sample::where($exists_where)->update($same_values)); // Modelのwhere->first->updateで更新(変更なし) -> bool(true) var_dump(\App\Sample::where($exists_where)->first()->update($same_values)); // Modelのfind->updateで更新(変更なし) -> bool(true) var_dump(\App\Sample::find($exists_where['id'])->update($same_values)); echo "レコードが存在しない場合" . PHP_EOL; $not_exists_where = [ 'id' => 999 ]; // クエリビルダーで更新(変更なし) var_dump(\DB::table('samples')->where($not_exists_where)->update($same_values)); // Modelのwhere->updateで更新(変更なし) var_dump(\App\Sample::where($not_exists_where)->update($same_values)); // Modelのfirstおよびfindはnullが返るので実行不可
実行すると冒頭の表の通り、int(件数)を返すものとbool(成否)を返すものがある。
何故違うかは、それぞれの実行クラスを見ると分かる。
<?php var_dump(get_class(\DB::table('samples')->where($exists_where))); // => string(33) "Illuminate\Database\Query\Builder" var_dump(get_class(\App\Sample::where($exists_where))); // => string(36) "Illuminate\Database\Eloquent\Builder" var_dump(get_class(\App\Sample::where($exists_where)->first())); // => string(10) "App\Sample" var_dump(get_class(\App\Sample::find($exists_where['id']))); // => string(10) "App\Sample"
つまりDB::table
経由だろうとモデル経由だろうと、firstなりfindなりで一旦モデルを取得したかどうかで処理が異なる。
なおIlluminate\Database\Eloquent\Builder
は内部でIlluminate\Database\Query\Builder
に委譲している。
- 余談1: 試していないが
\App\Sample::where($where)->update($values)
の場合は各種フックが走らないはず - 余談2:
\DB::table('samples')->where($where)->first()
はモデルではなくstdClassが返る
Illuminate\Database\Query\Builder
このクラスのupdate
は最終的にPDOStatement::rowCountを返す。
なので常に更新件数が返る(はず)。
<?php /** * Run an SQL statement and get the number of rows affected. * * @param string $query * @param array $bindings * @return int */ public function affectingStatement($query, $bindings = []) { return $this->run($query, $bindings, function ($query, $bindings) { if ($this->pretending()) { return 0; } // For update or delete statements, we want to get the number of rows affected // by the statement and return that back to the developer. We'll first need // to execute the statement and then we'll use PDO to fetch the affected. $statement = $this->getPdo()->prepare($query); $this->bindValues($statement, $this->prepareBindings($bindings)); $statement->execute(); return $statement->rowCount(); }); }
Illuminate\Database\Eloquent\Model
App\Sample
が継承しているModelのupdate
は以下のようになっている。
<?php /** * Update the model in the database. * * @param array $attributes * @param array $options * @return bool */ public function update(array $attributes = [], array $options = []) { if (! $this->exists) { return false; } return $this->fill($attributes)->save($options); }
save
はEloquentのフックの処理を色々したりするが、最終的には必ずboolを返す。
件数を返すことはないように見える。
なお$this->exists
で見ているexists
はレコードをチェックしているわけではなく、論理上のフラグである。
モデルのコンストラクタで指定するか、insertに成功したりするとフラグが立つ。