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既にめちゃめちゃ有名だけどやっぱり良さげだった。

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

RustにおけるOwnershipの仕組み

前回に引き続き、Mozillaイチオシの言語であるRustを触ってみる。今日はメモリ管理の仕組みとしての「Ownership」に着目する。

ちなみに、今日書く内容はRust Guideの17章、OwnershipのGuidePointerのGuideをつまみ食いしながら適当にまとめたものなので、原文読んでもらっても良い。

メモリ管理について

まずは背景として、メモリ管理の必要性について考えてみる。

一般に、計算機上で動くソフトウェアというのはデータをメモリ上に次々に確保して様々な計算等を行っていく訳だけど、メモリというのは有限の大きさしか持たない為にデータの追加ばかりしていく訳にも行かなくて、使わなくなった領域を次々に解放して再利用しなければならなくなる。

こういった事を考えると、メモリ解放の機構を言語に組み込む必要性があると分かる。

メモリに確保されるデータの種類

メモリに確保されるデータには、大別すると次の3種類がある。

  1. ソフトウェア実行中はずっと残る静的なデータ
  2. プロシージャ呼び出し時に確保され、プロシージャを抜けると解放されるスタック上のデータ
  3. ソフトウェア実行時に動的に確保されるデータ(ここで確保される領域をヒープメモリと呼ぶ)

メモリの解放という文脈でこれらのデータを考えると、1の静的なデータはずっと残るものなので解放されないし、2のスタック上のデータは勝手に解放されるので気にしなくて良いのだけど、3の動的に確保されるデータをどう扱うかが問題になってくる。

c言語では、mallocやfreeによってユーザーが責任を持ってメモリの確保と解放を行うことが求められたけど、メモリリークや不正なメモリへのアクセス等が起きやすく大きな問題を抱えていた。

一方、近年の多くの言語では Garbage Collection(GC) の機構を組み込みで持つことでユーザーをメモリ管理の複雑さ・煩雑さから解放したが、 GC 実行時にストップザワールドが起こる等のデメリットもあった。

そこで、Rustは「Ownership」という考え方を導入することで、メモリリークの危険性を抑えながらパフォーマンス上の問題を解決することを目指した。

Ownership

動的にメモリを確保する際、例えばc言語では

{
    int *x = malloc(sizeof(int));

    // we can now do stuff with our handle x
    *x = 5;

    free(x);
}

の様な書き方をする。同様の事が、Rustにおいては

{
    let x = box 5i;
}

という記述で表現できる。ポイントは、この記述できちんとメモリの確保と解放が表現出来ている事だ。

box というキーワードによって 5i という値がヒープ上に動的に確保され、その参照がxへbindされている。このxがownershipを持っていて、xがscopeから外れる(xを含むブロックを抜ける)時にメモリの解放が行われる。

この様に変数にownershipを持たせる事で、メモリの解放が必ず行われる事を保証できる様になる。

ownershipは移動させることもできて、例えばBox型の値(boxキーワードでヒープに確保した値への参照)を引数として関数を呼び出した場合には、ownershipはその関数内の変数numに移る。

fn main() {
    let x = box 5i;

    add_one(x);
}

fn add_one(mut num: Box<int>) {
    *num += 1;
}

ただし気をつけなければならないのは、ownershipを移すと、ownershipを失った変数からは元の値へアクセス出来なくなる事である。上記の例であれば、add_one(x)呼び出しの後にxを使おうとするとエラーが出る。

fn main() {
    let x = box 5i;

    add_one(x);

    println!("{}", x); // error!!!!!
}

fn add_one(mut num: Box<int>) {
    *num += 1;
}

この問題を避けるために、例えば関数から返り値を使うといった方法が考えられる。

fn main() {
    let x = box 5i;

    let y = add_one(x);

    println!("{}", y);
}

fn add_one(num: Box<int>) -> int {
    *num + 1;
}
//注: これはあくまで例で、あまり良い方法では無い。

