CoreDataを使う1。基本的なオブジェクト。

iphoneアプリでデータの永続化にCoreDataの使用について、ちょっと整理していきましょ。
(これまで使ってきたDB接続のインターフェイスとは考え方が違うのでちょっとわかりにくかったこともあるので。)
使用している開発環境は、「XCode 3.2.1」です。

アプリケーションの作成

プロジェクトのタイプとして「navigation-based Application」を選択、「Use Core Data for storage」をチェックしてプロジェクトを作成するとCore Dataを使うためのテンプレートソースなどを作成してくれます。
今回をそれを利用します。

基本的なオブジェクト

「Core Data」を使用するさいの基本となるオブジェクトを生成するコードが、アプリケーションのデレゲートクラスに作られます。
このクラスのヘッダーファイルは以下のようなものになりますが。

@interface NoteAppDelegate : NSObject <UIApplicationDelegate> {
    
    NSManagedObjectModel *managedObjectModel;
    NSManagedObjectContext *managedObjectContext;	    
    NSPersistentStoreCoordinator *persistentStoreCoordinator;

    UIWindow *window;
    UINavigationController *navigationController;
}

@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

- (NSString *)applicationDocumentsDirectory;

@end

「Core Data」に関係あるのは、以下の属性です。

NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;	    
NSPersistentStoreCoordinator *persistentStoreCoordinator;
NSManagedObjectModel

これは、各EntityやEntity同士の関連を保持、提供するためのモデルオブジェクトです。今回のプロジェクトのテンプレートでは、この定義を「xdatamodel」という拡張子のモデルファイルから読み込むようになっています。
以下は、その自動生成された実装部分です。

/**
 Returns the managed object model for the application.
 If the model doesn't already exist, 
 it is created by merging all of the models found in the application bundle.
 */
- (NSManagedObjectModel *)managedObjectModel {
	
	if (managedObjectModel != nil) {
		return managedObjectModel;
	}
	managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];    
	return managedObjectModel;
}
NSPersistentStoreCoordinator

これは、永続化オブジェクトです。上記のモデルと実際のStorage(SQLiteなどのStorage)を関連づけて永続化を行う機能を実現します。
多分XMLなど他の形式をStorageにすることも可能なのでしょうけど、今回のプロジェクトのテンプレートでは、SQLiteが使われるようになっています。多分これが、標準的でベターな方法なのでしょう。
以下が実装部分です。
「addPersistentStoreWithType:configuration:URL:options:error」メッセージにおいて データベースファイルとして、アプリケーションディレクトリのトップのデータベースファイルを具体的なStorage、タイプをNSSQLiteSoterTypeが指定されています。

/**
 Returns the persistent store coordinator for the application.
 If the coordinator doesn't already exist, it is created and the application's store added to it.
 */
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
	
	if (persistentStoreCoordinator != nil) {
		return persistentStoreCoordinator;
	}
	NSURL *storeUrl = [NSURL fileURLWithPath: 
							[[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"Note.sqlite"]];
	
	NSError *error = nil;
	persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] 
							initWithManagedObjectModel:[self managedObjectModel]];
	if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType 
							configuration:nil URL:storeUrl 
							options:nil error:&error]) {
		NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
		abort();
	}    
	
	return persistentStoreCoordinator;
}
NSManagedObjectContext

データベースから取得したデータオブジェクトを管理したりするメモリ内データベースのようなもの。
以下、自動生成された実装部分です。上記の永続化オブジェクト(NSPersistentStoreCoordinator)との関連づけが行われます。

/**
 Returns the managed object context for the application.
 If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
 */
- (NSManagedObjectContext *) managedObjectContext {
	
	if (managedObjectContext != nil) {
		return managedObjectContext;
	}
	
	NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
	if (coordinator != nil) {
		managedObjectContext = [[NSManagedObjectContext alloc] init];
		[managedObjectContext setPersistentStoreCoordinator: coordinator];
	}
	return managedObjectContext;
}


とりあえず、準備部分をみたところで、続きはまた今度。

