ITコンサルの日常

ITコンサル会社に勤務する普通のITエンジニアの日常です。

CSVファイルでもDynamic Fixturesが使えるのか(結論: fixtures.rbにパッチを当てれば使える)

Class: FixturesのDynamic fixtures with ERbの節に、

In these cases, you can mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing

とか書いてあるので、YAMLだけじゃなくて、CSVでもDynamic Fixturesが使えそうな雰囲気です。


まず、こんなCSVファイルを用意しました。

id,name
1,test
2,hoge
<% 10.times do |i| %>
<%= (i+3) %>,hoge<%= i %>
<% end %>

erbが埋め込まれているより前の部分までならば、正常にロードできます。
ただ、このままだと、

id,name
1,test
2,hoge

3,hoge0

4,hoge1

5,hoge2

6,hoge3

7,hoge4

8,hoge5

9,hoge6

10,hoge7

11,hoge8

12,hoge9

のようなCSVが出力されてしまい、このままFixturesに食わせると、

E:/jruby-1.1.4/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/conne
ction_adapters/abstract/database_statements.rb:73:in `transaction': ActiveRecord
::ActiveRecordError: Syntax error: Encountered ")" at line 1, column 33.: INSERT
 INTO PEOPLE (id) VALUES () (ActiveRecord::StatementInvalid)
        from E:/jruby-1.1.4/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active
_record/fixtures.rb:518:in `create_fixtures'
        from E:/jruby-1.1.4/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active
_record/connection_adapters/abstract_adapter.rb:78:in `disable_referential_integ
rity'
        from E:/jruby-1.1.4/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active
_record/fixtures.rb:509:in `create_fixtures'
        from E:/jruby-1.1.4/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active
_record/base.rb:1267:in `silence'
        from E:/jruby-1.1.4/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active
_record/fixtures.rb:508:in `create_fixtures'
        from hoge.rb:12

のようなエラーとなってしまいます。


そこで、-%>の出番キタ!って感じです。
早速CSVを以下のように修正します。

id,name
1,test
2,hoge
<% 10.times do |i| -%>
<%= (i+3) %>,hoge<%= i %>
<% end -%>

が、これまたエラーになります。

E:/jruby-1.1.4/lib/ruby/1.8/erb.rb:743: (erb):5: , unexpected ';' (SyntaxError)

trim_modeの指定の問題のようです。


で、
lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/fixtures.rb
をみてみると、

    def erb_render(fixture_content)
      ERB.new(fixture_content).result
    end

trim_modeの指定無いじゃん。ってわけで、こんなパッチを当ててみる。

    def erb_render(fixture_content)
      #ERB.new(fixture_content).result
      ERB.new(fixture_content, nil, "-").result
    end

すると、無事動きました。

JRubyで改行コードがCRLFのファイルを入力するとERBの-%が効かない

ようです。LFで保存しましょう。


あんまり関係ないですが、-%使おうと思ったときに出たエラー。

