HikiDoc plugin

投稿者 okkez 2010-05-25 13:53:00 GMT

HikiDoc - FrontPage.ja でプラグインを使いたかったので、プラグインを作る方法を調べてみた。

ソースを読めばプラグインを作る方法はわかる。 しかし、HikiDoc を使った上でプラグインも使っている例はあまりないようだった。

class CustomOutput < HikiDoc::HTMLOutput
  def initialize(suffix = " />", options = {})
    super suffix
    @options = options
  end

  def inline_plugin(src)
    # あんまり使わないので未実装
  end

  def block_plugin(src)
    name, *args = parse_plugin(src)
    klass = Plugins.const_get(name.classify)
    @f.puts klass.new(@options).call(args)
  rescue NameError => ex
    @f.puts ex.message
  end

  private
  def parse_plugin(src)
    result = []
    if /([a-z0-9_]+)\((.+)\)/m =~ src
      result.push $1
      result.push *$2.split(',').map(&:strip) if $2
    end
    result
  end
end

こんな感じで HikiDoc::HTMLOutput を継承して inline_plugin と block_plugin を定義すれば良い。 今回の実装方法でプラグインを定義するには以下のようにする。 Rails で使用するのでヘルパーを使えるようにしておく。

module Plugins
end
class Plugins::Base
  include ActionView::Helpers, ::ERB::Util

  def initialize(options = {})
    @options = options
  end

  def call(args)
    raise 'must implement in subclass'
  end
end
class Plugins::Echo < Plugin::Base
  def call(args)
    %Q!<pre>#{args.inspect}</pre>!
  end
end

クラスを使わずに実装する方法もあるが、今回は Rails なので Rails っぽくなるようにクラスを使ってみた。 クラスを使わない場合は、プラグインの名前をキーにしたハッシュに処理を登録する方法が使えると思う。

Hiki では HikiDoc のプラグイン機構は使っていないように見えたけど気のせい?また今度調べてみる。

カテゴリ  | タグ ,  | コメントなし | トラックバックなし

using cancan

投稿者 okkez 2010-05-25 13:17:00 GMT

ryanb’s cancan at master - GitHub を使ってみた。

元々 be9’s acl9 at master - GitHub を使っていたのだけど、 ちょっと手の込んだことをやろうとすると、やり方がわからなくなったので cancan を使うことにした。 Rails Authorization in The Ruby Toolbox でも人気だったし @kakutani にも紹介してもらったし。

ほとんどのことは Home - cancan - GitHub の Wiki を見ればわかる。これで分からない場合は rdoc.info :: cancan を見ればいいはず。

acl9 に比べていいところは

  • 権限に関するコードが一ヶ所に書けるところ
  • 権限だけのテストを簡単に書けるところ
  • 複雑な権限管理もそれなりにシンプルに書けるところ
  • たぶん DB に権限情報を書いてそれを読み込むように書いても使えるところ

良くないところは、権限のコードが長くなってしまうことくらい。 これはテストを書けば、特に問題にはならないかもしれない。 あるいは DB に権限データを登録するようにしてしまえば、シンプルになるだろう。

カテゴリ  | タグ ,  | コメントなし | トラックバックなし

using paperclip

投稿者 okkez 2010-05-25 13:00:00 GMT

thoughtbot’s paperclip at master - GitHub を使ってみた。

あるモデルにカラムを追加して使う方法の場合は Home - paperclip - GitHub をよく読んで使えば問題なさそうだった。

一つのレコードに複数の添付ファイルを持たせる場合に少しハマったので書いておく。

class Issue < ActiveRecord::Base
  has_many :attachments, :class_name => "::Attachment", :dependent => :destroy
end
class Attachment < ActiveRecord::Base
  belongs_to :issue
  has_attached_file :attachment
end

このようなクラス構成の場合、Attachment というモデルは paperclip で定義している Paperclip::Attachment と名前が 被るので Issue の定義で注意する必要がある。 上で書いているように、:class_name オプションに「フルパス」でモデルのクラス名を書いておけば良い。

