ActiveRecord で id 以外のカラムをプライマリキーにして、しかも型が int じゃないときの話

http://www.hsbt.org/diary/20120524.html#p01 見てそういえばハマった話思い出したので、次みたときなんでこうしたんだっけ、とならないように書いておく。

既に存在するDBをそのまま使う必要があったりする時で、しかもプライマリキーが int(11) とかじゃないときは、少し残念な気持ちになりながら以下のようなマイグレーションファイルを用意することになる。

class CreatePrefMaster < ActiveRecord::Migration
  def change
    create_table :pref_master, id: false do |t|
      t.column :code, :"char(2)", null: false
      t.column :name, :"varchar(64)"
    end

    execute "ALTER TABLE pref_master ADD PRIMARY KEY (code)"
  end
end

テーブル名が複数形でアンダースコア繋ぎという Rails の標準的な命名規則ではない場合もあるので、モデルのほうで table_name を指定する。

class PrefMaster < ActiveRecord::Base
  self.table_name = "pref_master"
  attr_protected :title, :body
end

さらに、config/application.rb で、db/schema.rb じゃなく db/structure.sql を吐くよう指定する。

module Aaa
  class Application < Rails::Application
    # Use SQL instead of Active Record's schema dumper when creating the database.
    # This is necessary if your schema can't be completely dumped by the schema dumper,
    # like if you have constraints or database-specific column types
    config.active_record.schema_format = :sql
    # ↑ここのコメントアウトはずす。
    # ...
  end
end

なぜかと言うと、上記の手順でマイグレーションして schema.rb を吐いたりすると以下のようなファイルができる。create_table オプションに直接 :primary_key オプションを指定しているため、Rails 的な primary_key の解釈で int な型のカラムで create table してしまう。

ActiveRecord::Schema.define(:version => 20120531162643) do

  create_table "pref_master", :primary_key => "code", :force => true do |t|
    t.string "name", :limit => 64
  end

end

テストが通らずなんでかなーと思って見たら何か意図した型でない状態になっていて気付く。
structure.sql なら安心。

