webmockすごい

エラー出ても使い方を教えてくれて優しい

$ ruby webmock.rb
/Users/(ユーザー名)/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/webmock-1.20.4/lib/webmock/http_lib_adapters/net_http.rb:114:in `request': Real HTTP connections are disabled. Unregistered request: GET http://www.example.com/ with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'} (WebMock::NetConnectNotAllowedError)

You can stub this request with the following snippet:

stub_request(:get, "http://www.example.com/").
  with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
  to_return(:status => 200, :body => "", :headers => {})

registered request stubs:

stub_request(:any, "http://www.example.com/").
  with(:body => "abc",
       :headers => {'Content-Length'=>'3'})

============================================================
  from /Users/(ユーザー名)/.rbenv/versions/2.1.5/lib/ruby/2.1.0/net/http.rb:1280:in `request_get'
   from /Users/(ユーザー名)/.rbenv/versions/2.1.5/lib/ruby/2.1.0/net/http.rb:474:in `block in get_response'
  from /Users/(ユーザー名)/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/webmock-1.20.4/lib/webmock/http_lib_adapters/net_http.rb:123:in `start_without_connect'
    from /Users/(ユーザー名)/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/webmock-1.20.4/lib/webmock/http_lib_adapters/net_http.rb:150:in `start'
  from /Users/(ユーザー名)/.rbenv/versions/2.1.5/lib/ruby/2.1.0/net/http.rb:473:in `get_response'
   from /Users/(ユーザー名)/.rbenv/versions/2.1.5/lib/ruby/2.1.0/net/http.rb:455:in `get'
  from webmock.rb:7:in `<main>'

Pryではトップレベルで定義したメソッドがObjectクラスのpublicなインスタンスメソッドになる

表題の通り。

今日Pry触ってて気づいたんだけど、Pryのトップレベルで定義したメソッドはObjectクラスのpublicなインスタンスメソッドになるっぽくて、大抵のオブジェクトから呼び出せる様になる。意図した仕様なのかバグなのかは分からないけど、びっくりしたからメモっとく。

ちなみにRubyのversionは2.1.5。Pryは普通にgem installした。irbRubyを普通に起動した場合には挙動が違ってて、privateなインスタンスメソッドになる。

$ ruby -v
ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-darwin14.0]
$ pry -v
Pry version 0.10.1 on Ruby 2.1.5

何に驚いたのか?

以下にコードで例を示す。

[1] pry(main)> def my_method
[1] pry(main)*   p 'my_method'
[1] pry(main)* end
=> :my_method
[2] pry(main)> [].my_method // arrayオブジェクトのメソッドとしてmy_methodが呼び出せる!!
"my_method"
=> "my_method"
[3] pry(main)> [].methods.include?(:my_method) // arrayオブジェクトの呼び出し可能なメソッドにmy_methodが含まれてる!!
=> true

トップレベルで定義したmy_methodメソッドが、空のarrayオブジェクトである[]から呼び出せている。割と驚きの挙動。

なぜこうなるのか?

表題にも書いたけど、Objectクラスのインスタンスメソッドとしてmy_methodが定義されてるのが原因。Objectクラスはほとんどのオブジェクトの上位クラスに位置しているので、大抵のオブジェクトからmy_methodが呼び出せるようになる。

[4] pry(main)> Object.instance_methods(false).include?(:my_method) // instance_methods(false)で、そのクラスで定義されたinstance_methodsが取得できる
=> true
[5] pry(main)> [].class
=> Array
[6] pry(main)> [].class.superclass // Arrayの上位クラスにObjectが位置している
=> Object

自分で適当に定義したクラスもObjectクラスを継承した状態になるので、そこから生成したオブジェクトもmy_methodを呼び出せる。

[7] pry(main)> class MyClass; end
=> nil
[8] pry(main)> my_obj = MyClass.new
=> #<MyClass:0x007fbf6972ce20>
[9] pry(main)> my_obj.my_method
"my_method"
=> "my_method"

Objectよりもさらに上位のBaicObjectを継承すれば、こうはならない。

[10] pry(main)> class MyBasicClass < BasicObject; end
=> nil
[11] pry(main)> my_basic_obj = MyBasicClass.new
=> #<MyBasicClass:0x3fdfb62dab38>
[12] pry(main)> my_basic_obj.my_method
NoMethodError: undefined method `my_method' for #<MyBasicClass:0x007fbf6c5b5670>
from (pry):12:in `__pry__'

