Java から ActiveRecord を利用する(不完全)
Javaプログラムから、JRubyを利用してRails 3.0のActiveRecordを使う実験をしていたが、どうも上手くいかなかった。
環境構築
OSは Windows 7 の64bit版。
Maven2のインストール
最新版のMaven2(3.0.1)をインストール。
こんな感じ。
> mvn -v Apache Maven 3.0.1 (r1038046; 2010-11-23 19:58:32+0900) Java version: 1.6.0_23 Java home: C:\Program Files\Java\jdk1.6.0_23\jre Default locale: ja_JP, platform encoding: MS932 OS name: "windows 7" version: "6.1" arch: "amd64" Family: "windows"
JRuby のインストール
JRuby(x64)の最新版をインストール。
1.5.6 を D:/bin/jruby-1.5.6 にインストールした。
こんな感じ。
> jruby -v jruby 1.5.6 (ruby 1.8.7 patchlevel 249) (2010-12-03 9cf97c3) (Java HotSpot(TM) 6 4-Bit Server VM 1.6.0_23) [amd64-java]
ActiveRecord-JDBC のgemをインストール
JavaなのでJDBCで。activerecord-jdbc-adapterの最新版(1.1.0)をインストール。
> gem install activerecord-jdbc-adapter
Invalid output formatter とか出るけど気にしない。
あと、今回はSQLiteを使うのでSQLite3用のgemもインストール。
> gem install activerecord-jdbcsqlite3-adapter
実装
適当なディレクトリを作って、その中に以下のファイルを置く。
/pom.xml
Maven2を使うのでpom。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>example</groupId> <artifactId>jruby</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.jruby</groupId> <artifactId>jruby-complete</artifactId> <version>1.5.6</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.0.1</version> </dependency> </dependencies> <repositories> <repository> <id>codehaus</id> <name>Maven Codehaus repository</name> <url>http://repository.codehaus.org/</url> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> </project>
/src/main/java/User.java
ユーザ情報のI/F。実体はJRubyで実装する。
package example; public interface User { String getName(); }
/src/main/java/UserManager.java
ユーザ管理クラスのI/F。実体はJRubyで実装する。
package example; import java.util.List; import org.jruby.RubyObject; public interface UserManager { /* ユーザ一覧取得 */ List<RubyObject> all_users_ro(); /* ユーザ一覧取得 */ List<User> all_users(); /* ユーザー登録 */ User create_user(String name); /* ユーザーテーブル作成 */ void create_table(); }
実験のために、ユーザの一覧取得サービスはUserのリストで受け取るものと、RubyObjectという生のRubyオブジェクトのリストで受け取るものと、2種類用意した。
/src/main/resources/user_manager.rb
ActiveRecord を使ったユーザ管理クラスを作って、そのインスタンスを返すJRubyスクリプト。
require 'rubygems' require 'arjdbc' class User < ActiveRecord::Base include Java::example.User end class UserManager include Java::example.UserManager def initialize ActiveRecord::Base.establish_connection( :adapter => "jdbcsqlite3", :database => "test.db" ) end def create_table ActiveRecord::Schema.define do create_table :users, :force => true do |t| t.column :name, :string end end end def create_user(name) User.create(:name => name) end def all_users_ro User.all end alias :all_users :all_users_ro end UserManager.new
Java側のUserManagerインタフェースで定義したメソッドを実装している。
all_users() と all_users_ro() は、実体は同じ。
/src/main/java/Main.java
メインクラス。
package example; import java.io.File; import java.util.Arrays; import java.util.List; import org.jruby.RubyObject; import org.jruby.embed.PathType; import org.jruby.embed.ScriptingContainer; public class Main { public static void main(String[] args) throws Exception { String classpath = System.getProperty("java.class.path"); List<String> loadPaths = Arrays.asList(classpath.split(File.pathSeparator)); ScriptingContainer container = new ScriptingContainer(); container.setHomeDirectory("D:/bin/jruby-1.5.6"); container.setLoadPaths(loadPaths); Object userManagerObject = container.runScriptlet(PathType.CLASSPATH, "user_manager.rb"); UserManager userManager = container.getInstance(userManagerObject, UserManager.class); // 一回目だけ userManager.create_table(); // (1) userManager.create_user("John Doe"); // (2) System.out.println("*** RubyObject ***"); for (RubyObject ro : userManager.all_users_ro()) { User user = (User)ro.toJava(User.class); System.out.println(user.getName()); } System.out.println("*** Auto mapping ***"); for (User user : userManager.all_users()) { System.out.println(user.getName()); } } }
実行
pom.xml のあるディレクトリから、以下のように実行する。
> mvn exec:java -Dexec.mainClass=example.Main
まず1回実行するとこんな結果が表示され、テーブルが作られてユーザが1人登録されたことがわかる。
-- create_table(:users, {:force=>true}) -> 0.8940s -> 0 rows *** RubyObject *** John Doe *** Auto mapping *** John Doe
その後 (1) をコメントアウトすると、既存のテーブルに対してユーザが追加登録され、2件のユーザが表示される。
*** RubyObject *** John Doe John Doe *** Auto mapping *** John Doe John Doe
で、ここからが問題なんだけど、さらに (2) をコメントアウトすると、何故か List
*** RubyObject *** John Doe John Doe *** Auto mapping *** Exception in thread "main" java.lang.ClassCastException: org.jruby.RubyObject cannot be cast to example.User at example.Main.main(Main.java:38)
ruby側で User.create を呼び出しているとOK、呼び出していないと ClassCastException というよくわからない状況。RailsにもJRubyにもまだ未熟のため、この最後の問題がどうしても解決できず。
UIBarButtonItem 内に UISearchBar を配置するとキャンセルボタンが出ない
iOS SDK 3.2.5 でアプリを作っているんだけど、UIBarButtonItem 内に UISearchBar を配置するとキャンセルボタンが出ないという細かい問題に悩まされた。
具体的には、例えば以下のように UINavigationItem に UISearchBar を入れると検索バーは表示されて入力もできるが、UISearchBar の setShowsCancelButton で YES をセットしても、なぜかキャンセルボタンが表示されない。
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 300, 44)]; self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:searchBar] autorelease];
SDKのソースが公開されていないので理由はさっぱりわからないけど、UISearchBar の右に兄弟ビューを入れられるような構造になっていないとだめなのかなぁと思い、以下のように適当に UIView をかませてみたら、ちゃんと表示されるようになった。
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 300, 44)]; UIView *searchBarContainer = [[[UIView alloc] initWithFrame:searchBar.frame] autorelease]; [searchBarContainer addSubview:searchBar]; self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:searchBarContainer] autorelease];
とりあえずめでたい。
それにしても、iOS SDKは妙なバグだか制限だかが多くて面倒だ。この件とか、UISplitViewController や UITabBarController のビューは root view にしないとだめだとか(UINavigationController に UISplitViewController を入れたいんですけど…)、位置とサイズを同時に変更するようなアニメーションが妙な動きをする*1とか(SafariやiPad版Evernoteの検索バーが拡大するような動きって、作り込みが必要なの?)、UIBarButtonSystemItemFlexibleSpace がバカで、「ツールバー中央と右だけに」ボタンを配置すると中央のボタンの位置がずれるとか*2
。ほかにもあったけど忘れた。
*1:http://stackoverflow.com/questions/2460313/animating-resizing-and-moving-uiview-at-the-same-time
*2:例: 左側のボタンが中央にならず、flexible spaceの意味がない
SQLite (SQLite3) で JPA
JPA (Java Persistence API) でも SQLite を使いたい!ということで試してみる。いちいちデータベースサーバとか用意したり起動したり面倒くさいし。
$ java -version Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8 java version "1.6.0_20" Java(TM) SE Runtime Environment (build 1.6.0_20-b02-279-10M3065) Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01-279, mixed mode) $ mvn -v Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8 Apache Maven 2.2.0 (r788681; 2009-06-26 22:04:01+0900) Java version: 1.6.0_20 Java home: /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home Default locale: ja_JP, platform encoding: UTF-8 OS name: "mac os x" version: "10.6.4" arch: "x86_64" Family: "mac"
まず pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.jpa</groupId> <artifactId>hibernate</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Hibernate JPA Example</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <inherited>true</inherited> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <artifactId>maven-eclipse-plugin</artifactId> <configuration> <downloadJavadocs>true</downloadJavadocs> <downloadSources>true</downloadSources> </configuration> </plugin> </plugins> </build> <dependencies> <!-- SQLite3のJDBCドライバ。Central Repository での最新版 3.6.20 だと不具合あり --> <!-- see: http://code.google.com/p/xerial/issues/detail?id=54 --> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.6.20.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.5.3-Final</version> <scope>compile</scope> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>0.9.24</version> <type>jar</type> <scope>compile</scope> </dependency> <!-- Hibernateから自動解決されるバージョンだと上の logback で問題があるので指定 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.1</version> <type>jar</type> <scope>compile</scope> </dependency> </dependencies> <repositories> <repository> <id>Xerial.org</id> <name>Xerial.org Repo</name> <url>http://www.xerial.org/maven/repository/artifact/</url> </repository> <repository> <id>JBoss.org</id> <name>JBoss.org Repo</name> <url>https://repository.jboss.org/nexus/content/groups/public/</url> </repository> </repositories> </project>
HibernateはDBごとの違いをdialect(方言)で吸収しているが、HibernateにはSQLite3用のDialectが無いようなので(EclipseLinkにも無い)、自分で作成する。一番近いのはおそらく MySQL なので、MySQL用の Dialect をもとに差異がある部分のみオーバライド。非常に手抜きで、このエントリのデモが動作する程度しか確認していない。
src/main/java/com/example/jpa/hibernate/SQLite3Dialect.java
package com.example.jpa.hibernate; import org.hibernate.dialect.MySQL5Dialect; public class SQLite3Dialect extends MySQL5Dialect { public SQLite3Dialect() { super(); } /** * INSERT後のID取得用SQL。 */ @Override public String getIdentitySelectString() { return "select last_insert_rowid()"; } /** * 自動採番ID用のカラム定義。 * デフォルトだと Long 型は BIGINT になってしまう。 */ @Override public String getIdentityColumnString() { return "integer"; } /** * IDカラムにデフォルトの型を使用するかどうか。 * {@link #getIdentityColumnString()} の定義のみを使いたいので false。 * true のままだと、"id bigint integer" のような定義になってしまう。 */ @Override public boolean hasDataTypeInIdentityColumn() { return false; } /** * タイムスタンプ取得用の関数名。 * デモでは使わない。これでいいのかも不明。 */ @Override public String getCurrentTimestampSQLFunctionName() { return "datetime('now')"; } /** * タイムスタンプ取得用のSQL。 * デモでは使わない。これでいいのかも不明。 */ @Override public String getCurrentTimestampSelectString() { return "select datetime('now')"; } }
JPA管理のエンティティの共通親クラス
src/main/java/com/example/jpa/hibernate/BasicEntity.java
package com.example.jpa.hibernate; import java.util.Date; import javax.persistence.*; @MappedSuperclass public class BasicEntity { @Id @GeneratedValue private Long id; @Version private Long version; private Date createdAt; private Date updatedAt; public Long getId() { return id; } public Long getVersion() { return version; } public void setVersion(Long version) { this.version = version; } public Date getCreatedAt() { return createdAt; } public Date getUpdatedAt() { return updatedAt; } @PrePersist public void onCreate() { createdAt = new Date(); onUpdate(); } @PreUpdate public void onUpdate() { updatedAt = new Date(); } }
テスト用の Customer クラス
src/main/java/com/example/jpa/hibernate/Customer.java
package com.example.jpa.hibernate; import javax.persistence.*; @Entity @NamedQueries({ @NamedQuery(name = Customer.FIND_ALL, query = "SELECT c FROM Customer c") }) public class Customer extends BasicEntity { public static final String FIND_ALL = "findAllCustomers"; private String name; public Customer() { super(); } public Customer(String name) { this(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return String.format( "id=%d, name=%s, version=%d, created_at=%s, updated_at=%s", getId(), getName(), getVersion(), getCreatedAt(), getUpdatedAt()); } }
JPAの設定。さっき作った Dialect を使うように指定する。
src/main/resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8" ?> <persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="testPu" transaction-type="RESOURCE_LOCAL"> <class>com.example.jpa.hibernate.Customer</class> <properties> <property name="javax.persistence.jdbc.driver" value="org.sqlite.JDBC" /> <property name="javax.persistence.jdbc.url" value="jdbc:sqlite:test.db" /> <property name="hibernate.dialect" value="com.example.jpa.hibernate.SQLite3Dialect" /> <!-- createdAt (Java) <=> created_at (DB) のような naming strategy --> <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" /> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show-sql" value="true" /> <property name="hibernate.format-sql" value="true" /> </properties> </persistence-unit> </persistence>
テスト用のメインクラス。引数ひとつでその名前の Customer を追加、引数ふたつ(名前、ID)でそのIDの Customer の名前を更新する。
src/main/java/com/example/jpa/hibernate/Customer.java
package com.example.jpa.hibernate; import javax.persistence.*; public class App { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("testPu"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); Customer customer; tx.begin(); if (args.length >= 2) { customer = em.find(Customer.class, Long.valueOf(args[1])); } else { customer = new Customer(); } customer.setName(args[0]); em.persist(customer); tx.commit(); Customer found = em.find(Customer.class, customer.getId()); System.out.println("PERSISTED OBJECT: " + found); TypedQuery<Customer> query = em.createNamedQuery(Customer.FIND_ALL, Customer.class); for (Customer c : query.getResultList()) { System.out.println("OBJECTS IN TABLE: " + c); } em.close(); emf.close(); } }
これで、pom.xml のあるフォルダから
mvn exec:java -Dexec.mainClass=com.example.jpa.hibernate.App -Dexec.args="山田太郎"
とやると
OBJECTS IN TABLE: id=1, name=山田太郎, version=0, created_at=Mon Jul 19 18:51:12 JST 2010, updated_at=Mon Jul 19 18:51:12 JST 2010
のようにデータが作成され、
mvn exec:java -Dexec.mainClass=com.example.jpa.hibernate.App -Dexec.args="上田次郎 1"
とやると
OBJECTS IN TABLE: id=1, name=上田次郎, version=1, created_at=2010-07-19 18:51:12.448, updated_at=Mon Jul 19 18:53:11 JST 2010
のように更新されましたとさ*1。
EclipseLink でも上と同じようなことを試したけど、
- 「主キーカラムだけ型のマッピングを変える」ということができないため、id に @Column(columnDefinition = "INTEGER") をつけなきゃいけない
- @GeneratedValue のデフォルトである strategy = GenerationType.AUTO だと、Hibernateのような「DBごとに適した方法」ではなく、「ID管理用テーブルを作って使う」方式が固定で選択され、しかもデフォルトを変更する方法がない。
等々、だめな部分が目立った。
で、あとで気づきましたが、既に Dialect は存在するようで。
システム日付を安易に使ったプログラムのテスト
全くテストを意識しないで Calendar.getInstance() とか new Date() とかを使いまくるプログラムがあったとする。例えば、以下のようなものだ。
public void oomisoka() { Date date = new Date(); if (date.getMonth() == 11 && date.getDate() == 31) { // 12月31日にのみ行う処理… } }
せめて引数で Date を受け取るようになっていれば…という感じだが、こういったメソッドをテストするにはどうしたらいいだろう?
など様々な対処を見たが、JMockit などを使えば上記のような挑戦的プログラムも普通にテストができる。
たとえば、new Date() や Calendar.getInstance() で作られるインスタンスの年を2000年に変更してしまうには、以下のようにすれば良い*1。
package com.example.mockdate; import static junit.framework.Assert.assertEquals; import java.util.Calendar; import java.util.Date; import mockit.Mock; import mockit.MockUp; import mockit.Mockit; import org.junit.BeforeClass; import org.junit.Test; public class AppTest { @BeforeClass public static void setUpClass() { // 大抵不要だが一応 // see: http://jmockit.googlecode.com/svn/trunk/www/tutorial/RunningTests.html Mockit.setUpMocks(); } // staticメソッドのモックを作るにはクラス定義が必要? public static class MockCalendar { private static Calendar instance; public static void setInstance(Calendar desired) { instance = (Calendar)desired.clone(); } @Mock public static Calendar getInstance() { return (Calendar)instance.clone(); } } @Test public void testCalendar() { // MockCalendar.getInstance() が返す Calendar を設定 Calendar desired = Calendar.getInstance(); desired.set(Calendar.YEAR, 2000); MockCalendar.setInstance(desired); // Calendarクラスのモックとして MockCalendar を指定 Mockit.setUpMock(Calendar.class, MockCalendar.class); // millennium now! assertEquals(2000, Calendar.getInstance().get(Calendar.YEAR)); } @Test @SuppressWarnings({ "unused", "deprecation" }) public void testMockDate() { new MockUp<Date>() { Date it; // Date のデフォルトコンストラクタの差し替え // インスタンス変数 it で、コンストラクタが返す値を操作できる @Mock void $init() { it.setYear(100); } }; // millennium again! assertEquals(100, new Date().getYear()); } }
*1:使い込んでいないためもっといい方法がある可能性は大
ヘッダ固定テーブルの最良解 Pushpin Header
HTMLで、ヘッダを固定してボディを縦方向にスクロール可能にしたテーブルを作る方法はいろいろあって、テーブル ヘッダ固定 でぐぐると百花繚乱、群雄割拠という感じだけれども、個人的には http://codylindley.com/CSS/249/pushspin-header-a-simplified-data-grid-with-a-stationary-header に載っていた方法が
- HTML+CSSのみで動作。JavaScript不要
- IE6, IE7, IE8でも動作*1
- tableタグとその中身は全く普通の構造。tableの分割もしない
- 「ヘッダ部分だけを、CSSの絶対位置指定で無理矢理スクロール領域の外に固定する」と一行で説明できる単純で理解しやすい動作原理
と、ベストだと思う。
ところが、最近上記ページを見直してみたらなんか消えていて確認できなくなっていたので*2、記憶をたよりにやり方を再確認しておく。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> <title>Pushpin Header test</title> <style type="text/css"> body, table { font-size: 12px; } /* ---------- キモになる部分+枠線 ---------- */ /* 全体の枠。ヘッダが入る分だけ上部を空けておく */ .container { position: relative; padding-top: 20px; border: 1px solid red; } /* .container からヘッダのスペースを除いた部分。ここがスクロール対象 */ .content { overflow: auto; } .scrollable { border-collapse: collapse; } /* ヘッダ部分。位置を .container の左上端に移動 */ .scrollable thead tr { position: absolute; top: 0; left: 0; } /* このheightと .container の padding-top を合わせる */ .scrollable thead th { height: 20px; border-color: blue; border-style: solid; border-width: 0px 1px 1px 0px; } .scrollable tbody td { height: 20px; border-color: black; border-style: solid; border-width: 1px 1px 0px 0px; } /* ---------- 表ごとに異なる値はclassを分ける ---------- */ .container1 { width: 200px; } .content1 { width: 200px; height: 80px; } /* 各カラムにはwidth を設定する必要がある */ .table1 th, .table1 td { padding: 0 3px 0 3px; width: 80px; } .table1 th { background-color: #ccc; } </style> </head> <body> <!-- 全体枠 --> <div class="container container1"> <!-- スクロール領域 --> <div class="content content1"> <table class="scrollable table1"> <thead> <tr><th>head1</th><th>head2</th></tr> </thead> <tbody> <tr><td>body1</td><td>body2</td></tr> <tr><td>body1</td><td>body2</td></tr> <tr><td>body1</td><td>body2</td></tr> <tr><td>body1</td><td>body2</td></tr> <tr><td>body1</td><td>body2</td></tr> </tbody> </table> </div> </div> </body> </html>
- IEでは、DOCTYPE等を書いて標準準拠モードで動かす必要あり
- 細かい幅や枠線の調整ではIE/Firefox等で違いが出る場合あり。ただしこの手法自体とは関係ないCSSの一般的な問題なので自分で頑張る
*1:上記のサンプル自体はIE6, IE7では未確認。ただ、以前同様の方法を採ったときはIE6, IE7でも動作を確認したので、微調整すれば使えるはず。
*2:ブログ記事抜きの実例だけは http://codylindley.com/blogstuff/css/pushpin/pushpin.html に残っているっぽい。
ActiveScriptRuby で WAVE DASH 問題にハマる
Windows 7 に ActiveScriptRuby 1.8.7 をインストールして使っているんだけど、Oracle+JDBCではよくあるWAVE DASH問題的な問題にはまってしまった。
こんな環境で、
> ruby -v ruby 1.8.7 (2010-01-10 patchlevel 249) [i386-mswin32]
UTF-8の文字列をShift JISに変換したいだけなんだけど、Iconvを使うと文字列の中に FULLWIDTH TILDE (U+FF5E) がある場合にエラーになってしまう。
require 'iconv' Iconv.conv 'SHIFT_JIS', 'UTF-8', [0xff5e].pack('U') Iconv::IllegalSequence: "\357\275\236" from (irb):7:in `conv' from (irb):7 from :0
Macだとこちら d:id:kyut:20081229:1230519610 にあるように SHIFT_JIS ではなく CP932 を指定すれば良いみたいだけど、ActiveScriptRuby では CP932 を指定しても結果は同じ。どうも現在の ActiveScriptRuby に入っている iconv はバージョンが古く(1.9.1)、どこかのバージョンで取り込まれた(?)CP932 指定時の U+FF5E ⇒ 波ダッシュ への変換ができないらしい(手元の Snow Leopard は iconv 1.13 で、CP932 がうまく動く)。
どうしようもなさそうなので結局信頼と伝統のNKFを使うようにした*1。
require 'nkf' NKF.nkf '-sW8m0', [0xff5e].pack('U') # => "〜"
プログラマのための文字コード技術入門
プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)
- 作者: 矢野啓介
- 出版社/メーカー: 技術評論社
- 発売日: 2010/02/18
- メディア: 単行本(ソフトカバー)
- 購入: 34人 クリック: 578回
- この商品を含むブログ (129件) を見る
もちろん内容は興味深く、楽しく読めたんだけど、こぼれ話的なものにも面白いものがあった。