あと paperclip では xxx_{file_name,content_type,file_size,updated_at} というカラムを用意すると、 モデルに xxx という名前のメソッドが定義されるので xxx の部分を長くしすぎるとちょっとコードが読みづらくなる。

実際に Attachment モデルに attachment_* という名前でカラムを用意したので以下のようなコードが頻出して気持ち悪くなったことがある。


link_to @attachment.attachment_file_name, @attachment.attachment.url

Attachment クラスでエイリアスを定義しておけば多少はマシになるかと思う。

カテゴリ  | タグ ,  | コメントなし | トラックバックなし

using jQuery in Rails

投稿者 okkez 2010-02-20 02:00:00 GMT

最近 Rails で作っているウェブアプリケーションでは jQuery を使うようにしている。 なんとなくこうすれば良さそう、というのがわかってきたので書いておく。

google の jsapi を使う。 どこかのブログで見つけたコード片をちょっと変更したやつ。

module ApplicationHelper

  def google_jsapi_tag
    '<script type="text/javascript" src="http://www.google.com/jsapi"></script>'
  end

  def google_load_tag(name, version)
    %Q|<script type="text/javascript">google.load("#{name}", "#{version}");</script>|
  end

end

layout では以下のようにする。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
  <head>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <meta http-equiv="Content-Script-Type" content="text/javascript" />
    <meta http-equiv="Content-Style-Type" content="text/css" />
    <%= google_jsapi_tag %>
    <%= google_load_tag 'jquery', '1.3' %>
    <%= google_load_tag 'jqueryui', '1.7' %>
    <%= javascript_include_tag 'application' %>
    <%= yield :head %>
    <%- javascript_tag do -%>
      jQuery(function($){
        if (navigator.cookieEnabled) {
          $("#cookie_check").hide();
        } else {
          $("#cookie_check").show();
        }
        <%= yield :script %>
      });
    <%- end -%>
    <%= stylesheet_link_tag 'application' %>
    <%= stylesheet_link_tag 'jquery-ui-1.7.2.custom' %>
    <title><%= @title %></title>
  </head>
  <body>
    <!-- 略 -->
  </body>
</html>

こうしておくと view では以下のように書くことができる。

<%- content_for :script do -%>
$("a#hoge").click(function(event){ /* なんか処理 */});
<%- end -%>

global な名前空間を汚さないし、割と短く書けるし気に入っている。

カテゴリ ,  | タグ , , ,  | コメントなし | トラックバックなし

prawn with Rails2.3.5

投稿者 okkez 2010-02-12 04:00:00 GMT

prawn を Rails2.3.5 で使おうと思って調べてみたら、prawnto が使えなくなっていた。

仕方が無いので prawn 単体を Rails で使う方法を考えてみたので書いておく。

以下のコードをそれぞれのファイルに書く。

# config/environment.rb
config.gem "prawn"
config.gem "prawn-layout", :lib => "prawn/layout"
config.gem "prawn-security", :lib => "prawn/security"

config.gem “prawn” だけだと prawn/{layout,security} がちゃんと読み込まれないので別の場所で require する必要がある。

# config/initializers/mime_types.rb
Mime::Type.register "application/pdf", :pdf
# 適当なヘルパー
  def render_pdf
    pdf = Prawn::Document.new(:page_size => "A4")
    # フォントは環境に合わせる
    pdf.font "/usr/share/fonts/truetype/vlgothic/VL-Gothic-Regular.ttf"
    yield pdf
    pdf.render
  end

以上を追加したら、割と簡単に PDF を出力することができるようになる。

コントローラーでこんな風に書いて、

