Web用画像処理言語purlにGyazoモジュール追加

投稿者 nanki 2008-10-30 18:11:00 GMT

purl にGyazoモジュールを追加しました。

デフォルト環境にはロードされないので、自分でロードする必要があります。

Purl::Purl.new のあたり。

# app/controllers/purl_controller.rb
class PurlController < ApplicationController
  caches_page :filter

  def filter
    begin
      result = Purl::StackMachine.new(
        params[:commands], Purl::Purl.new{|p| p.load_feature Purl::Features::Gyazo}).run
      send_data(result[-2], :type => result[-1], :disposition => 'inline')
    rescue => e
      render :status => 500, :text => 'error'
    end
  end
end

コード例

/purl/(a7d96c2ff64900f507ef9c8dab99f983):gyazo:5:round:5:5:5:5:extend:5:dropshadow:to.png gyazo

参考:

決定版! Rails用画像添付プラグインpurl

投稿者 nanki 2008-10-23 10:15:00 GMT

Rails pluginとして動く言語、purlを公開しました。

purl はクライアントサイドから柔軟な画像処理を行うために開発されました。 サムネイルの生成などを行うプラグインはいくつかありますが、そのどれよりも強力で汎用性、拡張性があります。

セットアップ

Rails2.1以上では、

$ script/plugin install git://github.com/nanki/purl.git

それ未満では、

$ git clone git://github.com/nanki/purl.git -- vendor/plugins/purl

gitが無い環境なら、githubのdownloadからファイルを落として、vendor/plugins/ に配置してください。

依存ライブラリ

  • 必須: ImageMagick, rmagick
  • あった方がいい: cairo, rcairo

その他

purl のデフォルトの環境では、画像の保存にデータベースを利用します。 imagesテーブルに、blob型のdataカラムを追加してください。

# app/models/image.rb
class Image < ActiveRecord::Base
  attr_readonly :data
end

その上で、ImageController#upload などに以下のように記述します。

# app/controllers/image_controller.rb
class ImageController < ApplicationController
  def upload
    Image.create(:data => params[:data].read)
  end
end

さらに、config/routes.rbを書き替え、purl が実行されるようにします。 書く場所は先頭に近い方がいいと思います。

# config/routes.rb
  map.connect 'purl/:commands',
    :controller => 'purl', :action => 'filter', :commands => /.*/
# app/controllers/purl_controller.rb
class PurlController < ApplicationController
  caches_page :filter

  def filter
    begin
      result = Purl::StackMachine.new(
        params[:commands], Purl::Purl.new).run
      send_data(result[-2], :type => result[-1], :disposition => 'inline')
    rescue => e
      render :status => 500, :text => 'error'
    end
  end
end

そんなこんなでセットアップは終わり。

言語

purl はURL上で記述されることを前提としています。 常にワンライナーであれ!

文法

purl は逆ポーランド記法を採用しています。 データと命令を書かれた順番にスタックマシンにプッシュしていき、命令が実行された結果がスタックマシン上につまれていきます。 命令列は最初に:で区切られ、数字としてパースできるものは数字へ、()で囲われた部分は文字列へと変換されます。

ポリシー

デフォルトで用意される全ての命令は、外部の値に依存しません。 つまり、命令列が決まれば実行結果は確定します。 このポリシーにより、全ての実行結果がキャッシュ可能になります。

サンプルコード

画像をそのまま表示

次の例では、load はスタック上に既にある数字1を消費して、ID=1のImage#dataをデータベースからロードして、スタック上にプッシュします。 to.png では、スタック上の画像データをPNG形式のバイト列に変換し、mimetypeとともにスタック上に積みます。

/purl/1:load:to.png

画像の形式を変換

次の例では、画像をGIF形式に変換しています。

/purl/1:load:to.gif

画像のリサイズ

次の例では、画像を100x100にリサイズしています。 リサイズ用の命令は、何種類か用意されており、高さを一定に保つ resize.heightや、上限を指定する、resize.upto などがあります。

/purl/1:load:100:100:resize:to.png

画像の合成

次の例ではふたつの画像を合成しています。

/purl/1:load:9:load:composite:to.png