Class::Stdを使用したインサイドアウトクラスの作り方の基本

Perlベストプラクティスを参照してClass::Stdを利用したインサイドアウトクラスを使うようにしてみたので、基本のところを整理しておきます。

前提など

確認環境は、perl5.8.8です。
Class::Stdを使用するのでcpanよりインストールします。
$sudo cpan install Class::Std

インサイドアウトクラス、Class::Stdを使用する目的などですが..

インサイドアウトクラスを使うことによりインスタンス属性へのカプセル化をする。通常のブレスされたハッシュを使う方法だと、ハッシュでのアクセスと同様の方法で属性にアクセスできてしまうので、属性をレキシカルなハッシュで管理する。
インサイドアウトクラスを使うと属性ごとのハッシュでのインスタンスデータの管理が行われる。インスタンス破棄時にそのハッシュデータのクリーンが必要ななるが、個々にたっているとメンドーなことになりので、Class::Stdを使って、このインスタンスのクリーンを自動化する。
継承を行うようになると(特に多重継承)、イニシャライズ、クリーンアップの順番などがややこしくなるのでClass::Stdを使用して、各階層個別のイニシャライズ、クリーンアップが適切に呼び出されるよう自動化。

実装例

とりあえず、コード例としては、以下のような2つの属性をもつ簡単なものを。

use strict;
use warnings;

package Cart::Item;
use Class::Std;
{
    my %item_id :ATTR;
    my %quantity :ATTR;
    

    sub BUILD {
        my ($self, $ident, $args_ref) = @_;
        $item_id{$ident} = $args_ref->{item_id} ? $args_ref->{item_id} : '';
        $quantity{$ident} = $args_ref->{quantity} ? $args_ref->{quantity} : '';
    }
    
    sub DEMOLISH {
        my ($self) = @_;
        print($self->item_id . " demolished \n");
    }

    sub item_id {
        my $self = shift;
        $item_id{ident $self} = shift if @_;
        return $item_id{ident $self};
    }

    sub quantity {
        my $self = shift;
        $quantity{ident $self} = shift if @_;
        return $quantity{ident $self};
    }


    sub to_string() {
        my $self = shift;
        "item_id=" . $self->item_id
        . ",quantity=" . $self->quantity 
    }

}

実装に関するメモなど

「use Class::Std」として、Class::Stdをimportする。これにより、new、DESTROYなどのインスタンス構築、破棄に関するメソッドなどのここで定義したパッケージへのエクスポートがされる。
クラスの定義本体はカプセル化されるよう、レキシカルスコープ内に入れる。
インスタンスの属性は、Blessされたハッシュを使うのではなく、個々にハッシュとしてして宣言。インスタンスのIDをキー、インスタンス値をするHashでのインスタンス属性値管理を行うことになる。そして、このハッシュには:ATTR属性マーカーをつける。この:ATTRをつけることにより、ここで宣言した属性ハッシュを一括して管理できるようになり、インスタンス破棄時に属性ハッシュからの破棄が自動的に行われるようになる。( 属性マーカ宣言されたときに起動される「MODIFY_HASH_ATTRIBUTE」ハンドラー - Class::Stdで定義 - によって)。
インスタンスのイニシャライザはBUILDという名称で定義する。Class::StdからエクスポートされるnewメソッドがこのBUILDが呼び出される仕組みとなっていて、継承を行ったときも適切な順番でこのBUILDイニシャライザが呼び出されるようになっている。また、引数はインスタンスインスタンスのID,newに渡された引数(Hashへの参照として渡される)となる。
インスタンスの破棄は、DEMOLISHという名称で定義する。Class::Stdからデストラクタ(DESTROY)がエクスポートされる仕組みになっていて、このデストラクタからDEMOLISHが呼び出されるようになっている。インスタンス属性ハッシュからの破棄は自動的に行われるので、単なる破棄でない(ファイルのクローズなど)もののクリーンアップの処理をこのDEMOLISHで行うことになる。これらのクリーンアップの処理は継承を行ったときも適切な順番で行われるようになっている。
インスタンス属性値へのアクセスは、属性ハッシュにからインスタンスIDをキーにして行うことになる。インスタンスIDは、Class::Stdで定義されているidentメソッド(=Scalar::Util::refaddrメソッドへの参照)で行うことができる。