ただし、今回の例ではnumの所有権は一時的なものであれば良く、返り値という形ですぐにまたmainのyに戻されている。こういった一時的な所有権というシチュエーションは良くあるものであり、上記の例よりもBorrowingという仕組みを使う方が推奨されている。

Borrowing

Borrowingとは、ownershipは移さずに値への参照を可能にする機構である。&キーワードで値への参照を作ると、それは値をborrowingする為のpointerとなる。例えば、ownershipを移すことで実現していた上記の例は、以下のコードに書き直せる。

fn main() {
    let x = box 5i;

    println!("{}", add_one(&*x));
}

fn add_one(num: &int) -> int {
    *num + 1
}

呼び出し方がちょっとややこしくなってるけど、 *x で5iという値を意味していて、さらにその値をborrowingするために&キーワードで参照を作っている。

xのownershipは移動しない為、何度でもxの値を使用できる。

fn main() {
    let x = box 5i;

    println!("{}", add_one(&*x));
    println!("{}", add_one(&*x)); // 何度でもxを使える!!
    println!("{}", add_one(&*x)); // 何度でもxを使える!!
}

良い感じ。

mutableなborrowing

mutableなborrowingというのも一応出来て、&mutキーワードを使ってmutableな参照を作れば良い。ただし、もともとの値がmutableでないといけない。

fn main() {
    let mut x = box 5i;

    add_one(&mut *x);

    println!("{}", x); // xの値はborrowingされただけで移動してないから使える!!
}

fn add_one(num: &mut int) {
    *num += 1
}

mutや&mutや*の様なキーワードが入り乱れる事になるのでちょっとつらい感じになる。

Owner、およびBorrowerの権限について

Rustにおいては、ownerやborowerにはそれぞれ独自の権限がある。ownerは、次の3つの権限を持つ。

owner の権限

  1. リソースがいつ解放されるかを制御出来る。
  2. リソースを immutable な形で多くの borrower に貸し与えることが出来る。
  3. リソースを mutable な形で1つの borrower に貸し与えることが出来る。

ただし、これは次の2つの制約の存在も意味する。

owner の制約

  1. 既に誰かに貸し与えたリソースを変更したり、mutable な形で別の誰かに貸し与えたりする事は出来ない。
  2. 既に誰かに mutable な形で貸し与えたリソースは、アクセスすることが出来ない。

基本的に、変更によって変な競合が起きる可能性のある事は禁止されてるっぽい。

また、borrowerには次の権限がある。

  1. borrowがimmutableなら、リソースの読み取りが出来る。
  2. borrowがmutableなら、リソースの読み書きが出来る。
  3. 他の誰かにリソースを貸し与える事が出来る。

ただし、borrowerにも重要な制約があり、

「他の誰かにリソースを貸し与えたら、自分のborrow backの前に貸し与えた分を borrow backしてもらわなければならない」

らしい。この制約が守られないと、例えばownerのスコープが終了してメモリが解放され、不正な値となった領域にborrowerがアクセスする、といった危険な事が起きてしまう。そういった意味で、大事な制約となっている。

所感

Ownershipについて調べてみて、Cなんかと比べて一番便利なのはヒープ上に確保した値をプロシージャで安心して返せることなのかもなーと思った(返り値のownerが明確であれば、メモリの解放を保証出来るから)。

正直、Cでバリバリコーディング出来るとかでは全然無いんだけど、解放が保証されるか分からないポインタを使うとか想像するだけで怖すぎるなーとは思う。

で、C++におけるメモリ管理のお作法がちょっと気になったのでGoogle C++スタイルガイド 日本語訳とかいうのを見てみたら、現代的なC++コーディングではスマートポインタという解放が保証された機構を使うものらしい事を知った。

基本的には所有権の移動が無い scoped_ptr を使って、どうしても共有の必要性が出てきたら(参照カウントを組み込んだ) shared_ptr を使うと。なるほど。

割とRustと似たような事をやるっぽい。