ここまでがPryでの話。

irbRubyコマンドで起動した場合はどうなるか

Objectのprivateなインスタンスメソッドとして定義されるので、外部から呼び出しは出来ない。

irb(main):001:0> def my_method
irb(main):002:1>   p 'my_method'
irb(main):003:1> end
=> :my_method
irb(main):004:0> [].my_method
NoMethodError: private method `my_method' called for []:Array
  from (irb):4
  from /Users/(ユーザ名)//.rbenv/versions/2.1.5/bin/irb:11:in `<main>'

じゃあ何でこんな挙動(わざわざObjectクラスにprivateなインスタンスメソッドとして定義)になってるかと言うと、オブジェクトのメソッドを定義する時なんかにトップレベルで定義されたメソッドを使えるようにする為。

irb(main):005:0> class MyClass
irb(main):006:1>   def try_2_my_method
irb(main):007:2>     my_method           // my_methodが使える!!
irb(main):008:2>     my_method           // my_methodが使える!!
irb(main):009:2>   end
irb(main):010:1> end
=> :try_2_my_method
irb(main):011:0> my_obj = MyClass.new
=> #<MyClass:0x007fefdb285938>
irb(main):012:0> my_obj.try_2_my_method
"my_method"
"my_method"
=> "my_method"

要は、Rubyではいわゆるグローバルスコープ的な機能を提供する為にObjectクラスを利用してるっぽい。

この事は、BaiscObjectを継承するクラスの中でインスタンスメソッドを定義する時にはトップレベルで定義したメソッドが使えない事からも確認できる。