以下、確認例。コンストラクタへの引数は、ハッシュへの参照となる、

my $item1 = Cart::Item->new({item_id=>'A-1', quantity=>2});
print($item1->to_string . "\n");
$item1->quantity(4);
my $item2 = Cart::Item->new({item_id=>'A-2', quantity=>3});
print($item1->to_string . "\n");
print($item2->to_string . "\n");

インスタンス値の参照、更新がうまくいっていること、クリーンアップメソッド(DEMOLISH)が実行されていることが確認できる。

attachment_fuプラグインの保存先を変更

前回、attchment_fuでとりあえず画像アップロードをしてみたが、

今回、保存先と画像を参照するurlを変更する実装を行っていみる。
アプリケーションとは別のディレクトリで画像を管理したいということと、
railsではなくapacheで画像のリクエストを処理させるということが目的。
対象としたrailsは2.0.2。


基本としては、画像をあつかうmodelでattachment_fuのファイルストレージ(file_system_backend.rb)で定義されているメソッドのオーバーライドすることと、
初期化スクリプトを使ってパスとurlの設定を行えるようにすることにより対応。

モデルの定義

今回、Photoというモデルを画像を扱うモデルとして作成。以下のような実装を行う。

Configというネストしたクラスを定義。

Configというネストしたクラスを定義。さらにモデル側にそのインスタンス参照するためのconfigというスタティック属性を定義。
ここに、ベースとなるurl,格納ディレクトリのベース、対応する画像が無いときに表示する画像の相対パスを定義。
config/initializer/photo.rbで各属性へ値を設定することを想定した定義である。
なお、Configをインスタンス化した後ででconfig/initializer/photo.rbをloadしているのは、development環境でrailsを起動している場合、
このモデル自体が、リクエスト毎に再読み込みされるので、この初期化スクリプトも毎回読み直してやる必要があるから。

Configされた属性を返すメソッドをモデルに定義

上記したConfigの属性を参照するメソッド(base_url,base_path, no_image_filename)を定義。属性のアクセスの記述を簡易にする程度の目的。

full_filenameをオーバーライド

attachment_fuのファイルストレージ(file_system_backend.rb)で定義されているfull_filenameをオーバーライド。
このメソッドは、ファイルを格納するファイル名をフルパスで返すもの。Configの属性で定義したベースパス下のパスになるよう定義。

public_filenameをオーバーライド

attachment_fuのファイルストレージ(file_system_backend.rb)で定義されているpublic_filenameをオーバーライド。
これは、画像を参照するurlを返すメソッド。Configの属性で定義したベース以下のurlになるよう定義。