class ReportsController < ApplicationController
  def show
    @report = Report.find(params[:id]
    respond_to do |format|
      format.html # show.html.erb
      format.pdf # show.pdf.erb
    end
  end
end

ビューでこう書けばよい。

# show.pdf.erb
<%=
render_pdf do |pdf|
  pdf.text "Hello, PDF world!"
end
%>

追記

本家(github)をよく見たら別の方法が書いてあった。 * Using Prawn in Rails - prawn - GitHub

どっちが良い方法なんだろう。 github に書いてある方法だとちゃんとダウンロードするファイル名を指定できるのが良い。

自分が書いた方法だと、view なので view で使用するデータの受け渡しが楽だけどファイル名の指定とかが面倒。 あと Emacs で適切なモードになってくれないのが気にくわない。

カテゴリ  | タグ ,  | 2 comments | トラックバックなし

Rails の generator gem のテスト方法

投稿者 okkez 2009-12-18 14:39:00 GMT

自分で Rails の generator gem のテストを書いてみたんだけど、どうもしっくり来ない。

参考にしたのはここ。

作ってた engines はこれ。

やりたかったことは以下のとおり。

  • Rails Engines なので基本的には Rails アプリケーションとしてテストしたい
  • generator は engines として使うときに必要なファイルを生成するためのものなので独立してテストしたい
  • rake test でちゃんとテスト出来れば OK

で、冒頭の Rails Guides を参考にテストを書いてみたけどちゃんと動かなかったので Rails のソースを読んでみた。 読んでみて分かったのは以下のこと。

  • Rails::Generator::Scripts::Generate#run でファイルを生成する先は指定できる
  • けど、generator をどこから探してくるかは指定できない
  • RAILS_ROOT が定義されていると RAILS_ROOT/lib/generators や RAILS_ROOT/vendor/generators などから探してくれる
  • 他には既にインストール済みの generator からも探してくれる
  • RAILS_ROOT/generators は見てくれない

作っているのは engines gem なのだから RAILS_ROOT/generators も見てほしいんだけど、それはやってないらしいので、ここでは RAILS_ROOT/lib/generators に symlink を張ることにした。 一応、これでちゃんと動いているみたいなのだけど、setup で symlink 作成して teardown で symlink を削除したりしてるのがちょっと気持ち悪い。

Rails Guides にも書いてあったとおり generator を提供している多くの Gem は generator 自体のテストは書いていない。(cucumber や rspec を参考にしたかったんだけど書いてなかった。)

もう一回やりたかったことをまとめるとこうなる。

  • Rails Engines なので基本的には Rails アプリケーションとしてテストしたい
  • generator は engines として使うときに必要なファイルを生成するためのものなので独立してテストしたい
  • 既存の generator を読み込まずにテストしたい (開発中の Gem と同名の Gem が入っていてもいいように)
  • rake test でちゃんとテスト出来れば OK

あ、コードは github に全部上がっているので見てください。

みんな generator のテストはあんまり書かないものなのかなぁ?

カテゴリ ,  | タグ ,  | コメントなし | トラックバックなし

Rails Engines なプラグインを書いてみた

投稿者 okkez 2009-12-18 14:38:00 GMT

Rails Engines なプラグインを書いてみたら、いくつかハマったポイントがあったので未来の自分のために記録しておく。

  1. スタイルシートや画像などが読めない
  2. マイグレーションファイルをどうしよう
  3. プラグインがロードできない
  4. Engine 側で使っている Gem を rake gems などで扱いたい

大体この四つくらいかと。それ以外は普通にアプリケーションを作れば OK

スタイルシートや画像などが読めない

これは簡単で、本家でも解説されてた。 Rake タスクを作ってコピーするだけ。

マイグレーションファイルをどうしよう

これも簡単。本家では、プラグイン側のマイグレーションを実行する方法がまだ無いから仕方ないと書かれていた。 Rake タスクを作ってコピーすればいい。タイムスタンプなマイグレーションを使っている場合はこれで問題ないはず。

プラグインがロードできない

これは調べるのに時間がかかったけど分かってみれば簡単だった。 plugin_root/init.rb や plugin_root/rails/init.rb では Rails::Initializer.run のブロックパラメータの config が見える ので(正確には違うけど)それ経由で以下のようにしておくとよいみたい。

plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
config.plugin_paths << File.join(plugin_root, 'vendor/plugins')

Engine 側で使っている Gem を rake gems などで扱いたい

プラグインと同様、init.rb 内で config.gem すれば良い。

カテゴリ ,  | タグ ,  | コメントなし | トラックバックなし

annotate_models_with_index

投稿者 okkez 2009-11-04 15:14:00 GMT

annotate_modelsにindexの情報を付加する - Hello, world! - s21g

こちらで公開されてたものに少し気になるところがあったので修正してみたのが以下。

okkez’s annotate_models_with_index at master - GitHub

修正ポイントは

  • trailing spaces を書き込まないようにした
  • magic comment を保存するようにした

です。

よかったら使ってみてください。

カテゴリ  | タグ ,  | コメントなし | トラックバックなし

Rails の fixtures の書き方を整理してみる

投稿者 okkez 2009-08-26 12:45:00 GMT

ほとんど以下に書いてあるけど、最近 fixtures の整理をしてるので書いてみる。

  • http://railsapi.com/doc/rails-v2.3.3.1/files/activerecord/lib/active_record/fixtures_rb.html

CSV とか single file とか色々あるけれどここでは YAML fixtures について書きます。

ラベルで参照する

pirates.yml
### in pirates.yml reginald: id: 1 name: Reginald the Pirate monkey_id: 1 ### in monkeys.yml george: id: 1 name: George the Monkey pirate_id: 1

上記のようなものが以下のように書けます。

  ### in pirates.yml

  reginald:
    name: Reginald the Pirate
    monkey: george

  ### in monkeys.yml

  george:
    name: George the Monkey
    pirate: reginald

polymorphic belongs_to

今日一番の収穫がこれ。

  ### in fruit.rb
  class Fruit < ActiveRecord::Base
    belongs_to :eater, :polymorphic => true
  end

  ### in fruits.yml

  apple:
    id: 1
    name: apple
    eater_id: 1
    eater_type: Monkey

こんな風に書いていたのが、こう書けます。

  ### in fruits.yml
  apple:
    eater: george (Monkey)

今、修正している Rails アプリではポリモーフィック関連をたくさん使っているのでこれはすごく便利です。

belongs_to 応用

こんなモデル定義がある場合にはちょっと変わった書き方ができます。

# in expense.rb
class Expense < ActiveRecord::Base
  has_many :expense_details
end
# in expense_detail.rb
class ExpenseDetail < ActiveRecord::Base
  belongs_to :header, :class_name => 'Expense', :foreign_key => 'expense_id'
end

普通は belongs_to :expense と書きますが、上のように書くことで header と書けるようになります。

### expenses.yml
hawaii:
  title: hawaii trip!

### expense_details.yml
chocorate:
  name: chocorate
  header: hawaii

nested set を使っているとき

nested set を使っている時はこう書けます。

### menus.yml
root:
  name: root
  parent_id:
menu_1:
  name: menu_1
  parent_id: <%= Fixtures.identify(:root) %>

$LABEL

$LABEL を使うと上の menus.yml がこうなります。

### menus.yml
root:
  name: $LABEL
  parent_id:
menu_1:
  name: $LABEL
  parent_id: <%= Fixtures.identify(:root) %>

他にもまだまだ便利な書き方はたくさんありそうです。

カテゴリ ,  | タグ  | コメントなし | トラックバックなし

Rails2.1.2 -> Rails2.3.2 で変わったこと

投稿者 okkez 2009-07-06 15:09:00 GMT

いろいろなところで出尽くしている感はありますが、それらでは触れられていないであろう事をいくつか書こうと思います。

delegate で private メソッドを呼べなくなった

2.1.2 では呼べていたのですが、呼べなくなりました。 これは send が可視性をチェックしていないためです。 ActiveRecorde::Associations::AssociationProxy#method_missing でのチェックが厳しくなっています。

gettext_activerecord になった

gettext が gettext_activerecord などに分割されました。 このことにより human_name_without_gettext -> human_name_without_gettext_activerecord などのように する必要がありました。

もう少し書くことがあった気がしたのだけど、二つしかなかったorz

でも一番驚いたのは、Rails のソースをスラスラ読んでる自分に今、気がついたことです。

カテゴリ  | タグ ,  | コメントなし | トラックバックなし