irb(main):013:0> class MyBasicClass < BasicObject
irb(main):014:1>   def try_3_my_method
irb(main):015:2>     my_method
irb(main):016:2>     my_method
irb(main):017:2>     my_method
irb(main):018:2>   end
irb(main):019:1> end
=> :try_3_my_method
irb(main):020:0> my_basic_obj = MyBasicClass.new
(Object doesn't support #inspect)
=>
irb(main):021:0> my_basic_obj.try_3_my_method
NameError: undefined local variable or method `my_method' for #<MyBasicClass:0x007fefdc86dc50>
    from (irb):15:in `try_3_my_method'
  from (irb):21
  from /Users/(ユーザ名)/.rbenv/versions/2.1.5/bin/irb:11:in `<main>'

try_3_my_methodメソッドの定義の中でmy_methodを呼び出そうとしてエラーが出てる事が分かる。my_methodはグローバルに定義されてる訳では無くて、あくまでObjectクラスのインスタンスメソッドとして定義されてる事が確認出来たと言える。

まとめ

Pryの驚きの挙動から始めて、Rubyメソッドスコープの仕組みについての知見を得た。あとどうでも良いかもだけど、methodsとかinstance_methodsとかprivate_instance_methodsとかsingleton_methodsみたいな定義されたメソッドをarrayで取得する系のメソッドが今回かなり役立ったので、使っていくと良いと思う。特に、falseを引数として渡すと上位クラス由来のものは取り除いてくれるので、どこでメソッドが定義されてるかが特定しやすかった。

7つのデータベース7つの世界を読んだ

タイトルは半分嘘で、全部じゃなくて途中まで読んだ。具体的には、第2章のPostgreSQL、第3章のRiak、第4章のHBase(Hadoop)の1,2日目、第5章のMongoDB、第8章のRedisの1,2日目。CouchDBとNeo4jはイントロだけ。

PostgreSQL

postgresqlは一応慣れ親しんでる(?)はずだけど、知らない事・使った事の無い機能がいっぱい載ってた。というか、普段はActiveRecord越しでイジるかデータの確認にシェルでselectするかぐらいでしか使ってなかったので、実質的には慣れ親しんで無かったんだと思う。

2日目で言うと、ストアドプロシージャ、トリガー、ビュー、クロス集計は使った事が無い機能だった。


ストアドプロシージャとは

DB側である程度まとまった処理を行いたい時に、プロシージャとして名前をつけて処理を保存しておける機能。「7つのデータベース 7つの世界」では、例としてテーブルA上に特定のレコードaがあるかを確認して、無ければaを作成してからテーブルB上にaと関連づけられたレコードbを追加するというストアドプロシージャのコードが載っていた。レコードの「確認(select)」と「追加(insert)」を1回でまとめて行えるので、パフォーマンスも上がるらしい。

ちょっと自分でもコードを書いてみようと思う。

データベースを作る。

いろいろ試すために、まずは練習用のデータベースを作成する。psqlコマンドでシェルを立ち上げて、以下のコマンドを実行。seven_databaseテーブルが作られる。

user=# create database seven_database; // terminalでcreatedbでも良い
CREATE DATABASE
user=# \c seven_database
You are now connected to database "seven_database" as user "(user_name)".

これでOK。次に、contribパッケージとしてcube等が必要になるので、インストールする。一番楽なのはCREATE EXTENSION (パッケージ名)でインストールする事らしいので、tablefunc, dict_xsyn, fuzzystrmatch, pg_trgm, cubeについて順に実行してインストール。

現在のデータベースにインストールされたパッケージは、pg_extensionビューで確認できる。

seven_database=# select extname from pg_catalog.pg_extension ;
    extname
---------------
 plpgsql
 tablefunc
 dict_xsyn
 fuzzystrmatch
 pg_trgm
 cube
(6 rows)

ちゃんとインストールされている事が分かる。

次に、テーブルとして7つのデータエース 7つの世界に載ってたgenres, movies, actors, movie_actorsテーブルを作る。

このページにおいてあるコードをダウンロードしてpsql -d seven_database < create_movies.sqlで実行するか、シェルから以下のSQLを実行。

CREATE TABLE genres (
    name text UNIQUE,
    position integer
);
CREATE TABLE movies (
    movie_id SERIAL PRIMARY KEY,
    title text,
    genre cube
);
CREATE TABLE actors (
    actor_id SERIAL PRIMARY KEY,
    name text
);

CREATE TABLE movies_actors (
    movie_id integer REFERENCES movies NOT NULL,
    actor_id integer REFERENCES actors NOT NULL,
    UNIQUE (movie_id, actor_id)
);
CREATE INDEX movies_actors_movie_id ON movies_actors (movie_id);
CREATE INDEX movies_actors_actor_id ON movies_actors (actor_id);
CREATE INDEX movies_genres_cube ON movies USING gist (genre);

これでOK。

ストアドプロシージャを作ってみる

とりあえず書籍をパクって、moviesテーブルにレコードがあるかを確認して、無ければ作成してからそれに関連付けられたレコードをactorsテーブルにinsertしてみる。

  1 CREATE OR REPLACE FUNCTION add_actor_with_movie( actor_name text, movie_title text, movie_genre cube )
  2 RETURNS boolean AS $$
  3 DECLARE
  4   did_insert   boolean := false;
  5   found_count  integer;
  6   the_movie_id integer;
  7   the_actor_id integer;
  8 BEGIN
  9   SELECT movie_id INTO the_movie_id
 10   FROM movies m
 11   WHERE m.title=movie_title AND m.genre=movie_genre
 12   LIMIT 1;
 13
 14   IF the_movie_id IS NULL THEN
 15     INSERT INTO movies (title, genre)
 16     VALUES (movie_title, movie_genre)
 17     RETURNING movie_id INTO the_movie_id;
 18
 19     did_insert := true;
 20   END IF;
 21
 22   -- Note: not an “error”, as in some programming languages
 23   RAISE NOTICE 'Venue found %', the_movie_id;
 24
 25   INSERT INTO actors (name)
 26   VALUES (actor_name)
 27   RETURNING actor_id INTO the_actor_id;
 28
 29   INSERT INTO movies_actors(movie_id, actor_id)
 30   VALUES (the_movie_id, the_actor_id);
 31
 32   RETURN did_insert;
 33 END;
 34 $$ LANGUAGE plpgsql;

とりあえずこんな感じで書いたらちゃんと動いた。32行目でRETURN did_insertしてて、実際にmovieのinsertを行った場合はtが返ってきてる。

seven_database=# SELECT add_actor_with_movie('Taro', 'Star Wars','(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)');
NOTICE:  Venue found 2
 add_actor_with_movie
----------------------
 t
(1 row)

'Star Wars'に違うactorを紐づけて実行すると、ちゃんとactorはinsertされるけど返り値はfになる。

seven_database=# SELECT add_actor_with_movie('Jiro', 'Star Wars','(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)');
NOTICE:  Venue found 2
 add_actor_with_movie
----------------------
 f
(1 row)

とりあえず想定通りの挙動はしてる。

ハマりポイント

ストアドプロシージャの引数名がtableのカラム名と一致してるとエラーが出た。下の実行例は、ストアドプロシージャの引数名としてtitleを使ってたら、movie tableのtitleカラムと同じって事でエラーが出てる。

seven_database=# SELECT add_actor_with_movie('Saburo', 'Star Wars','(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)');
ERROR:  column reference "title" is ambiguous
LINE 2:   WHERE m.title=title AND m.genre=movie_genre
                        ^
DETAIL:  It could refer to either a PL/pgSQL variable or a table column.
QUERY:  SELECT movie_id                     FROM movies m
  WHERE m.title=title AND m.genre=movie_genre
  LIMIT 1
CONTEXT:  PL/pgSQL function add_actor_with_movie(text,text,cube) line 8 at SQL statement

これをErrorにするのは許せない。。。

あと、途中でいくつか作り替えながら試してみたら、同じadd_actor_with_movieという名前のストアドプロシージャを複数作ってしまって削除の必要が生じた。どうやら、引数の型が違うと同じ名前でも別のプロシージャとして保存されるっぽい。

このページいわく

drop function プロシージャ名([引数の型])

で削除できたので、drop function add_actor_with_movie(text, text, cube);で削除した。あと、引数の型は変えずに名前だけ変えようとすると既存のプロシージャを消してくれってエラーが出た。

seven_database=# \i my_code/add_actor_with_movie.sql
psql:my_code/add_actor_with_movie.sql:34: ERROR:  cannot change name of input parameter "title"
HINT:  Use DROP FUNCTION add_actor_with_movie(text,text,cube) first.

いったんまとめ

学んだ機能を順に説明してくつもりがストアドプロシージャだけでそこそこ時間たっちゃったので、今日はここまで。ストアドプロシージャは強力だけどデータベースにロジックが激しく依存してしまうので、使いどころは難しそう。覚悟の上で使うなら良いと思う。

SQLアンチパターンを読んだ

3ヶ月くらい積読されてたのを引っ張り出して読んだ。例によって、読み始めるとめちゃめちゃ面白くて何故こんな素晴らしいものを積読してたんだ...って感じだった。

ざっくりとした感想

けっこう色んな人が読んだ感想とか内容を簡単にまとめたものとかを書いてるので、ぶっちゃけ今更な気もするけど簡単にまとめとく。

この本ではSQL(というかRDB)のスキーマ設計とかクエリの投げ方とかで陥りがちなアンチパターンを25個に絞って順に解説していってる(1章で1つのテーマを扱うので、ちょうど25章ある)。25章とか多すぎるんじゃってビビってたけど、むしろ1章1章のボリュームは抑え気味で簡潔にまとまってて読みやすかった。

アンチパターンって言っても頭ごなしに否定する訳じゃなくて、どういう意図があってそのアンチパターンを採用してしまうのかとか、より良い解決策は何なのかとか、そういった事が順に説明されてく構成だった。「何を避けるべきか」だけで無く、「どう進むべきか」の指針も打ち出してくれてて、嬉しかった。

自分にとってはⅠ部の「データベース論理設計のアンチパターン」で扱われてた5~8章が印象的で、特に「データのメタデータへの混入」や「メターデータのデータへの混入」を避けよう、みたいな特定のパターンに依存しない心がけを知れたのが良かった。8章の「メタデータトリブル」で扱われてたケース(データであるはずの「年代」をメタデータであるテーブル名として採用してしまう)はけっこうやってしまいそうな気がしたので、まずいって感覚を掴めたのは良かったと思う。

24章. マジックビーンズについて

24章はモデルをアクティブレコードそのものとして扱うことへの弊害について書かれていた。この辺のこと(特にRailsにおいてActiveRecord::Baseを継承したオブジェクトがmodelとして扱われること)はこれまでに多くの人がいろんな議論をしていて、自分の中でもいろいろ思うところがあったので、ちょっとまとめておく。

まずは用語の定義から。マーティン・ファウラーがPoEAAで取り上げた「アクティブレコードパターン」はモデルオブジェクトのクラスをテーブルと対応付け、1つのオブジェクトをテーブルの1行と、オブジェクトの1つのフィールドをテーブルの1つの列と対応付けるというもの。アクティブレコードは基本的なCRUD操作をサポートする。実は今挙げた点だけだと「Row Data Gatewayパターン」(これもPoEAAで取り上げられてるもの)と全然違いはなくて、ただアクティブレコードは domain logicを含む という点でRow Data Gatewayとは違う。

よく問題だと言われるのが、アクティブレコードをデータアクセスオブジェクト(DAO)としてしか使わず、Controllerにロジックを書いてしまうというもの。正直、これに関してはModelやControllerの役割を理解してれば絶対にしない事だからあまり問題じゃ無いと思う。

また、別の点でよく突っ込まれるのは

  • アクティブレコードはモデルをデータベーススキーマに強く依存させてしまう
  • アクティブレコードを用いると1つのクラスで扱う仕事が多くなり過ぎる

みたいな点で、これはまあその通りかなと思う。というか、アクティブレコードだけを用いてモデル層を記述しようとすれば、そりゃこういった問題は出て来るだろうという感じ。

PoEAAの中でマーティン・ファウラーも言ってるんだけど、そもそもアクティブレコードはロジックの薄いモデルを扱うときに便利なパターンで、複雑なロジックを記述するには向いていない。だからまあ複雑になってきたらDAOとしての機能は切り出して別のモデルクラスにロジックは書くとか、そういった感じで対処すれば良いしそれが望まれてるんだと思う。

この辺は既に経験豊かな優秀な人たちがたくさん議論してて、Web上にもいっぱい情報が落ちてるのでそういった情報を参考にしながら適宜対処していけば良さそう。

まとめ

まとめとくと、SQLアンチパターンは良い本なので、読むと良い。と思う。

ブラウザで動くMarkdown editor

vue.jsの公式サイトのexampleにmarkdown editorっていうのがあって、以下のコードの様にただfilterにmarked指定するだけでちゃんと動いててすごかった。

new Vue({
  el: '#editor',
  data: {
    input: '# hello'
  },
  filters: {
    marked: marked
  }
})

htmlもこれだけ

<div id="editor">
  <textarea v-model="input"></textarea>
  <div v-html="input | marked"></div>
</div>

marked.jsのmarked関数を適用してるだけなのでmarked.jsがすごいのかもだけど、感動したので手元でも動かしてみた。

環境

例によって、yoのwebappを使う。

bowerでvue.jsとmarked.jsをinstall。ついでに、textareaが縦に伸びていく様にjquery-autosizeもインストール。

$bower install vue marked jquery-autosize --save

markedは勝手にはindex.htmlに追記されないので、自分で追記してやる必要がある。

    <!-- manual import -->
    <script src="bower_components/marked/lib/marked.js"></script>

後は、autosizeを適用するコードをjsに追記。

 11 $(document).ready(function(){
 12   $('#autosizable').autosize();
 13 });

htmlもちょっと修正

   38       <div id="editor">
   39         <div v-html="input | marked"></div>
   40         <textarea v-model="input" id="autosizable" ></textarea>
   41       </div>
   42     </div>

cssも書いてやる。

   95 html, body, #editor {
   96   margin: 0;
   97   height: 100%;
   98   font-family: 'Helvetica Neue', Arial, sans-serif;
   99   color: #333;
  100 }
  101
  102 textarea, #editor div {
  103   display: inline-block;
  104   width: 49%;
  105   height: 600px;
  106   vertical-align: top;
  107   -webkit-box-sizing: border-box;
  108   -moz-box-sizing: border-box;
  109   box-sizing: border-box;
  110   padding: 0 20px;
  111 }
  112
  113 textarea {
  114   border: none;
  115   border-right: 1px solid #ccc;
  116   resize: none;
  117   outline: none;
  118   background-color: #f6f6f6;
  119   font-size: 14px;
  120   font-family: 'Monaco', courier, monospace;
  121   padding: 20px;
  122 }
  123
  124 code {
  125   color: #f66;
  126 }
  127

ついでに、highlight.jsをインストールしてcodeのsyntax-highlightもしてみた。

$ bower install highlightjs --save

jsにもちょこっと追記

  1 marked.setOptions({
  2   highlight: function (code) {
  3     return hljs.highlightAuto(code).value;
  4   }
  5 });

これだけで、以下の画像みたいに表示される。けっこうちゃんとしたmarkdown editorになっててすごい!!

f:id:south37:20141228033940p:plain

ちなみに、右側がテキストエリア、左側がmarked.jsでレンダリングした結果になっている。vue.js使ってるのでもちろんリアルタイムで表示がされる。

まとめ

余計なお世話かもだけど、はてなブログにもプレビュー同時に見ながら編集できるマークダウンエディタがついてると嬉しいなーとか思った。ただ、画面のサイズどうするのとかいろいろ問題あるし、実際つけると微妙なのかもなーとも思った。試してみないと分からなそう。

ただ、はてなブログのプレビュー見るときにサーバーへリクエスト送ってるっぽくてけっこう遅延があるので、クライアントサイドでのレンダリングは試す価値があると思う。

Masonry試してみた

とある事情でPinterest的UI作ってみたいなーと思ってて、Masonryを試してみた。

環境構築

とりあえず、試すための環境構築をしてみる。久しぶりに、yeomanを使う。

yeoman

久しぶりでversionが古い気がしたので、npm updateしてnpmのモジュールを一気にupdateした。この時点で、yo, bower, gruntのversionはそれぞれ

$ yo -v
1.2.0

$ bower -v
1.3.12

$ grunt -V
grunt-cli v0.1.13
grunt v0.4.5

みたいな感じになった。

yoで雛形を作る。

まず、webappという名前のgeneratorをinstallする。

npm install generator-webapp

これで、yo webappでwebapp generatorを使ってwebアプリケーションの雛形を作ることが出来る。どうでも良いかもだけど、npmコマンドでyo用のgeneratorをインストールするって忘れててちょっと戸惑ってしまった。

次に、yoでgeneratorを走らす。

$ yo webapp

     _-----_
    |       |    .--------------------------.
    |--(o)--|    |    Welcome to Yeoman,    |
   `---------´   |   ladies and gentlemen!  |
    ( _´U`_ )    '--------------------------'
    /___A___\
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

Out of the box I include HTML5 Boilerplate, jQuery, and a Gruntfile.js to build your app.
[?] What more would you like? Bootstrap, Sass, Modernizr
[?] Would you like to use libsass? Read up more at
https://github.com/andrew/node-sass#node-sass: Yes
   create Gruntfile.js
   create package.json
   create .gitignore
   create .gitattributes
   create .bowerrc
   create bower.json
   create .jshintrc
   create .editorconfig
   create app/styles/main.scss
   create app/favicon.ico
   create app/robots.txt
   create app/index.html
   create app/scripts/main.js
   invoke   mocha
.
.
.

この時、最初に

[?] What more would you like? (Press <space> to select)
❯⬢ Bootstrap
 ⬡ Sass
 ⬡ Modernizr

みたいな画面が出て、Bootstrap, Sass, Modernizrをそれぞれ使うかどうかを選べた。j, kで上下移動でspaceで切り替えが出来て、今回は3つともチェックをつけて実行した。

処理が終わるといろいろファイルが作られてた。

$ ls
Gruntfile.js app          bower.json   node_modules package.json test

ただ、bower.jsonに記載されてる類のjsファイルはまだインストールされてなかったので、bower installしてやる必要があった。

$ bower install
bower modernizr#~2.8.2          cached git://github.com/Modernizr/Modernizr.git#2.8.3
bower modernizr#~2.8.2        validate 2.8.3 against git://github.com/Modernizr/Modernizr.git#~2.8.2
bower bootstrap-sass-official#~3.2.0           cached git://github.com/twbs/bootstrap-sass.git#3.2.0+2
bower bootstrap-sass-official#~3.2.0         validate 3.2.0+2 against git://github.com/twbs/bootstrap-sass.git#~3.2.0
bower jquery#>= 1.9.0                          cached git://github.com/jquery/jquery.git#2.1.3
bower jquery#>= 1.9.0                        validate 2.1.3 against git://github.com/jquery/jquery.git#>= 1.9.0
bower bootstrap-sass-official#~3.2.0          install bootstrap-sass-official#3.2.0+2
bower modernizr#~2.8.2                        install modernizr#2.8.3
bower jquery#>= 1.9.0                         install jquery#2.1.3

bootstrap-sass-official#3.2.0+2 bower_components/bootstrap-sass-official
└── jquery#2.1.3

modernizr#2.8.3 bower_components/modernizr

jquery#2.1.3 bower_components/jquery

これで、bower_componentsというディレクトリが生成され、そこにbootstrap, jquery, modernizrなどのライブラリがインストールされた。

$ cat bower.json
{
  "name": "masonry-practice",
  "private": true,
  "dependencies": {
    "bootstrap-sass-official": "~3.2.0",
    "modernizr": "~2.8.2"
  }
}%

$ ls bower_components
bootstrap-sass-official jquery                  modernizr

良い感じ

一度雛形のWebAppを動かしてみる

とりあえず、今generatorで作ったアプリケーションを動かしてみる。grunt serveコマンドを叩くとlocalhost:9000をlistenするサーバーが起動され、さらに勝手にブラウザの画面がたちあがる。

$ grunt serve // 注: grunt serverではない。serverコマンドは非推奨らしい。
// サーバーが立ち上がり、ブラウザにhttp://localhost:9000/が表示される。

ここまではうまくいった。

masonryを試す

次に、masonryを試してみる。bowerでmasonryをインストールする。

bower install masonry --save

割といろいろ依存してるみたいで、けっこうbower_componentsの中身が増えた。

$ ls bower_components
bootstrap-sass-official eventEmitter            get-size                jquery                  matches-selector        outlayer
doc-ready               eventie                 get-style-property      masonry                 modernizr

あと、app/index.htmlにも勝手にscriptタグが追加されてた。

// app/index.htmlのscript読み込み部分
    <!-- build:js(.) scripts/vendor.js -->
    <!-- bower:js -->

    ~ いろいろ ~

    <script src="bower_components/get-style-property/get-style-property.js"></script>
    <script src="bower_components/get-size/get-size.js"></script>
    <script src="bower_components/eventie/eventie.js"></script>
    <script src="bower_components/doc-ready/doc-ready.js"></script>
    <script src="bower_components/eventEmitter/EventEmitter.js"></script>
    <script src="bower_components/matches-selector/matches-selector.js"></script>
    <script src="bower_components/outlayer/item.js"></script>
    <script src="bower_components/outlayer/outlayer.js"></script>
    <script src="bower_components/masonry/masonry.js"></script>
    <!-- endbower -->
    <!-- endbuild -->

ちょこちょこ修正

もともとあったdiv要素とか消してmasonryのGetting started見ながらdiv作ったり簡単なjsを書いたりした。

これがjs。ほぼドキュメントのまんま。

  1 var container = document.querySelector('#container');
  2
  3 imagesLoaded( container, function() {
  4   var msnry = new Masonry( container, {
  5     columnWidth:  340,
  6     itemSelector: '.item'
  7   });
  8 });

これがhtmlに追記した部分。

   36       <div id="container">
   37         <div class="item">
   38           <img src="http://corp.toei-anim.co.jp/press/watori01.jpg" width="320px">
   39           <p>ワールドトリガー</p>
   40         </div>
   41         <div class="item">
   42           <img src="http://worldtrigger.info/images/cover.png" width="320px">
   43           <p>ワールドトリガー</p>
   44         </div>
   45         <div class="item">
   46           <img src="http://www.tv-asahi.co.jp/worldtrigger/intro/img/wt.jpg" width="320px">
   47           <p>ワールドトリガー</p>
   48         </div>
   49         <div class="item">
   50           <img src="http://livedoor.blogimg.jp/zakuzaku911/imgs/f/3/f39495e6.jpg" width="320px">
   51           <p>ワールドトリガー</p>
   52         </div>
   53       </div>

これがcss。丸みつけたり線引いたりした。

   94 .item {
   95   margin: 5px;
   96   border: 1px solid #ccc;
   97   box-shadow: 0 2px 1px #eee;
   98   -webkit-border-radius: 4px;
   99 }

で、出来たのがこんな感じ。

f:id:south37:20141221021112p:plain

うん、なんか良さげな気がする!!!!

ここから発展させるとすると、XMLHTTPRequestで追加で画像取得してどんどん下に伸びていけばさらにそれっぽくなると思う。

まとめ

サクッとそれっぽいviewが作れて楽しかった。Masonry既にめちゃめちゃ有名だけどやっぱり良さげだった。

あと、ワールドトリガー週刊少年ジャンプで好評連載中でめちゃめちゃ面白いので読むと良いと思う。