次の例では順番を入れ替えています。

/purl/1:load:9:load:swap:composite:to.png

コメント

スタックに積んだ後すぐにpopすると、単純に無視されるので、コメントとして使えます。 キャッシュのライフサイクルコントロールなどに使えると思います。

/purl/1:load:(daily):pop:to.png

図形の描画

詳しくは解説しませんが、試験的にcairoの描画機能をベースにした図形描画命令を用意しています。 ctx命令で、指定したサイズのcairoコンテキストを準備し、xtc命令で実際にサーフェースへの描画を実行します。

/purl/80:80:ctx:40:40:moveto:40:40:40:-20:20:arc.ccw:1:0.3:0:rgb:fill:60:20:4:circle:0:0:0:rgb:fill:xtc:to.png

制限

実行結果をファイルとしてキャッシュする場合、ファイルシステムによる字数制限を受けます。

拡張

拡張は命令モジュールの追加という形で行います。 詳しくはソースコード見てください。

今後

  • 無名関数的なもの。
  • 外部から直接呼び出せない命令とか?
  • エラーメッセージとか?
参考:

BenchmarkForRails導入でrakeが動かない

投稿者 nanki 2007-12-28 11:10:00 GMT
 ♞ rake test:units
(in /Users/nanki/work/...../trunk)
rake aborted!
undefined method `watch' for BenchmarkForRails:Module

これは困った。

AutoLoadingがうまくいっていないようだが、この手の問題は追跡が難しい。 とりあえず適当な箇所で、require 'benchmark_for_rails'しておけば大丈夫だが、気持ち悪いので次の日原因を追ってみた。

p追跡の結果だけ書くと、rake 実行時に読み込まれるlogs.rake冒頭でのrequireで、BenchmarkForRailsモジュールが初期化されて、本来読まれて欲しいはずの、benchmark_for_rails.rbがロードされないのが問題。

# in vendor/plugins/benchmark_for_rails/tasks/logs.rake
require File.dirname(__FILE__) + '/../lib/parsing.rb'
require File.dirname(__FILE__) + '/../lib/report.rb'

対処法はこちらの通り。

と思ったら、今朝五時に直ってるじゃないの。

参考:


Railsアプリケーションの国際化 - Globalize

投稿者 nanki 2007-08-26 10:15:00 GMT

Globalizeを使ってみる。

セットアップ

適当なRailsアプリを作って、Globalizeをインストール。

$ script/plugin install http://svn.globalize-rails.org/svn/globalize/trunk
....
$ mv vendor/plugins/trunk vendor/plugins/globalize
$ rake globalize:setup

CSV::Cellがinspectされたようなエラーが出るので、vendor/plugins/globalize/tasks/data.rake を修正する。

--- ./vendor/plugins/globalize/tasks/data.rake.orig     0000-00-00 00:00:00.000000000 +0000
+++ ./vendor/plugins/globalize/tasks/data.rake  0000-00-00 00:00:00.000000000 +0000
@@ -17,3 +17,3 @@

-    columns = reader.shift.map { |column_name| cnx.quote_column_name(column_name) }
+    columns = reader.shift.map { |column_name| cnx.quote_column_name(column_name.data) }
     column_clause = columns.join(', ')
@@ -24,3 +24,3 @@
       raise "No header defined"     unless column_clause
-      values_clause = row.map { |v| cnx.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(', ')
+      values_clause = row.map { |v| cnx.quote(v.data).gsub('\\n', "\n").gsub('\\r', "\r") }.join(', ')
       sql = "INSERT INTO #{table_name} (#{column_clause}) VALUES (#{values_clause})"

さらに、vendor/plugins/globalize/data/language_data.csv の空行がエラーを起こしているので修正。

$ rake globalize:setup
...

成功。

View翻訳

base_languageを英語に。

# config/environment.rb
Globalize::Locale.set_base_language("en-US")
$ script/console
>> "March [month]".t
=> "March"
>> Globalize::Locale.set("ja-JP")
=> #<Globalize::Locale...
>> "March [month]".t
=> "3月"
>> Time.now.loc "%A"
=> "日曜日"

ふむふむ。この辺は、gettextの方が使いやすそうかなぁ。

Model翻訳

適当なモデルを作成

$ script/generate model Product
...
# db/migrate/001_create_products.rb
  def self.up
    create_table :products do |t|
      t.column :name, :string
      t.column :manufacturer, :string
    end
  end
$ rake db:migrate
$ script/console
>> p = Product.create :name => "Pucchin Pudding", :manufacturer => "Glico"
=> #<Product:...
>> p.name
=> "Pucchin Pudding"
>> p.manufacturer
=> "Glico"

# jaで保存。
>> Globalize::Locale.set("ja-JP")
>> p.name = "プッチンプリン"
>> p.manufacturer = "グリコ"
>> p.save
=> true

# jaで読む。
>> p.reload
>> p.name
=> "プッチンプリン"
>> p.manufacturer
=> "グリコ"

# enで読む。
>> Globalize::Locale.set("en-US")
>> p.reload
>> p.name
=> "Pucchin Pudding"
>> p.manufacturer
=> "Glico"

ちゃんと動いてる。

この時、productsテーブルにはbase_languageで格納され、それとは別にglobalize_translationsテーブルに、翻訳情報を保持している。

sqlite> SELECT * FROM globalize_translations WHERE id > 7088;
id          type              tr_key      table_name  item_id     facet       built_in    language_id  pluralization_index  text                   namespace
----------  ----------------  ----------  ----------  ----------  ----------  ----------  -----------  -------------------  ---------------------  ----------
7089        ModelTranslation              products    18          name        t           2723                              プッチンプリン
7090        ModelTranslation              products    18          manufactur  t           2723                              グリコ

Object#_が定義されていたので、gettextとケンカするかと思ったけど、一緒に動いた。 偶然かもしれないけど。

screenshot

そんなこんなで、↑こんなアプリケーションができてしまう。
はたして、使う機会は訪れるのか?

追記:

  • eager loadingを行う時は、:include_translatedを使う。
  • が、対応が中途半端で、user.product_nameとアクセスしないとだめっぽい。
class User
  belongs_to :product
end

user = User.find(:first, :include_translated => :product)

user.product.name #=> NG
user.product_name #=> OK
参考:

ActiveRecord - フォーマット

投稿者 nanki 2006-12-27 21:09:00 GMT

小分けにしないとサーバが止まる・・・

model_formatter
  • 特定のカラムを、フォーマット付きでアクセス。
  • formatted_weight で、フォーマット済みのweightにアクセス。
  • フォーマット済み属性の名前は、:formatted_attr, :prefixで変更。
  • class Widget < ActiveRecord::Base
    # FormatInteger という名前のFormatterでフォーマットを指定
    # :boolean, :currency, :decimal, :integer, :percent, :phone が定義済み
    # :as には、Formatterのインスタンスも指定できる
    format_column :weight, :as => :integer

# Procを使って format_column :weight,

<span class="symbol">:formatted_attr</span> =&gt; <span class="symbol">:formatted_weight</span>,
<span class="symbol">:options</span> =&gt; {<span class="symbol">:delimiter</span> =&gt; <span class="string"><span class="delimiter">'</span><span class="content">,</span><span class="delimiter">'</span></span>},
<span class="symbol">:from</span> =&gt; <span class="constant">Proc</span>.new{|attribute, options| ... },
<span class="symbol">:to</span>   =&gt; <span class="constant">Proc</span>.new{|str, options| ... }

# ブロックによる定義も format_column :weight do

<span class="keyword">def</span> <span class="function">from</span>(attribute, options)
  <span class="comment"># ...</span>
<span class="keyword">end</span>

<span class="keyword">def</span> <span class="function">to</span>(str, options)
  <span class="comment"># ...</span>
<span class="keyword">end</span>

end end

w = Widget.new(:weight => 12345) w.formattedweight # => 12,345 # 書き込みもできる! w.formattedweight = 54,321 w.weight # => 54321

auto_convert_fields
  • attribute=を定義。
  • class MyModel < ActiveRecord::Base
    # name=, :text= の前に、TextHelper#striptags をはさむ。
    autoconvert :name, :text, :with => :strip_tags

# 同じ auto_convert :name, :text do |value|

strip_tags(value)

end end


Rails Plugin - View系2

投稿者 nanki 2006-10-05 06:55:00 GMT
MenuEngine
  • DHTMLのメニューを作る。
  • # in config/environment.rb
    module ApplicationHelper
    include MenuHelper
    end

# in app/helpers/applicationhelper.rb module ApplicationHelper include MenuHelper end

*
module MenuEngine
  # UserEngineと連携
  # trueで許可されないcontroller/actionへのメニューが表示されなくなる
  config :accesscontrol, false
  # エフェクトの設定。element はメニューを指す。
  config :onshow, "new Effect.Appear(element);"
  config :onhide, "new Effect.Fade(element);"
end
*
module ApplicationHelper
  def my_menu

item0 = <span class="constant">MenuItem</span>.new(<span class="symbol">:text</span> =&gt;  <span class="string"><span class="delimiter">&quot;</span><span class="content">Users</span><span class="delimiter">&quot;</span></span>, <span class="symbol">:controller</span> =&gt; <span class="string"><span class="delimiter">&quot;</span><span class="content">user</span><span class="delimiter">&quot;</span></span>)
item1 = <span class="constant">MenuItem</span>.new(<span class="symbol">:text</span> =&gt;   <span class="string"><span class="delimiter">&quot;</span><span class="content">List</span><span class="delimiter">&quot;</span></span>, <span class="symbol">:controller</span> =&gt; <span class="string"><span class="delimiter">&quot;</span><span class="content">user</span><span class="delimiter">&quot;</span></span>, <span class="symbol">:action</span>=&gt;<span class="string"><span class="delimiter">&quot;</span><span class="content">list</span><span class="delimiter">&quot;</span></span>)
item2 = <span class="constant">MenuItem</span>.new(<span class="symbol">:text</span> =&gt; <span class="string"><span class="delimiter">&quot;</span><span class="content">Images</span><span class="delimiter">&quot;</span></span>, <span class="symbol">:controller</span> =&gt; <span class="string"><span class="delimiter">&quot;</span><span class="content">image</span><span class="delimiter">&quot;</span></span>)

item0.items &lt;&lt; item1
[item0, item2]

end end

*
<%= enginestylesheet "menuengine" %>
<%= enginejavascript "menuengine" %>
...
<%= menu :menu => my_menu, :layout => :vertical %>
* YAMLで設定する方法もある。


Rails Plugin - ActiveRecord拡張系2

投稿者 nanki 2006-08-29 16:24:00 GMT
acts_as_modified

# restore person.restore_attributes :only => :age person.age # 100

# clear person.clearoriginalattributes :only => :age

# dbと比較 person.modified?(:reload) # true

acts_as_bookmarkable

post.add_bookmark(Bookmark.new)

だけ。 * 構成はacts_as_commentableとほぼ同じ。

acts_as_commentable
acts_as_sluggable

urlfor :controller => "articles", :action => 'show', :id => @article # => "/articles/show/76-omg-my-cat-is-so-cute-lol" * toparam というメソッドを上書きしてるらしい。

acts_as_sequenced

class Customer < ActiveRecord::Base actsassequenced

<span class="comment"># 連番</span>
<span class="symbol">:column</span> =&gt; <span class="symbol">:customer_number</span>,
<span class="comment"># 有効範囲</span>
<span class="symbol">:scope</span> =&gt; <span class="symbol">:account</span>

end

class Job < ActiveRecord::Base actsassequenced :column => :job_number, :scope => :account endこれで、Account毎に連番が。 * プラグインの形では提供されてないので、コードをlib/以下にコピペして、environment.rbでrequireしてね。 * コードからは、削除して追加すると、過去の管理番号と重複する番号が割り当てられるように見える。(これはうれしくない) * acts_as_paranoid(削除フラグで削除したかの用に見せるプラグイン)と併用できるようになっていて、併用すればこの問題は起こらなさそう。

acts_as_most_popular

# すると、頻度の高い順に3個とってこられる。 Person.mostpopularnames # => ["Jajamaru", "Piccolo", "Porori"] * nameが複数形になるってどういうことよ。

acts_as_network

alice, bob = User.find([1,2])

# bobはaliceの友達です alice.connections << bob

# bobはaliceの2です alice.connections.pushwithattributes(bob, :role => 2) alice.connections.find(bob.id).role # => 2

# bobはaliceの友達ですか? alice.connections.include?(bob) # => true

# aliceは誰の友達ですか? alice.connections.all_in

acts_as_rateable

def self.down

drop_table <span class="symbol">:ratings</span>

end end *

class YourModel
  actsasrateable
end

# 5点をつける m.rate(5) m.rating = 5

# 5点 m.rating # => 5

# 最初に見つかった3点のもの YourModel.findbyrating(3) # 3点以上のもの全部 YourModel.findallbyrating(3..-1) # 1点または3-5点のもの全部 YourModel.findallbyrating([1,3..5])

acts_as_ratable

# currentuserが5点をつけました m.rateas(5, :by => current_user)

# currentuserによって付けられた点数 m.ratingby(current_user)

m.average_rating # => 平均点 m.raters # => 採点者 * 同じユーザが同じものに採点すると上書きされる。

acts_as_popular
acts_as_searchable
acts_as_rast_indexed
acts_as_ordered
acts_as_taggable
acts_as_versioned
acts_as_paranoid
acts_as_bitfield
acts_as_deque
acts_as_attachment
better_nested_set
acts_as_threaded
acts_as_ldapable
acts_as_habtm_list
acts_as_shellable
acts_as_enumerated

Rails Plugin - ActiveRecord拡張系

投稿者 nanki 2006-04-25 23:30:00 GMT
composite_primary_keys

class Road < ActiveRecord::Base belongsto :attribute, :foreignkey => [:attributecode, :meshcode] end

class Attribute < ActiveRecord::Base setprimarykeys :attributecode, :meshcode end

Road.find(:first).attribute #=> SELECT...WHERE roads.attributecode = attributes.attributecode and roads.meshcode = attributes.meshcode ...

pessimistic locking

Account.transaction do account = Account.find(:first) account.lock # ... end

で悲観的ロックができる。 * Edgeには取り込まれたっぽい。

has_many_polymorphs

class Cat < ActiveRecord::Base hasmany :eaterpetfoods, :as => :eater hasmany :petfoods, :through => :eaterpetfoods end

class EatersPetfood < ActiveRecord::Base belongsto :petfood belongsto :eater, :polymorphic => true end

class Petfood < ActiveRecord::Base hasmany :eaterpetfoods hasmany :eaters, :through => :eaterpetfoods # これができない end *

# hasmanypolymorphs
class Dog < ActiveRecord::Base;end
class Cat < ActiveRecord::Base;end

class EatersPetfood < ActiveRecord::Base belongsto :petfood belongsto :eater, :polymorphic => true end

class Petfood < ActiveRecord::Base hasmanypolymorphs :eaters, :from => [:dogs, :cats] end

* eaterが増える度にPetfoodを書き換えないといけないのがかっこよくない。

store_multiplied
force uppercase
ActiveCrypto
UpdateHabtmAttributes
allow_habtm_primary_key
userstamp

class ApplicationController < ActionController::Base before_filter do |c|

<span class="constant">User</span>.current_user = <span class="constant">User</span>.find(c.session[<span class="symbol">:user</span>].id) <span class="keyword">unless</span> c.session[<span class="symbol">:user</span>].nil?

end end

class Post < ActiveRecord::Base belongsto :createdby, :classname => "User", :foreignkey => "createdby" belongsto :updatedby, :classname => "User", :foreignkey => "updatedby" end

active form
riff
enforce_column_limits
validates_numericality_of
ActiveSearch
Spatial Adapter
settings
autoescape
deadlock_retry
calculations
guid
where
count_limit_associations
file_column
filters_column
filtered_column

Rails Plugin - View系

投稿者 nanki 2006-04-25 23:28:00 GMT
hotkey
SimpleSidebar
google maps
InPlaceControls

<%= inplaceradio_buttons :post, :published, :choices => [[true, "Yes"],[false, "No"]] %> <!-- :choicesは、値の配列 or 値とラベルの配列 -->

<%= inplaceselect :address, :country, :choices => countryoptionsforselect %> <!-- :choicesは、値の配列 or 値とラベルの配列 or optionsfor_selectの結果 -->

* svn://rubyforge.org/var/svn/inplacecontrols

widgets
acts_as_wizard

add_step do

named <span class="string"><span class="delimiter">'</span><span class="content">Insert your dog</span><span class="delimiter">'</span></span>
controlled_by <span class="symbol">:dogs</span>
enable_actions <span class="symbol">:new</span>, <span class="symbol">:create</span>
<span class="comment"># main/finishedに飛んでもらわないと困る</span>
add_rewrite({<span class="symbol">:action</span>=&gt;<span class="string"><span class="delimiter">'</span><span class="content">list</span><span class="delimiter">'</span></span>}, {<span class="symbol">:controller</span> =&gt; <span class="string"><span class="delimiter">'</span><span class="content">main</span><span class="delimiter">'</span></span>, <span class="symbol">:action</span> =&gt; <span class="string"><span class="delimiter">'</span><span class="content">finished</span><span class="delimiter">'</span></span>})

end

add_step do

named <span class="string"><span class="delimiter">'</span><span class="content">Finished!</span><span class="delimiter">'</span></span>
controlled_by <span class="symbol">:main</span>
enable_actions <span class="symbol">:finished</span>

end end

な感じで。 * application.rbに require 'create_cat_and_dog_wizard'をお忘れなく。 * URLはそのままだと、/cats/new?wizard=create_cat_and_dogになってかっこわるいので、
# routes.rb
map.connect 'createcatand_dog/:controller/:action/:id',

        <span class="symbol">:wizard</span> =&gt; <span class="string"><span class="delimiter">'</span><span class="content">create_cat_and_dog</span><span class="delimiter">'</span></span></span></notextile></pre></div>でかっこよく。
  • wizardのURLは、
    urlfor :controller => 'cats', :action => 'new', :wizard => 'createcatanddog'
tabnav
  • タブナビゲーションを簡単に。
  • $ script/generate tabnav Main
  • <!-- layout.rhtml -->
    <%= starttabnav :main %>
    <%= @contentforlayout %>
    <%= endtabnav %>
  • class MainTabnav < Tabnav::Base     
    addtab do named 'User' # タブの名前 titled 'User!' # title属性(ブラウザによってはtooltipが出る) linksto :controller => 'user' # リンク先(url_forと同じオプション) end

add_tab do

named <span class="string"><span class="delimiter">'</span><span class="content">Admin</span><span class="delimiter">'</span></span>
links_to <span class="symbol">:controller</span> =&gt; <span class="string"><span class="delimiter">'</span><span class="content">admin</span><span class="delimiter">'</span></span>
show_if <span class="string"><span class="delimiter">&quot;</span><span class="content">params[:admin] == true</span><span class="delimiter">&quot;</span></span> <span class="comment"># 表示条件</span>

end
end

のように。 * links_toで指定されたページを表示している時は、自動的にハイライトされるが、それ以外のページでもハイライトさせたかったらhighligths_onを使う。

UJS Rails
rails-tidy
12_hour_time
live_tree
browser_filters
labeled_form_helper
label_helpers
restifarian
scriptaculous_slider
localization
css_graphs
asset_timestamping
add_query_mtime
javascript_generator_templates
us_states
wiki_engine
date_slider
head-helper
enhanced-form-tag-helper
liquid
rails_pdf
upload_progress
datebox_engine
HTML Output
syntax_highlight
theme_support
selector
safe_render
RUtils
cartographer
markaby

Rails Plugin - 開発系

投稿者 nanki 2006-04-25 23:27:00 GMT
row_version_migrations
Safe ERB
ARTS
annotate_models
assert_valid_markup
assert-valid-asset
debug_view_helper
continuous_builder
exception_notification
ar_fixtures
engines
refactor
assert_accessible
javascript-packaging
migrate_ext
Foreign Key Schema Dumper