rcmdnk's blog

LOOP Magazine vol.19 (サンエイムック)

Octopressでfor文を使ってその中でタグを使った時に 全てが同じ出力の様になったり 同じ文字が追加され続けて出力が肥大化していったりしてしまいました。

ちょっとfor文について理解が間違ってた所があったのでそれについて。

Liquidタグでのfor文

よく使うのが

<ul>
  {% for post in site.posts %}
  <li><a href="{{post.url}}">post.title</a></li>
  {% endfor %}
</ul>

みたいな感じで現在ブログにあるポストに関してループを回して 色々出力させる、と言う事。

また、一定数だけ回したい時は

{% for i in (1..10) %}
  ...
{% endfor %}

みたいな風にするとi=1から10までで回すことが出来ます 1

for文の中でのタグの利用

次の様なテスト用タグを用意してみます。

for_test.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module Jekyll

  class ForTest < Liquid::Tag
    @val_name = nil
    @val_markup = nil
    @val_count = 1

    def initialize(tag_name, markup, tokens)
      @val_name = tag_name
      @val_markup = markup
      @val_count = 1
      p "Initialize: name = #{@val_name}, markup = #{@val_markup}, count = #{@val_count}, self = #{self}"
      super
    end

    def render(context)
      @val_count += 1
      p "count: name = #{@val_name}, markup = #{@val_markup}, count = #{@val_count}, self = #{self}"
      "count: name = #{@val_name}, markup = #{@val_markup}, count = #{@val_count}, self = #{self}"
    end
  end
end

Liquid::Template.register_tag('for_test', Jekyll::ForTest)

単に入力したタグ名とマークアップを取り入れて さらにval_countに関しては一度renderの方でインクリメントしています。

これを出力するだけのものです。

これを

{% for i in (1..10) %}
  {% for_test test %}
{% endfor %}

としてみると

"Initialize: name = for_test, markup = test0 , count = 1, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test , count = 2, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test , count = 3, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test , count = 4, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test , count = 5, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test , count = 6, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test , count = 7, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test , count = 8, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test , count = 9, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test , count = 10, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test , count = 11, self = #<Jekyll::ForTest:0x007fe904db5778>"

と言った標準出力が出てきますが、ここで注目は まずInitializeが1回しかありません。 さらに全てのrenderからのselfの出力を見ると同じものを使っています。

つまり、for文で繰り返し同じタグを使った場合でも、 書かれた物が同じであれば同じオブジェクトとして扱われます。

Liquidタグではmarkupの部分に変数を与える事は通常出来ないので、 結果的にfor文の中は基本的に全て同じになるので これで問題が起こることは余り無いのかもしれません。

ですが、以前、変数をなんとか渡したくて

Liquidで変数をTagに渡す方法

ここに書かれた方法でsite.configに含まれる値を抜き出して来る様な事を出来るようにしていて、 これが問題を起こしました。

この様な設定をしてるとfor文の中でも別々の値を使う事が出来、 それぞれinitializeで設定される値が変わってきます。

ですが、実際にはinitializeは1回しか行われず、更に意図と違った形でしか実行されません。

ただ、これを回避する方法があって、 それはfor文を別のファイルに入れてincludeタグで読み込むことです。

Liquidタグのinitializerenderが実際いつ実行されてるのか よく分かってなかった部分がありましたが、 initializeはファイルが読み込まれた時、に実行されます。

この読み込まれた時と言うのはincludeで読み込まれた時もその時点で行われます。

なので、あるページでタグを使っていて さらにそこからincludeした先でも別のタグを使っているとすると、 まず、最初のページを読み込んだ時点でそのページにあるタグがinitializeされ、 タグが書いてある部分まで来たら実行し、 また、includeタグがあった場合、その時点で その読み込むファイルの中にあるタグをinitializeし タグまで辿り着いたら実行し。。。みたいな事を行います。

また、includeに関しては、for文の中にあったとしても それぞれ別の物として扱われるので for文の中でもincludeが呼ばれる度、その中にあるタグがinitializeされます。

