トップ 最新

kumaryu日記

2009-07-30 できた!

_ [Ruby] IronRubyいじりのつづき

エラー表示でた!!

DLRのプロジェクトページから最新のソースを拾ってきて自前でビルドしたらちゃんとエラー表示されるようになったわー。

IronRubyの最新ソースはビルド方法がよくわからなかったのでパス。

最新じゃないといけないのかは確認してないけど、たぶん、たぶんだけど最新であることよりは自前ビルドが効いたのかもしれない。というか公式ビルドはどんな環境でビルドしてんだよー。

そんなわけでDLRの最新ソースを自分でビルドするのがおすすめです。Silverlight2で動かなくなっちゃったけど。まあ3で動くからいいや。

きっかけはIronRubyのDaily野良ビルド(ただし古い)を使ったらエラー表示が出たこと。あとIronRubyのMLで「なんかエラー出ないんですけど」→「出てるけど何言ってんの?」という感じのやりとりを発見したこと。

そりゃ開発してるんだったら最新を自前ビルドしてますわな。

で、ソース整理もできました。

バインディングをなんとかするのに参考にした*1のはこちら。How to Bind Silverlight DataGrid From IEnumerable of IDictionary by Transforming Each Dictionary Key Into a Property of Anonymous Typed Object

で、できたのがこちら。

include System
include System::Collections
include System::Reflection
include System::Reflection::Emit

module DataSource
  PropertName = /^[A-Za-z]+[A-Za-z0-9_]*$/
  @@types = {}

  class CLRClassBuilder
    def initialize(name)
      asm_name = AssemblyName.new("TempAsm#{name}")
      asm = AppDomain.current_domain.define_dynamic_assembly(
        asm_name, AssemblyBuilderAccess.Run)
      mod = asm.define_dynamic_module('TempModule')
      @builder = mod.define_type(
        "TempType#{name}",
        TypeAttributes.Public |
        TypeAttributes.Class |
        TypeAttributes.AutoClass |
        TypeAttributes.AnsiClass |
        TypeAttributes.BeforeFieldInit |
        TypeAttributes.AutoLayout,
        Object.to_clr_type) 
      @builder.define_default_constructor(
        MethodAttributes.Public |
        MethodAttributes.SpecialName |
        MethodAttributes.RTSpecialName)
      define_property(:__source__, Object)
    end

    def type
      @builder.create_type
    end

    def define_property(name, type)
      if type==TrueClass or type==FalseClass then
        clr_type = System::Boolean.to_clr_type
      else
        clr_type = type.to_clr_type
      end

      field_builder = @builder.define_field(
        "_#{name}", clr_type, FieldAttributes.Private)

      getmethod_builder = @builder.define_method(
        "get_#{name}",
        MethodAttributes.Public |
        MethodAttributes.SpecialName |
        MethodAttributes.HideBySig,
        clr_type, Type.EmptyTypes)
      il_gen = getmethod_builder.GetILGenerator
      il_gen.emit(OpCodes.Ldarg_0)
      il_gen.emit(OpCodes.Ldfld, field_builder)
      il_gen.emit(OpCodes.Ret)

      setmethod_builder = @builder.define_method(
        "set_#{name}",
        MethodAttributes.Public |
        MethodAttributes.SpecialName |
        MethodAttributes.HideBySig, nil, System::Array[Type].new(1) { clr_type })
      il_gen = setmethod_builder.GetILGenerator
      il_gen.Emit(OpCodes.Ldarg_0)
      il_gen.Emit(OpCodes.Ldarg_1)
      il_gen.Emit(OpCodes.Stfld, field_builder)
      il_gen.Emit(OpCodes.Ret)

      property_builder = @builder.define_property(
        name, PropertyAttributes.HasDefault, clr_type, nil)
      property_builder.set_get_method(getmethod_builder)
      property_builder.set_set_method(setmethod_builder)
    end
  end

  module_function
  def to_data_source(list)
    return [] if list.empty?
    d = list[0]
    sig = ''
    d.each_pair do |key, val|
      sig << "_#{key}_#{val.class}".gsub(/\-/, '_')
    end
    unless @@types.include?(sig) then
      builder = CLRClassBuilder.new(sig)
      d.each_pair do |key, value|
        if key.to_s=~PropertName then
          builder.define_property(key.to_s, value.class)
        end
      end
      @@types[sig] = builder.type
    end
    to_typed_list(@@types[sig], list)
  end

  def to_typed_list(type, list)
    list.collect {|d|
      row = Activator.create_instance(type)
      row.__source__ = d
      d.each_pair do |key, value|
        row.__send__("#{key}=".to_sym, value)
      end
      row
    }
  end
end

class Array
  def to_data_source
    DataSource.to_data_source(self)
  end
end

これで

data_grid.items_source = [
  { 'name' => 'あああ" },
  { 'name' => 'いいい" },
].to_data_source

のようにするとそれっぽくなります。この例だとnameという文字列型のプロパティを持ったオブジェクトの配列ができます。でもなんかプロパティの書き込みが上手くいってないかも。

配列の中身はHashじゃなくてStructもいけます。生成されたオブジェクトには__source__プロパティがついてて、元のオブジェクトが入ってます。

UIはあとはさくっと書けたので、次はロジック部分。サーバと通信したいんですけど、まずサーバ書かないと。

サーバはめんどいからRack単体で使えばいいかと思ったんですが、Rack単体もそれなりに面倒で、Sinatraが楽そうだったのでSinatraを使うことに。Rack単体で使っても便利なようにすれば結局Sinatraっぽくなっちゃうもんね。

*1  というかパクった