CREATE TABLE `pref_master` (
  `code` char(2) COLLATE utf8_unicode_ci NOT NULL,
  `name` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Rails の標準からはずれてくるといろいろつらいおもいするのでこういう事態を避けられるなら、そっちのほうがいいと思う。

Rails のシンプルな証跡管理ライブラリ auditable

https://github.com/harleyttd/auditable

  • ちいさくてシンプル(かわいい)
  • メソッド(属性)であれば、変更を記録できる(汎用的)
  • ポリモーフィック関連とシリアライズしたものをカラムに入れるみたいなつくりで、トリッキーなことをあまりしていない(よめる)

あたらしめで、ちょいちょい更新されてるかんじ。

証跡管理は要件でブレがおおきいのかなー?という感じがするので、オシゴトでこれをそのまま使えるのかは知らない。仲間内でつかうようなものになら、気軽に利用してみてもいい気がする。

https://github.com/harleyttd/auditable/commit/d3c78b047998c83419cab6207d94f003caccc47c#README.md の時点の README の雰囲気訳。


Ruby toolbox のバージョニングカテゴリ証跡カテゴリにはたくさんの gem があるが、それぞれに様々な問題がある(これを書いている今現在も)。

  • ほとんどが古く、Rails 3.2.2 では動かない。人気のライブラリのいくつか、たとえば、papertrail と vestal_versions はたくさんの issue と pull request があげられているが、どれも処理されたりマージされたりしていない。こういう gem ライブラリのくさるほどある fork を見ると、みんなはそれぞれで gem を改善したもので良いと思ってるようだ。いい感じのものもあるのだが、自分の見たものだと、やたらとハックが多用されてたりした(最近の Rails の変更に対応するため)。そのなかで人気のあるものを試したけど、かんたんな、自分のやりたいこともちゃんとできない。
  • この手の gem は、ときとともに進化して、だんだん(すごく)扱いにくくなる
  • たいてい、データベースのカラムしかサポートしていなくて、たとえば、メソッドとか関連で動かない(か、微妙に動かない)。papertrail は has_one には対応しているが、作者はそれ以上に対応するのは簡単じゃないと言っている
  • シンプルで軽いのが欲しかった


上記のカテゴリの gem の多くはよくできてる。自分は、かんたんにモデルのアトリビュートとかメソッドの変更を扱えるような、もう完全にシンプルな gem を目標にした。そう、メソッドもですよ、アトリビュートだけじゃなくて。これが自分のライブラリのアプローチの特徴的なところです。

もし、込み入った情報、たとえば関連するレコードとかの変更を記録したいなら、関連するレコードを表現するような値を返すメソッド定義だけして、あとは、auditable にその値の変更を記録させておけばいい。

基本原理としては、

  • デフォルトでは、どのカラムについても、記録してほしくない。指定したアトリビュートとかメソッドだけでいい
  • ややこしい関連は扱いたくない。かわりにメソッドをつかう
  • データベースのカラムだけでなく、アトリビュートやメソッドの値を追いかけることに興味がある
  • シンプルで、ActiveRecord::Dirty#changes に似たものが欲しかったが、永続的に保存したい。下記の Auditable::Auditing#audited_changes の使い方を見てほしい。

導入

アプリケーションの Gemfile に以下の行を追加する。

gem 'auditable'

で、以下を実行。

$ bundle

あるいは gem コマンドから自分でインストールする。

$ gem install auditable

使い方

まず、audits テーブルをデータベースに追加する。

rails generate auditable:migration
rake db:migrate

そして、モデルで証跡管理したいメソッドのリストを audit メソッドに渡す。

class Survey
  has_many :questions
  attr_accessor :current_page

  audit :title, :current_page, :question_ids
end

デモ

テストコードにあるテストモデルでデモしたいと思います。監査対象のモデルを、たぶん、コンソールで試したいかと思うので。
詳細については、spec ディレクトリに含まれているテストケースを見てもらったほうがいいと思う。

$ bundle console
>> require(File.expand_path "../spec/spec_helper", __FILE__)
=> true

>> s = Survey.create :title => "demo"
=> #<Survey id: 1, title: "demo">

>> Survey.audited_attributes
=> [:title, :current_page]

>> s.audited_changes
=> {"title"=>[nil, "demo"]}

>> s.update_attributes(:title => "new title", :current_page => 2)
=> true

>> s.audited_changes
=> {"title"=>["demo", "new title"], "current_page"=>[nil, 2]}

>> s.update_attributes(:current_page => 3, :action => "modified", :changed_by => User.create(:name => "someone"))
=> true

>> s.audited_changes
=> {"current_page"=>[2, 3]}

>> s.audits.last
=> #<Auditable::Audit id: 3, auditable_id: 1, auditable_type: "Survey", user_id: 1, user_type: "User", modifications: {"title"=>"new title", "current_page"=>3}, action: "modified", created_at: ...>

>> s.audit_tag_with("something memorable")
   # 最新の audit にタグをつけた。それでは、s を変更してみる
   # ...
   # もし、s に何か変更を加えたとしても

>> s.audited_changes(:tag => "something memorable")
   # 上でタグつけたバージョンに対する変更が返ってくる(タグをつけたバージョンとの差分が見られる)
   # ※  s.audited_changes は、1つ前の audit との変更が返ってくる。
   # ほかのフィルターもあって、たとえば、s.audited_changes(:changed_by => some_user, :audit_action => "modified") # (これで以前の some_user の audit との変更が見られる)
   # ※ これは引数で指定したもの(:tag とか :changed_by)にマッチした audit の一番直近のものを使う。

仕組み

Audit モデル

上で見た、Audit モデルのマイグレーションファイルをこんな感じで用意する感じになる:

class CreateAudits < ActiveRecord::Migration
  def change
    create_table :audits do |t|
      t.belongs_to :auditable, :polymorphic => true
      t.belongs_to :user, :polymorphic => true
      t.text :modifications
      t.string :action
      t.timestamps
    end
  end
end

推察できるとおもうけど、audits.modifications は、監査(証跡)対象の属性のキーと値の表現をシリアライズしたものになる。

誰が、どのアクションで変更したのか?

誰がそのレコードを変更したのかとっておきたいならば、こんなかんじで changed_by 属性にそれを入れてあげればいい:

# `attr_accessor: changed_by` は、gem で Survey クラスで定義される
>> @survey.update_attributes(:changed_by => current_user, # あと他に attributes があればそれも)
# すると @surveys.audits.last.user は current_user がはいってる
# もちろん、changed_by に値を入れて save でも動く。

actioncreate または update だけ、レコードの操作に対応して入るが、それを仮想的な属性で上書きでき、change_action でよべる。(change_actionaudit_action に変更されてる気がする)。

>> @survey.changed_action = "add page"
>> @survey.update_attribute :page_count, 2

以上が、自分のできる README ドリブンなアプローチでした。またきます。

TODO

  • API を改善する(まだ扱いづらい) -- 良い書き方があったらください
  • 提案とフィードバックをもらう
  • README の更新

たとえば、今、変更はシリアライズして audits.modifications カラムに入れているが、複数の監査(証跡)対象をそれぞれ保存したいときはどうしたものか。こういう感じの書き方でサポートしようと思ってる:

# audits.trivial_changes カラム(かんたんに追加できる) にスナップショットを入れておく。
audit :modifications => [:method_1, :method_2], :trivial_changes => [:method_3, :method_4, :method_5]

Contributing

提案を歓迎します。Github で issue をあげてください。

あと、pull require も歓迎です!

  1. fork する
  2. feature ブランチをつくる (git checkout -b my-new-feature)
  3. 変更をコミットする(git commit -am 'Added some feature')
  4. ブランチに push(git push origin my-new-feature)
  5. Pull Request する


詳細はこちら: https://github.com/harleyttd/auditable

rspec でふつうにテストを書く方法しりたい

先日の railsdevcon 以後、ちゃんとテスト書くようにしはじめたものの、何をお手本にしたらいいものかわからず、手探りでテスト書いてるので、ふつうのテストの書きかたを知りたい。

だんだんテストコードじゃないのが増えてくる

テストのヘルパー的メソッドが増えてきたら、/spec/support に別のモジュールでわけるようにしてるけど、こういう使いかたでいいのかなー?

module HogeModelTestHelper
  FIXTURES = [:hoges]
  def self.included(closure)
    closure.extend Helper
    closure.send :include, Helper
    closure.send(:fixtures, *FIXTURES) if closure.respond_to?(:fixtures)
  end

  module Helper
    # なんかいろいろ書く
  end
end

spec のほうで呼んでつかう。

describe Hoge do
  include HogeModelTestHelper
  context 'ほげふがなとき' do
    subject { create_hoge }
    it { should be_valid }
    # とかなんとか
  end
end

実際にテストしてるのじゃないコードは、わけちゃってる。

携帯からのアクセスとかをテストする方法がわからない

スタブをつかうべきなのかもしれないと思いつつも、jpmobile がいろいろいいかんじにいろいろやってるので、スタブだとちょっと大変かも、と思ってるところ。仕方ないので、インチキしてる。

class ActionController::TestRequest
  def set_env(key, value)
    @env[key] = value
  end
end

とか書いて /spec/support においておいて、コントローラのテストでインチキをする。携帯の UID をつかった機能のテストとかで使う。

@request.set_env('HTTP_X_DCMGUID', uid)

あと、UA を偽装した風のリクエストをなげたりするときは、以下のようなインチキをしたりしてる。

module MobileAccessTestHelper
  private
  [:get, :put, :post, :delete].each do |http_method|
    define_method("#{http_method}_by_mobile") do |*args|
      set_mobile_ua
      send http_method, *args
    end
  end

  def set_mobile_ua(ua = "DoCoMo/2.0 SH902i(c100;TB;W24H12)")
    @request.user_agent = ua
  end 
end


ナイステスト情報しりたい。

プラグイン整理した

あたらしい mac という機会なので、vim プラグイン整理した。


あと homebrew つかってみてる。あと macbook air はケチらず 128GB のモデルにしたほうがいい。64GB すぐ容量カツカツなりそう。

railsdevcon2010

http://railsdevcon.jp/ いってきた。

感想

  • おもしろかった!
  • 非常に参考になりました(真面目)
  • インターネットでは、Rails ってけっこう使われてるのかなーという雰囲気は感じるけど、実際どうなんだろうか、と思ってた。今日行って、実際けっこう使われてる雰囲気を感じて何故かびびった。
  • テスト駆動とかわりと普通にやってる、みたいな雰囲気が会場にあって、びびった(テストあんま書けてないですお)
  • 自分のまわりではドリコムの onk さんのセッションナイス!みたいな感じだった(内容すごく濃かった)
  • rails のカンファレンスとか勉強会含め行くのはじめてだったしテンションあがった
  • お金はらってでもいきたいと思った
  • ラクルさん太っ腹
  • 運営のみなさんおつかれさまでした

以下メモ

ほぼスライドかきうつしてる状態なので、あんま意味ないこれ。

渡米して感じたこと (@masuidrive)
  • 渡米の理由→英語ができないから
    • 2006 railsconf 行って1週間ほどいたら言ってることわかるようになってきた
    • むこうでくらしたら英語できるようになるんじゃね?
  • ワイズノット民事再生
  • Big Canvas
  • seattle.rb
  • いろいろあった
  • 2年半居たけどしゃべれるようにならなかった
  • 身振り手振り、ホワイドボードのスキル伸びる
  • 仕様の交渉 (交渉技術、語学で負ける)
  • アメリカの ruby/rails 事情
    • ruby == rails
    • $100k - $175k (spec, cuke, nosql, git, 3+年以上の ruby 経験…)
    • スタートアップで ruby
  • 英語できてないから、またいけるならいきたい
  • 住環境いいし、車社会だけどいい
  • 働く環境自由(フレックス、リモート、犬ok... 交渉次第)
  • 家で夕食後に働いてるひとおおい(家で)
  • いつクビになるかわからない、でも就職先は沢山ある(らしい)
  • 一度海外でると日本に縛られない(日本じゃなくてもいいじゃんおもう)
  • rails 世界で通用する
  • Appcelerator(Titanium Mobile)
  • rails 検定の問題つくってる
rails 情報源の歩きかた (@knsmr)
  • @IT 編集部
  • 英語だと情報いっぱいある
  • 公式サイト、screencast公式ブログ、edgeRails、公式レポジトリ
  • プラグイン、ウォッチ数200くらいこえてるといいかんじ
  • プラグインナイスなのは? → Asakusa.rb, rubygems.orb, ruby tools ...
  • railscasts (asciicasts)
  • Gregg の動画ナイス(クオリティたかい)
  • RailsTutorial.org (html, pdf, 動画)
  • rails wiki
  • Confreaks (カンファレンス情報)
  • カンファレンス参加するのがいい
  • yehuda katz → jquery やりたいらしい
  • StackOverflow よくぐぐるとでてくる(けっこうピンポイント)
  • rails hub (12/22)
とあるソーシャルアプリの開発運用 (@onk)

課題

  • API 利用
    • OAuth
    • 招待、メッセージ、ポイントなどなど
  • 人口爆発
    • mixi アプリとか それぞれ 2000万とかくる
  • 短納期
    • 2ヶ月とか
    • 月2リリース
  • API 利用
  • 人口爆発
    • 負荷
      • アクセス数
        • 巨大SNSだからスモールスタートさせてくれない
        • 週10万ユーザ、月で30万ユーザとか
      • データ量
        • 1 click で1レコード増えるアクション(あしあととか)
        • db なんとかする
          • 更新系(マスタ)、参照(slave)
          • マゾヒズムの gem 利用(マスタ/スレーブという関係…)
          • クエリキャッシュの罠とかある(マスタ更新したのにスレーブでデータ古いとか)
          • db縦分割
            • リレーションで使えない(使わない)
          • 横分割
          • sharding octopus(3対応してる)
          • MongoDB で解決?
      • 負荷テスト
        • 50万ユーザが3ヶ月遊んだ装丁
        • faker でテストデータ
        • スループット、ディスクIO、コネクション(表、DB)
        • 最速レスポンス
          • API, 非同期…など
          • 5秒以内
          • 非同期処理 resque, delayed_job
          • resque
            • 管理画面よい、アプリでバッチできる
          • bulk insert
      • 手抜きしたところはたいていボトルネックになる
        • count → カウンタキャッシュ, like やらないなど
      • 正しく web アプリをつくる
        • エンジニアの甘えゆるさない
  • 短納期
    • ドキュメントへらすように
    • まよわないように(rails っぽいか? rails らしく)
    • 超高速 PDCA サイクル
    • P, C をはやくようにする
      • Plan
        • いつでもかんがえてる
      • Check
        • はやいお
    • いつでもログ見られるように
      • scribe
      • scribe サーバ間でいいかんじに
      • 転送量、データ pack して送るなどしてく予定?
Railsプロジェクトを成功させるために現場ができること (@ukstudio)
  • タイトルの成功 → なに → お客さんが満足
  • 要望とそれをコードなどで実現するぶぶん をどれだけはやく、高品質にできるか
  • 技術的負債はいつか返さないといけない
  • バグ対くりかえすプロジェクトは負債の利子返すだけでいっぱいいっぱい(自転車操業)
  • 開発をいそいでしまう原因(仕様かわる、納期はやまる、あとなんか1つ)
  • 技術的負債をなくす!
  • テスト書こう
    • テストないと変更時こまる → 負債
    • テスト駆動(設計含む意味)
    • モジュール間の依存をなるべくへらす
  • リファクタリングしよう
    • 返せる負債はすぐ返す
    • 後ではかえせない(後になったら他のタスクにおわれてる)
    • 未解決の負債の解決に週の20%(とかいくらか)をあてるなどするって決める
    • バージョン管理
  • テストの資産価値
    • 上位のテストで動作を保証
    • 下位のテストの変更を自由に
    • cuke とかで要求仕様にそってればユニットテストすてられる
    • テストコード、どやってのこしてくか
    • 書いてるひとの想定外のテストは書けない(設計能力に依存)
      • レビュー、設計の勉強とかはやはり必要
  • スキニーコントローラ、ファットモデル
    • モデルに寄せないとテスト書きにくいだろjk
    • rails 、モデルにひきずられやすい感
      • クラスの責務を考える
    • rails らしく → rails の機能をうまくつかえてるか
      • rails らしさを共有してればメンバ間での無駄へる(チームでのコーディング規約みたいな)
      • rails guides 読むべき
  • 負債とスケジュール、トレードオフ
  • 営業、機能ふやしがちで使われない機能できたりして負債(パッケージとかだととくに)
  • お客の要求をちゃんと理解してないと使わない機能できたりして負債が
現実の世界で "はじめる!Cucumber" (@moro)
  • Cucumber について
    • ドキュメント(お客読める)をテストとして実行できる ナイス
  • 現実
    • 日本語の情報 → あります
      • 達人出版の cuke 本
    • 今のプロジェクトに適用する作戦が思いつかない
      • 簡単なところから少しずつ
        1. GET / (トップページからやってみる、テストの練習としても)
        2. そこからリンクたどってみる
        3. POST してみる
      • 今やってるタスクのテスト書いてみる(関心あることのほうが書ける)
    • 日本語のシナリオの使いみち
      • よみましょう(よみやすい日本語だし)
      • 実行できなくてもよみやすい振舞(読んだひとがイメージできる)を書く(だいじ)
      • 対話のきっかけに(イメージしりたいお客、タスク見積たいメンバ、実装の手掛かりわかりたい自分)
      • そとからテスト書いて実装してテストパスしてくのが正しい一歩?
        • それっぽい cuke, spec, 実装, テストパス…みたいなとこからはじめる
      • 最終的になにしたいのか、という振舞書けば、何すればいいのか、自分の理解の助けになる(だいじ)
  • うごくようにするには
    • 最低限のエラーとって、表記揺れ正して、ステップ定義追加して、ちょこちょこ実装、テストの修正
  • cucumber のリズム
    1. プロダクトオーナーと会話、やることおおまかにきめる
    2. cuke かきながらふるまい考える(かきながらかんがえる)
    3. rspec かく
    4. 実装する
    5. cuke とおす
    6. テストのバリエーションふやしたり、つめたり
  • お客に見せるか? → 見せる
Rails Add-onsで楽々開発 - youRoomを題材に - (@mat_aki)
  • skip のひと(SonicGarden アジャイルなところ)
  • rails addons → サービスやプラグイン
  • youroom
  • youroom つかってるものなど紹介
    • cuke
      • グリーンならリリースできる
      • 低コストな品質保証
      • 肝心なところは徹底的して書く(ユーザ登録みたいなの)、バリデーションひっかかる系とかも
      • ajax つかってる部分のテストがむずかしい
    • OmniAuth
    • SearchLogic
      • plugin
      • 検索機能をシンプル・DRY に
      • activerecord 拡張してるので rails バージョンアップで不具合おこりうる(おきた)
    • google docs (gdata api)
      • アプリケーション以外のデータ(ユーザ数のスナップショット)
      • アプリケーションにはアプリケーションのデータだけ
    • github
      • 有料なプライベートリポジトリのフォークは無料でプライベートに(ウラワザ)
    • amazon
      • ec2
      • → heroku は?
        • "rails アプリケーションの" ホスティング
        • ec2/s3 とかのほうが運用ノウハウあったし、メール送信とか、delayed_job でコストかかるらしいので
    • hoptoad
      • アプリケーションエラー通知/管理サービス
      • web app のエラーだけじゃなくて、cron や delayed_job のエラーも収集するように工夫した
        • batch logger, notifier 的なかんじかなー?
    • pivotal tracker
    • yourooom
      • グループの情報共有、どこからでもアクセスできてスッピードアップ
      • API もあるので、ここで一括して情報をあつかえる
      • ドッグフードを食う的ないみもあるのかなー?
  • プラグイン、サービスを利用して、低コスト、高速、高品質な開発
初めてがRuby (パネルディカッション)
  • 紹介など
  • 松田さん → 新人だったひとたちに質問
    • 研修とか
      • liverevolution:
        • 新人から内定者に教育
        • たのしい Ruby
      • grooves:
        • OJT(これやってみようかからはじまるスタイル)
        • github (プライベート)で管理、リポジトリで先輩とかからツッコミ
    • たいへんだったこと
      • ruby の勉強がアプリケーションでの利用へのイメージがつかない (ruby の先)
      • rails3(ベータ)で開発してたので、フレームワークのバグなのか、自分のバグなのか
      • SQL, HTML, プログラムやらなんやら、最初からいろいろ、何やってるのかわからなくなる
        • ひとりでいろいろできるようになるので良い
        • できるひとに rails の文化をつくってもらうことで、ひっぱってもらう
      • 1系のメンテ、機能追加、それはそれで大変 → カオス…
    • ブレイクスルー
      • デバッグのコツつかんだとき
      • モデルのコード見てオブジェクト指向つかんだ
      • クエリメソッドでオブジェクトに問いかけてるの見てオブジェクト指向より理解できた、いいコード書けるようになった
      • OJT で学ぶことがある(こういうことかー、みたいな)
    • はじめてが ruby 、どうだったか(まとめ)
      • 初学者(はじめてが ruby)にとってフレンドリー(よみやすい)
      • ruby やってるひと、いいひとでよかったです (matz ナイス → ruby ナイス → 使ってるひとナイス!)
      • いろいろ書いてみてたのしい、リファクタリングたのしい期
      • irb ですぐためせる(速攻フィードバック)、たのしめる
      • rails イイナー、直感的に書いて、やりたいことすぐできる(うらでなにしてるかわからない時点でも。いずれとっかかりになる)、rails たのしい
    • 質問
      • さいしょから知っときたかったことは? → ドキュメント先に読め(ぐぐる前に、一時情報を最初に)

OS X でなんか Ruby の openssl のエラーでて困ったとき

ねぎ式: rubyでSSLを使った接続を行うとcertificate verify failedと言われてしまう。 のとおり、そのまま、Debianの/etc/ssl/certsからごっそりファイルを持ってきて/opt/local/etc/openssl/certs/にコピーしたらエラーでなくなった。正解よくわからない。

Ruby で del.icio.us からはてなブックマークへデータを移行する感じのスクリプト

100に満たない数のブックマークでは一応動いてる感じだったけど、ちゃんと動くかは保証できない。

参考にしたところ

CPAN の使いかたがわからなかった…。なので Ruby
my $cache = file('delicious.xml'); の部分がよくわからなかったので、いったんファイルに書きだしてみた。

rubygems, atomutil なので、sudo gem install atomutil とかやって atomutil を導入しておく必要があります。atomutil については Atompub を。
あとよくわからないけど、Bad Content Type: application/x.atom+xml; charset=utf-8 とか言われるけど、スルーした。

#!/usr/bin/env ruby

require 'open-uri'
require 'rexml/document'
require 'rubygems'
require 'atomutil'

hatena_user = '<hatena username>'
hatena_pass = '<hatena password>'
del_user = '<del.icio.us username>'
del_pass = '<del.icio.us password>'

del_api    = 'https://api.del.icio.us/v1/posts/all'
hatena_api = 'http://b.hatena.ne.jp/atom/post'

file = 'delicious.xml'

unless File.exist?(file)
  open(del_api, :http_basic_authentication => [del_user, del_pass]) do |io|
    open(file, 'w'){|f| f.write(io.read) }
  end
end

content = open(file){|f| f.read }
doc = REXML::Document.new(content)
posts = doc.elements.to_a('posts/post')

auth = Atompub::Auth::Wsse.new(:username => hatena_user, :password => hatena_pass)
hatena = Atompub::Client.new(:auth => auth)

posts.reverse.each do |post|
  href     = post.attributes['href']
  tag      = "[" + post.attributes['tag'].split(' ').join('][') + ']' unless post.attributes['tag'].empty?
  extended = post.attributes['extended']
  summary  = tag.to_s + extended

  link = Atom::Link.new(
    :rel  => 'related',
    :type => 'text/html',
    :href => href
  )

  entry = Atom::Entry.new(
    :title   => 'dummy',
    :link    => link,
    :summary => summary
  )
  hatena.create_entry(hatena_api, entry)
  print "posted: #{href}\n"
end

もしも、なんかブックマークの登録のときミスって再実行しようとしたとき、また delicious にとりにいかないように、delicious.xml というファイル作ってるんだけど、つまりこのファイルがあると最新の delicious のデータじゃなくてローカルにあるデータを使う。なので最新のデータとってきて使いたい場合は delicious.xml を消す必要がある。あとインポート終わったら delicious.xml はいらないので消して。