#
# 商品画像のモデル
# attachment_fuプラグインを使用して、アップロード、画像表示に対応
#
class Photo < ActiveRecord::Base

  # モデルのConfiguration情報を格納
  # 以下の属性を含む
  # <tt>base_path</tt> - 画像を配置するベースパス 
  # <tt>base_url</tt> - 画像を参照するurlのベース
  # <tt>no_image_filename</tt> - No image画像の相対パス
  class Config
    attr_accessor :base_path, :base_url, :no_image_filename
    def initialize
      puts "init"
      @base_url = "http://localhost"
      @base_path = '/var/www'
      @no_image_filename = File.join( "/images/thumbs", "noimg.gif")
    end
  end


  # モデルのconfiguratioのインスタンス
  @@config = nil
  def Photo.config
    if @@config.nil? then  
      @@config = Photo::Config.new 
      load(File.join(RAILS_ROOT, 'config','initializers', 'photo.rb'  ))
    end
    @@config
  end

  # attachment_fuのオプション設定
	has_attachment :content_type => :image, 
                   :storage => :file_system, 
                   :max_size => 1.megabyte,
                   :thumbnails => { :thumb => '100x100>', :small => '50x50>' },
                   :path_prefix => "photos"
  
  # アップロード時のvalidationをすることを指定
  validates_as_attachment

  
  # No image画像の相対パス
  def self.no_image_filename
    @@no_image_filename = Photo.config.no_image_filename
  end
  
  
  # 画像を配置するベースパス
  # Used as the base path that #public_filename strips off full_filename to create the public path
  def base_path()
    @base_path ||= Photo.config.base_path
  end
  
  # 画像を参照するurlのベース
  def base_url()
    @base_url ||= Photo.config.base_url
  end
  
  # Gets the public path to the file
  # The optional thumbnail argument will output the thumbnail's filename.
  def public_filename(thumbnail = nil)
    base_url + full_filename(thumbnail).gsub(%r(^#{Regexp.escape(base_path)}), '')
  end
  
  # Gets the full path to the filename in this format:
  #
  # <base_path>/<path_prefix>/<画像IDの5から8桁>/<画像IDの1から4桁>/<画像名>のようになる。
  #
  # override from Technoweenie::AttachmentFu::Backend::FileSystemBackend
  def full_filename(thumbnail = nil)
    file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
    File.join(base_path, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
  end

    
end

初期化

/initializer/photo.rbでConfigurationを行う。

Photo.config.base_path = "/Library/WebServer/Documents"
Photo.config.base_url = "http://localhost"
Photo.config.no_image_filename = File.join( "/images/thumbs", "noimg.gif")

/initializerのファイルはrailsの起動時に読み込まれることになっている。
ただし、上にも書いたように、development環境で動作しているときは、アプリケーションの各クラスはリクエスト毎にロードされるので、
この初期化ファイルをモデル側で明示的に読み込まないといけないということになってしまっている。

結果

urlは//<画像IDの5〜8桁>/<画像IDの5〜8桁>/<ファイル名>となる。
たとえば、以下のようになる。
http://localhost/photos/0000/0013/hoge.jpg

格納場所は//<画像IDの5〜8桁>/<画像IDの5〜8桁>/<ファイル名>となる。
たとえば、以下のようになる。
/Library/WebServer/Documents/photos/0000/0013/hoge.jpg

attachment_fuプラグインで画像アップロード

前提

RMMagicをインストールしておく。

インストール

railsアプリケーションのトップでscript/pluginを実行してインストール

>ruby script/plugin install http://svn.techno-weenie.net/projects/plugins/attachment_fu

今回の例

既存の商品モデル(product)の編集ページに対してその商品写真(photo)を加えるという例を作ってみる。
http://d.hatena.ne.jp/cuspos/20071110を参照させていただきました。

Model生成とmigrate

アップロードデータを扱うモデルを作成。
今回は商品(product)の商品画像(photo)のモデルを作成。

nyaago@kyonkyon: 501 $./script/generate model photo
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/photo.rb
      create  test/unit/photo_test.rb
      create  test/fixtures/photos.yml
      exists  db/migrate
      create  db/migrate/20090607125051_create_photos.rb

それから、migration。
Photoテーブルを生成する。各カラムは、fu_attachmentでのおきまりのよう。

class CreatePhotos < ActiveRecord::Migration
  def self.up
    create_table :photos do |t|
      t.column :parent_id,  :integer
      t.column :content_type, :string
      t.column :filename, :string    
      t.column :thumbnail, :string 
      t.column :size, :integer
      t.column :width, :integer
      t.column :height, :integer
      t.timestamps
    end
  end

  def self.down
    drop_table :photos
  end
end

それから、商品テーブル側に、photoへの参照をつけるmigrate

nyaago@kyonkyon: 502 $./script/generate migration mod_product
      exists  db/migrate
      create  db/migrate/20090607125553_mod_product.rb
class ModProduct < ActiveRecord::Migration
  def self.up
    add_column(:products, :photo_id, :integer, :null => true)
  end

  def self.down
     remove_column(:products, :photo_id)
  end
end
rake db:migrate

model

photoモデルの実装を行う。

class Photo < ActiveRecord::Base
  
  	has_attachment :content_type => :image, 
                   :storage => :file_system, 
                   :max_size => 1.megabyte,
                   :thumbnails => { :thumb => '100x100>', :small => '50x50>' }

    validates_as_attachment

    def full_filename(thumbnail = nil)
      file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
      File.join(RAILS_ROOT, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
    end
end
    1. has_attachmentメソッドで画像の保存方法を指定。

今回、使っているのは以下のオプション

:content-type ファイルタイプ指定。今回は画像。他に何が指定できるのかは調べていないけど。
:storage ストレージタイプ。今回がファイルシステム。他に:db_file(db), :s3(amazon s3)がある。(プラグインのtechnoweenie/attachment_fu_/backends以下に対応するクラスが定義されている。)
:max_size ファイルの最大サイズ
:thumbnails これを指定すると、サムネイルを作ってくれる。

あと今回使ってないオプション

:resize_to リサイズして登録される。
      1. validates_as_attachmentメソッドにより、保存時のvalidationが実行されるようになる。
      1. full_filenameメソッドをオーバーライドすることにより、ファイルの保存場所を変更できる。今回、デフォルト実装のままだけど後で変更したくなりそうなので、ここの定義しているみている。


商品(product)モデル側も修正。

class Product < ActiveRecord::Base
  belongs_to :photo

  .. 省略 ..

商品からそれに関する写真を参照するためbelongs_to属性を指定。

views

以下、関係あるところだけを引用。

  <div>
    <% unless @product.photo.nil? then  %>
    <%= image_tag(@product.photo.public_filename()) %>
    <% end %>
    </div>
    <div>
    <label for="pthto">写真を追加:</label>
    <%= f.file_field :uploaded_data %>
    </div>
  
    1. モデルのpublic_filenameメソッドにより、画像へのurlが返されるので、その結果を使ってimageタグを作っている。
    2. file_fieldメソッドで ファイルアップロードタグを作っている。:uploaded_dataという名前を指定して作ってやるのが標準。そうすることにより。controller側ではPhoto.new(params[:photo] )としてmodelを作ってsaveするだけで、画像の保存までできる。

controller

参照元商品とこのphotoを保存するための定義を行う。

  # productの更新を行う。
  # Validationでエラーがあった場合は、再度入力ページ(edit)を表示する。
  def update
    begin
      if params[:id] and params[:id].to_i > 0 # for update
        product       = Product.find_by_id(params[:id])
        if product.nil? then
          redirect_to :controller => '/error', :action => 'index'
        end
        photo = prepare_photo_for_update(product)
        product.attributes = 
            params[:product].dup.delete_if { |key, value| key.to_sym == :uploaded_data }
      else          # for insert
        photo = prepare_photo_for_insert
        product = Product.new()
        product.attributes = 
            params[:product].dup.delete_if { |key, value| key.to_sym == :uploaded_data }
        product.photo_id = photo.id
      end
      # validate
      unless  photo.nil?
        unless photo.valid?
          prepare_for_edit(product)
          return render(:action => "edit")
        end
      end
      unless product.valid?
        prepare_for_edit(product)
        return render(:action => "edit")
      end
      # save
      Product.transaction {
        unless photo.nil?
          if(!photo.save)
            prepare_for_edit(product)
            return render(:action => "edit")
          end
          product.photo_id = photo.id
        end
        if(!product.save)
          prepare_for_edit(product)
          return render(:action => "edit")
        end
      }
    rescue => e
      p e.inspect
      logger.debug(e.inspect)
      return redirect_to_error(e.inspect)
    end
    
    #このあと省略(表示ページへのリダイレクトをする)
    
  end
  
  private
  
  # 商品挿入時のphotoモデル生成
  def prepare_photo_for_insert
    return nil if params[:product][:uploaded_data].blank?
    photo = Photo.new()
    # puts params[:product][:uploaded_data]
    photo.uploaded_data = params[:product][:uploaded_data]
    photo
  end

  # 商品変更時のphotoモデル生成
  def prepare_photo_for_update(product)
    photo = if product.photo_id then
        Photo.find_by_id(product.photo_id)
      else
        nil
      end
    if photo.nil?
      Photo.new(:uploaded_data => params[:product][:uploaded_data])
    else
      photo.uploaded_data = params[:product][:uploaded_data]
      photo
    end 
  end
  
  # 編集ページを開く場合の準備
  def prepare_for_edit(product)
    @product = product
    if 
      @photo = Photo.find_by_id(product.photo_id)
    else
      @photo = nil
    end
  end

上記処理についての備考。

    1. photoモデルのuploaded_dataにファイルパス(リクエストパラメータの:uploaded_data)を渡すことにより、モデルがvalidationや画像の保存を適切にしてくれるようになる。
    2. 複数モデルの更新なのでトランザクション処理をしている。ただし、ファイルシステム への画像の保存をしているので、それだけではうまく行かない。保存処理を行う前に別途validationをしている(根本的な解決ではないけど..)。
    3. リクエストパラメータからまとめてProductモデルに属性を渡すときは、photoモデルの属性は省いてやる必要がある。(delete_ifで省いている)。

課題

以下は、これから検討。

    1. 画像削除する。
    2. 保存確認ページを実装する場合はどうする?
    3. 商品に対する複数Photoの保存(has_many アソシエーション)。
    4. 一覧データ+画像ファイル群からバッチで登録する。
    5. 保存場所をアプリケーション以下とは別の場所(または別ホスト)に保存できるか?

RMagickインストールメモ

環境はMacBook Pro / MaxOSX 10.5.7
参考させていただいたのは、篳篥日記,Installing RMagick on OS X using MacPorts,Goodpic ブログなど。
とりあえず,jpeg,png,gif,tiffあたりのファイルタイプを使えるようにインストール。

png,jpeg, freetype

/usr/local以下にインストール済み

tiffライブラリをインストール

http://dl.maptools.org/dl/libtiff/よりダウンロードして展開してインストール

>./configure
>make
>sudo make install

これで、/usr/local以下にインストールされる

ghostscriptライブラリのインストール

http://pages.cs.wisc.edu/~ghost/よりダウンロードして展開してインストール

>./configure
>make
>sudo make install

gs-fonts-std

[_ftp://ftp.t.ring.gr.jp/pub/GNU/ghostscript/]より取得して、

>tar xvzf gnu-gs-fonts-std-6.0.tar.gz
>sudo cp -r fonts /usr/local/share/ghostscript/

ImageMagic

ftp://ftp.imagemagick.org/pub/ImageMagick/ImageMagick.tar.gzよりダウンロードして展開してインストール
今回のバージョンは6.5.3.1

>./configure --disable-static --with-modules --without-magick-plus-plus --with-quantum-depth=8
>make
>sudo make install
--disable-staticは共有ライブラリのみの指定, --with-modulesは「enable building dynamically loadable modules」 (動的にロードされるモジュールを構築)ということで、RMagicで使うには、これが必要らしい。
with-quantum-depthは1チャンネルのBit数。デフォルトは16Bitであるが、そんな高画質はいあらないので8Bitを指定。

RMagick

gemの実行でOK。

$sudo gem install rmagick
Building native extensions.  This could take a while...
Successfully installed rmagick-2.9.2
1 gem installed

RMagickインストール確認

引数に元画像と作成される画像の名称を指定してのリサイズを行うサンプルをつくる実行確認。

require 'rubygems'
require 'RMagick'

image = ARGV[0]
original = Magick::Image.read(image).first
resized = original.resize_to_fit(75,100)
resized.write(ARGV[1])

i18nの整理

Rails2.2のi18nの使い方を整理しておきましょうっと。

初期化

/config/initializers/locale.rb」で基本の設定を行う。
主な設定属性は以下のとおり。

      1. I18n.load_path 翻訳テキストファイルのパス
      2. I18n.default_locale デフォルトの言語
      3. I18n.locale 現在の言語

以下、設定例。load_pathはデフォルトのまま。言語設定は、日本語

I18n.load_path += Dir[ File.join(RAILS_ROOT, 'config', 'locales', '*.{rb,yml}') ]
I18n.default_locale = "ja"
I18n.locale = "ja"

翻訳テキスト

YAML形式で作成。
上のとおり、I18n.load_pathで設定したパスのファイルが翻訳テキストとして、読み込まれる。
階層化したキーに翻訳テキストを関連づけていく。

u_scope1:
  l_scope1:
    key1: "<翻訳テキスト>"
   

翻訳テキストの取得

I18n.t」メソッドで指定したキーの翻訳テキストを取得する。
階層化したキーを「.」区切りった文字列で指定したり。
上記階層をシンボルの配列または「.」区切った表現で:scapeパラメータに指定、最下位階層をシンボルで指定するなどいくつか記法を使って取得できる。

以下の3種類の記述は同等である。

I18n.t 'u_scope1.l_scope1.key1'
I18n.t :key1, :scope => [:u_scape, :l_scope]
I18n.t :key1, :scope => 'u_scape.l_scope'

翻訳の自動生成

翻訳テキストを自動的に生成できるようにする。
i18n-generatorsというのを使う。
まず、インストール

sudo gem sources -a http://gems.github.com
sudo gem install amatsuda-i18n_generators

RAILS_ROOTにて、以下のように実行する。

./script/generate i18n ja

これで、errorメッセージ、Modelの属性、基本的な書式パターンの日本語の翻訳テキストがYAMLファイル上に生成される。

コルーチンのお勉強

プログラミングGaucheのコルーチン(19.8)内容のお勉強。

P299に載っているexitのための手続きが定義されるほうのコルーチンを定義するためのマクロ定義の解読。

(use util.queue)
(define *tasks* (make-queue) )

(define-syntax define-coroutine
  (syntax-rules () 
    [ (_  (routine yield) body ...)
      (define (routine)
        (call/cc ( lambda(return) 
                   (define (yield)
                     (call/cc (lambda (cont)
                              (enqueue! *tasks* cont)
                              (return)) ) )
                   body ...))
        ((dequeue! *tasks*))) ] 
    [ (_ (routine yield exit) body ...)
      (define (routine)
        (call/cc ( lambda (escape) 
                   (call/cc ( lambda(return) 
                              (define (yield)
                                (call/cc (lambda (cont)
                                           (enqueue! *tasks* cont)
                                           (return)) ))
                   (define (exit) 
                     (call/cc (lambda (cont) 
                                ( (enqueue! *tasks* cont)
                                 (escape) ) ) ) )
                   body ...))
                   ((dequeue! *tasks*) ) ) ) )
     ]))

まず、exitのないほう。

コルーチン

これによって返される手続き(コルーチン)では、call/ccによりコルーチン呼び出し時の継続(return変数)の取得を行う。
このcall/cc内でbodyの実行を開始する(=他の継続が呼ばれないかぎり、このbodyの実行が続けられる)。call/ccを抜けて(<=他の継続が呼ばれて)このコルーチン呼び出し時の継続に戻ったときは、実行キューから継続を取り出し実行。

yield手続き

内側で定義される他のコルーチンを呼び出す手続き(yield)では、call/ccにより継続を取得して後で呼び出されるように実行キューに入れてから、
コルーチン呼び出し時の継続を呼び出す。この結果は実行キューからの取り出し+実行となる。

exitをつける場合

抜けるための継続取得

まず、再上位にcall/ccが呼び出される。これにより取得した継続がコルーチンの実行と実行キューの継続から抜けるためのものとなる。

exit手続き

exitの定義は、再開されたときに実行されるように現在の継続をキューに入れてから、コルーチンの最上位の継続を呼び出すことにより、コルーチンの実行と実行キューの継続から抜ける。