E:/jruby-1.1.4/lib/ruby/1.8/erb.rb:743:in `binding': (erb):2: , unexpected ';' (
SyntaxError)

        from testerb.rb:9:in `eval'
        from E:/jruby-1.1.4/lib/ruby/1.8/erb.rb:743:in `result'
        from testerb.rb:9

erbで-を指定する際の注意点 - お題目うぉっち
を参考に解決させていただきました。

結局JRubyって何がおいしいのか考えてみた。

  • Rubyの中で
    • Javaのライブラリが使える
      • こないだやったPOI on JRubyはここ
    • Rubyの文法でJavaが書ける
  • Javaの中で
    • Rubyのライブラリが使える
      • Railsアプリをwarblerでtomcatに乗せるとか、今やってるFixturesをJavaで使えるとかはここ
    • Javaの文法でRubyが書ける
      • メリットなしかも

ということなのかと理解しました。

JavaからFixturesを使ってみる - Rubyコードを使わずJavaコードのみで実現する編

といっても、evalScriptletを使っているので、少しはRubyコード出てきますが。。
FixturesをRails外で使ってみるRubyコードをJavaに移植するイメージ。

import org.jruby.*;
import org.jruby.runtime.*;
import org.jruby.runtime.builtin.*;

public class CallRuby
{
	public static void main(String[] args) throws Exception
	{
		// JRuby環境を取得
		Ruby runtime = Ruby.newInstance();
		ThreadContext context = runtime.getCurrentContext();

		// 必要なgemをrequire
		runtime.evalScriptlet("require 'rubygems'");
		runtime.evalScriptlet("require 'active_record'");
		runtime.evalScriptlet("require 'active_record/fixtures'");

		// runtime.getClass("ActiveRecord::Base")は不可
		// ActiveRecordモジュールを取得
		RubyModule activeRecordModule = runtime.getModule("ActiveRecord");
		// ActiveRecord::Baseクラスを取得
		RubyClass activeRecordBaseClass = activeRecordModule.getClass("Base");

		// コネクションを確立
		RubyHash arg = new RubyHash(runtime);
		arg.put(RubySymbol.newSymbol(runtime, "adapter"), "jdbcderby");
		arg.put(RubySymbol.newSymbol(runtime, "database"), "hoge");
		activeRecordBaseClass.callMethod(context, "establish_connection", arg);

		// Fixturesクラスを取得
		System.out.println("insert");
		RubyClass fixturesClass = runtime.getClass("Fixtures");
		RubyString arg1 = runtime.newString("data");
		RubyArray arg2 = runtime.newArray();
		arg2.add("PEOPLE");
		IRubyObject[] arguments = {arg1, arg2};

		// Fixtures#create_fixtures呼び出し
		IRubyObject fixturesObj = fixturesClass.callMethod(context, "create_fixtures", arguments);

		// Personクラスを定義
		//RubyClass personClass = runtime.defineClass("Person", activeRecordBaseClass, ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
		runtime.evalScriptlet("class Person < ActiveRecord::Base\nend");
		RubyClass personClass = runtime.getClass("Person");

		// 全レコードを表示
		System.out.println("select all");
		showAllPersons(runtime, context, personClass);

		// Fixtures#delete_existing_fixtures呼び出し
		System.out.println("delete");
		fixturesObj.callMethod(context, "delete_existing_fixtures");

		// 全レコードを表示
		System.out.println("select all");
		showAllPersons(runtime, context, personClass);

		System.out.println("finish");
	}

	/**
	 *	PEOPLEテーブルの全レコードを表示
	 */
	private static void showAllPersons(Ruby runtime, ThreadContext context, RubyClass personClass)
	{
		RubySymbol arg1 = RubySymbol.newSymbol(runtime, "all");
		RubyHash arg2 = new RubyHash(runtime);
		arg2.put(RubySymbol.newSymbol(runtime, "order"), "ID");
		IRubyObject[] arguments = {arg1, arg2};

		// Person#find呼び出し
		IRubyObject personObjArray = personClass.callMethod(context, "find", arguments);
		RubyArray persons = (RubyArray)personObjArray;

		for(int i=0; i<RubyNumeric.num2int(persons.length()); i++)
		{
			RubyObject personObj = (RubyObject)persons.get(i);
			IRubyObject id = personObj.callMethod(context, "id");
			IRubyObject name = personObj.callMethod(context, "name");
			System.out.println(id + "/" + name);
		}
	}
}

というわけで、なんかうまく動かないところは、evalScriptletに逃げるという策を使えば、
わりとできてしまうことが分かりました。
JRubyAPIって、JavaのリフレクションAPIをたたくのに似てますね。

Kernelモジュールにメソッド追加&二つの方法で呼び出し

Ruby#evalScriptletでKernelモジュールにメソッドを追加しておいてから、

  • Ruby#evalScriptletでメソッドを呼び出す
  • Ruby#getModule#callMethodでメソッドを呼び出す

の二つの方法を試してみる。

import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyString;
import org.jruby.runtime.builtin.IRubyObject;

public class ExtendModule
{
	public static void main(String[] args) throws Exception
	{
		Ruby runtime = Ruby.newInstance();

		// Kernelモジュールにメソッドを追加
		String scriptlet = "module Kernel\n  def hoge(str)\n    'hoge: ' + str\n  end\nend\n";
		runtime.evalScriptlet(scriptlet);

		// testメソッドを呼んで、戻り値を取得する
		IRubyObject obj = runtime.evalScriptlet("hoge('from evalScriptlet')");
		System.out.println(obj.getClass().getName());
		System.out.println(obj);

		// Kernelモジュールを取得
		RubyModule rubyModule = runtime.getModule("Kernel");
		RubyString param = runtime.newString("from runtime.getModule");
		IRubyObject obj2 = rubyModule.callMethod(runtime.getCurrentContext(), "hoge", param);
		System.out.println(obj2.getClass().getName());
		System.out.println(obj2);
	}
}

結果はこう。

org.jruby.RubyString
hoge: from evalScriptlet
org.jruby.RubyString
hoge: from runtime.getModule

後者の方法であれば、引数をscriptletの文字列として組み立てなくてもできますね。

Objectクラスにインスタンスメソッドとクラスメソッド追加&呼び出し

Ruby#evalScriptletでObjectクラスにインスタンスメソッドとクラスメソッドを追加しておいてから、

  • Ruby#evalScriptletでメソッドを呼び出す
  • Ruby#getClass#newInstance#callMethodでインスタンスメソッドを呼び出す
  • Ruby#getClass#callMethodでクラスメソッドを呼び出す

を試してみる。

import org.jruby.*;
import org.jruby.runtime.*;
import org.jruby.runtime.builtin.*;

public class ExtendObject
{
	public static void main(String[] args) throws Exception
	{
		Ruby runtime = Ruby.newInstance();

		// Objectクラスにインスタンスメソッドを追加
		String scriptlet = "class Object\n  def hoge(str)\n    'hoge: ' + str\n  end\nend\n";
		runtime.evalScriptlet(scriptlet);

		// Objectクラスにクラスメソッドを追加
		String scriptlet2 = "class Object\n  def self.moge(str)\n    'moge: ' + str\n  end\nend\n";
		runtime.evalScriptlet(scriptlet2);

		// evalScriptletでメソッドを呼んで、戻り値を取得する
		IRubyObject obj = runtime.evalScriptlet("Object.new.hoge('from evalScriptlet')");
		System.out.println(obj.getClass().getName());
		System.out.println(obj);

		obj = runtime.evalScriptlet("Object.moge('from evalScriptlet')");
		System.out.println(obj.getClass().getName());
		System.out.println(obj);

		// Objectクラスを取得
		RubyClass rubyObject = runtime.getClass("Object");
		IRubyObject rubyObjectObj = rubyObject.newInstance(runtime.getCurrentContext(), new IRubyObject[0], Block.NULL_BLOCK);
		RubyString param = runtime.newString("from runtime.getClass");

		// インスタンスメソッドを呼んで、戻り値を取得する
		obj = rubyObjectObj.callMethod(runtime.getCurrentContext(), "hoge", param);
		System.out.println(obj.getClass().getName());
		System.out.println(obj);

		// クラスメソッドを呼んで、戻り値を取得する
		obj = rubyObject.callMethod(runtime.getCurrentContext(), "moge", param);
		System.out.println(obj.getClass().getName());
		System.out.println(obj);
	}
}

結果はこう。

org.jruby.RubyString
hoge: from evalScriptlet
org.jruby.RubyString
moge: from evalScriptlet
org.jruby.RubyString
hoge: from runtime.getClass
org.jruby.RubyString
moge: from runtime.getClass

普通にできてます。

【iKnow!】あのタグを英語で言うと?【外部プレイヤー対応記念】‐ニコニコ動画(夏)

おもしろすぎw
っていうか、死亡フラグ = MFD(Marked For Death)ってなんだよ。
フラグって、goo辞書によると、flag bitの略なわけ?