試しに以下みたいなのを記事に書いておいて

{{"For loop test!"|debug: true}}
{% for_test test0 %}
{% for_test test1 %}
{% for i in (1..3) %}
   {{i | debug}}
   {% for_test test2 %}
   {% include include_test.html %}
{% endfor %}

さらにsource/_include/include_test.html

{% for_test test3 %}

と言う内容で用意します。

最初のFor loop test!の所で使ってるdebugというフィルターは jekyll-bootstrapというJekyllのフレームワークの中に入ってる プラグインで

1
2
3
4
def debug(obj, stdout=false)
  puts obj.pretty_inspect if stdout
  "<pre>#{obj.class}\n#{obj.pretty_inspect}</pre>"
end

みたいな定義になっています。

jekyll-bootstrap/debug.rb at master · plusjade/jekyll-bootstrap

記事の上にクラス名を含めて内容を表示させたり、 上の様にtrueを与えればjekyllのコマンドの標準出力として 表示させることも出来ます。

あると色々便利です。

で、これをgenerateしてみると

"Initialize: name = for_test, markup = test0 , count = 1, self = #<Jekyll::ForTest:0x007fe904db5778>"
"Initialize: name = for_test, markup = test1 , count = 1, self = #<Jekyll::ForTest:0x007fe904db54d0>"
"Initialize: name = for_test, markup = test2 , count = 1, self = #<Jekyll::ForTest:0x007fe904db4918>"
"For loop test!"
"count: name = for_test, markup = test0 , count = 2, self = #<Jekyll::ForTest:0x007fe904db5778>"
"count: name = for_test, markup = test1 , count = 2, self = #<Jekyll::ForTest:0x007fe904db54d0>"
1
"count: name = for_test, markup = test2 , count = 2, self = #<Jekyll::ForTest:0x007fe904db4918>"
"Initialize: name = for_test, markup = test3 , count = 1, self = #<Jekyll::ForTest:0x007fe904dcdb20>"
"count: name = for_test, markup = test3 , count = 2, self = #<Jekyll::ForTest:0x007fe904dcdb20>"
2
"count: name = for_test, markup = test2 , count = 3, self = #<Jekyll::ForTest:0x007fe904db4918>"
"Initialize: name = for_test, markup = test3 , count = 1, self = #<Jekyll::ForTest:0x007fe904dd7c38>"
"count: name = for_test, markup = test3 , count = 2, self = #<Jekyll::ForTest:0x007fe904dd7c38>"
3
"count: name = for_test, markup = test2 , count = 4, self = #<Jekyll::ForTest:0x007fe904db4918>"
"Initialize: name = for_test, markup = test3 , count = 1, self = #<Jekyll::ForTest:0x007fe904dd5960>"
"count: name = for_test, markup = test3 , count = 2, self = #<Jekyll::ForTest:0x007fe904dd5960>"

こんな感じの出力になります。

まず、記事中に書かれたtest0, test1, test2についてinitializeが行われています。

その後、普通に書かれたtest0, test1を実行。 これらは別々のオブジェクトです。

ですが、for文内にあるtest2については1つしかありません。

実際、その後のfor文の中でもcountの数が実行するたびに増えていってるのが分かりますし、 右のselfの値も全て同じです。

一方、includeして呼んでるtest3については、test2の出力から分かる通り、 実際その場に来て初めて読み込まれて中のタグをinitializeしています。 で、実行し、 さらに次に呼び出された時のも再びinitializeし別のオブジェクトになって countの数やselfを見ても違うものだと確認出来ます。

ということでfor文の中でタグを読み込んで何か書きたいような時は注意が必要で、 必要であればincludeを使って別ファイルにする、と言う対処が必要だ、ということです。

Sponsored Links
Sponsored Links

« Octopressで'ダブルハイフン'をそのまま残す OctopressでJavsScriptやHTMLを圧縮する »

}