生物工学の研究者 四方哲也氏について

生物工学の研究者 四方哲也氏について

大阪大学情報科学研究科元教授・生命機能研究科元教授 四方哲也氏は、高校時代、3年間同じ組で、気が合って、大学、大学院も同じ大阪大学で、付き合いがあったし、社会人になってからも、年賀状のやりとりなどしていた。彼は、高校時代、そんなに勉強ができる人ではなかったが、数学とか生物、化学の時間に、先生にそれは何でなのかとすごくしつこく質問し、理論を根本から理解しようとする姿勢の人だった。大阪大学工学部発酵学科(当時)に現役で入学し、大阪大学大学院で、遺伝子工学の分野で独創的な研究をやり、大阪大学の教授までになった。大学院生時代の彼は、従来からそのように言われていた学説を疑い追求したら、実は違っていたということを発見した(確か、そんなこと)とか、彼から直接、人真似の研究はやらないと聞いたことがあった。

2年くらい前に、研究不正使用のことが問題になり、彼の学生、助手、助教授時代の元教授、彼の下の確か助教と共に告発された。不正使用は、以前の教授(彼の指導教授か、それ以前のもっと前の教授からかは不明)の頃から慣例で行われていて、彼発案のことではないらしい。最終的には、不正使用の金額を返還するということで、刑事事件になることを免れたが、彼は大阪大学を懲戒解雇された。私は、彼は、すごく優秀な奴なのに、何でそんなバカなことをやったのかと思ったし、この不正問題で学術界でやっていくことや製薬メーカーに入ることは多分不可能だろうと思って、処分以後のことを心配していた。

昨夜、なんとなく、彼は今どうしているのかと、Wikipediaで調べた。中国の大学の研究所に研究所所長として招聘されたことを知って、よかったと思った。しかし、それと同時に、よりによって、中国なのかとも思った。彼の研究は、人工生命を作るとかの基礎研究的なもので、特許が絡むような、莫大な利潤を生む製薬の研究ではなかった。招聘の条件で、人工生命の研究もやっていいが、遺伝子工学を用いた製薬の研究、あるいは、研究所内の部下への製薬の研究の指揮、指導をやるように要求されたかもしれないと思った。アメリカに行ったということなら、少しはよく思ったが、中国に行ったというのは、日本の研究の国際的レベルにはマイナスになるかもしれないし、不正以前は、ある程度成果主義を要求されるにしても自由に人工生命の研究をやっていたが、中国では国策で金になる製薬の研究・開発を要求され、強いプレッシャーにさらされるかもしれない。しかし、不正を働いた人にはそれなりのペナルティーを課されるべきだとは思う。

あと、彼が、中国の大学で研究者のポジションを得たからといって、彼の今後の研究が、日本の学会で評価されるかというと、多分無理だろうと思うし、国際学会ではどうかだが、これに関しては私は分からない。ただ、私は、彼には、今後も以前のように独創的な研究をやって活躍して欲しいと思う。

今更ながら、ORMLiteを使ってアドレス帳アプリを作ってみた

Androidで使えるORMライブラリについて調べて、ORMLiteを使ってアドレス帳アプリをサンプルとして作ってみました。ORMLiteを使うのは今更感はありますが。

ORMライブラリについては、@pside氏の 天下一「AndroidのORM」武道会 の記事が詳しいです。主要なライブラリは、次の通りで、私が調べたFull text search 3(FTS3)のサポート状況を付け加えます。

  • ORMLite: 直接なサポートはないが、raw queyインターフェエースを使うといいという話。 FTS3 searches in ORMLite?
  • ActiveAndroid: full text searchはサポートされているようだ。 Add FTS support #272
  • greenDAO: サポートされていない。
  • Ollie: 調査せず
  • SugarORM: サポートの有無不明。
  • DBFlow: サポートの有無不明。
  • Realm: サポートせず。中の人によると、Coreでタスクリストにあるとのこと。
  • couchbase: サポートせず。

上で引用した @pside 氏の記事によると、NoSQLのRealmは極端に処理が速いとのことですが、FTS3をサポートしていません。SQLiteを使っているGreenDaoは、SQLiteを使っているORMの中で一番処理が速いとのことですが、これもFTS3をサポートしていません。私が調べた限りでは、FTS3が使えるのは、ORMLiteとActiveRecordですが、@pside氏の記事では、ORMLiteの方が若干性能がいいようなので、今回は、ORMLiteを使いました。

今回は、ORMLiteを使ってアドレス帳のアプリをサンプルとして作りました。上で、FTS3について言及していますが、今回は、FTS3の機能は使わず、データベースのテーブルに関連がある場合のアプリの実装の練習をしました。題材は、アドレス帳で、テーブルは、2つあります。1つめのテーブルは、personというテーブルで、カラムにid、name、addressを持ちます。もう1つのテーブルは、addressというテーブルで、カラムに、id、zipcode、prefecture、city、otherを持ちます。2つのテーブルは、1対1の関係にあります。

まず、Intellijで、Gradle: Android Moduleで新規プロジェクトを作り、app/build.gradleのdependenciesに

compile 'com.j256.ormlite:ormlite-android:4.48'

を追加します。

エンティティクラスは、

Person.java

@DatabaseTable(tableName = "person")
public class Person {

    @DatabaseField(generatedId = true)
    private Integer id;
    @DatabaseField
    private String name;
    @DatabaseField(foreign = true, foreignAutoCreate = true, foreignAutoRefresh = true)
    private Address address;

    public Person() {
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

Address.java

@DatabaseTable(tableName = "address")
public class Address {

    @DatabaseField(generatedId = true)
    private Integer id;
    @DatabaseField
    private String zipcode;
    @DatabaseField
    private String prefecture;
    @DatabaseField
    private String city;
    @DatabaseField
    private String other;


    public Address() {
    }

    public Integer getId() {
        return id;
    }

    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }

    public String getPrefecture() {
        return prefecture;
    }

    public void setPrefecture(String prefecture) {
        this.prefecture = prefecture;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getOther() {
        return other;
    }

    public void setOther(String other) {
        this.other = other;
    }
}

です。フィールドには、@DatabaseFieldアノーテーションを付け、自動生成する主キーには、generatedId = trueを付けます。また、Personは、Addressを持っているので、Personクラスで、Address型のフィールドが、関連する永続化フィールドであることをしめすために、foregin = trueを付けます。あと、foreignAutoCreate = trueは、関連に設定されたエンティティを自動で生成するために、foreignAutoRefresh = trueは、クエリが発行された際に自動で更新するために付けます。

次に、com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper を継承したクラスを作ります。

DatabaseHelper.java

public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
    private final static String TAG = DatabaseHelper.class.getSimpleName();

    private final static String DATABASE_NAME = "addressbook.db";
    private final static int DATABASE_VERSION = 1;

    private Dao<Person, Integer> mPersonDao;
    private Dao<Address, Integer> mAddressDao;

    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
        try {
            // エンティティを指定してcreate tableする
            TableUtils.createTable(connectionSource, Person.class);
            TableUtils.createTable(connectionSource, Address.class);
        } catch (SQLException e) {
            Log.e(TAG, "データベースを作成できませんでした", e);
        }
    }

    @Override
    public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
        // 省略
    }

    public Dao<Person, Integer> getPersonDao() throws SQLException {
        if (mPersonDao == null) {
            mPersonDao = getDao(Person.class);
        }
        return mPersonDao;
    }

    public Dao<Address, Integer> getAddressDao() throws SQLException {
        if (mAddressDao == null) {
            mAddressDao = getDao(Address.class);
        }
        return mAddressDao;
    }
}

onCreateメソッドとonUpgradeメソッドをオーバーライドするのは、SQLiteの場合と同じですが、onCreateメソッドでは、TableUtilsクラスを使ってテーブルを生成します。また、ORMLiteでは、Daoクラスの生成はコストがかかるということで、それぞれ、PersonのDaoとAddressのDaoオブジェクトがないときに限り、getPersonDaoとgetAddressDaoメソッドは、getDaoメソッドでDaoオブジェクトを作り値を返し、Daoオブジェクトがある場合は、それを返します。

そして、各アクティビティで、Daoメソッドを使うため、Applicationクラスを継承したクラスで、DatabaseHelperオブジェクトを返すメソッドを定義します。

AddressApplication.java

public class AddressApplication extends Application {
    private static AddressApplication instance;
    private DatabaseHelper mDatabaseHelper;

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
    }

    public static AddressApplication getInstance() {
        return instance;
    }

    public DatabaseHelper getDatabaseHelper() {
        if (mDatabaseHelper == null) {
            mDatabaseHelper = new DatabaseHelper(this);
        }
        return mDatabaseHelper;
    }
}

最後に、アプリのメイン画面は、名前リストで、アクティビティは、

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private final static String TAG = MainActivity.class.getSimpleName();

    private List<String > mList = new ArrayList<String>();
    private ListView mListView;
    private DatabaseHelper mHelper;

    public final static String TYPE = "type";
    public final static String CREATE_DATA_TYPE = "create";
    public final static String UPDATE_DATA_TYPE = "update";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHelper = AddressApplication.getInstance().getDatabaseHelper();

        mListView = (ListView)findViewById(android.R.id.list);

        mList = getAllList();

        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this, R.layout.rowdata, mList);
        mListView.setAdapter(arrayAdapter);

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                ListView listView = (ListView)parent;
                String name = (String)listView.getItemAtPosition(position);
                Intent intent = new Intent(MainActivity.this, RegisterActivity.class);
                intent.putExtra(TYPE, UPDATE_DATA_TYPE);
                intent.putExtra("name", name);
                startActivity(intent);
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);

        MenuItem item = menu.add("NEW");
        item.setIcon(android.R.drawable.ic_input_add);
        item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                Intent intent = new Intent(MainActivity.this, RegisterActivity.class);
                intent.putExtra(TYPE, CREATE_DATA_TYPE);
                startActivity(intent);

                return false;
            }
        });
        MenuItemCompat.setShowAsAction(item,
                MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    public List<String> getAllList() {
        List<String> list  = new ArrayList<String>();
        try {
            List<Person> persons =  mHelper.getPersonDao().queryForAll();
            for (Person person: persons) {
                list.add(person.getName());
            }
            return list;
        } catch (SQLException e) {
            Log.e(TAG, "例外が発生しました", e);
            return null;
        }
    }

    @Override
    protected void onRestart() {
        mListView = (ListView)findViewById(android.R.id.list);

        mList = getAllList();

        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this, R.layout.rowdata, mList);
        mListView.setAdapter(arrayAdapter);

        super.onRestart();
    }
}

リストは、名前のリストなので、

public List<String> getAllList() {
        List<String> list  = new ArrayList<String>();
        try {
            List<Person> persons =  mHelper.getPersonDao().queryForAll();
            for (Person person: persons) {
                list.add(person.getName());
            }
            return list;
        } catch (SQLException e) {
            Log.e(TAG, "例外が発生しました", e);
            return null;
        }
    }

のように、mHelperからPersonのDaoを得て、全件取得 queryForAllを実行しています。

メイン画面のActionBarのメニューのプラスアイコンをタップすると、名前、アドレス入力画面に遷移し、リストに名前がある時、リストの行をタップすると、アドレス更新画面に遷移します。遷移先のアクティビティのクラスは、次のように定義されています。

RegisterActivity.java

public class RegisterActivity extends AppCompatActivity
        implements View.OnClickListener {
    private final static String TAG = RegisterActivity.class.getSimpleName();

    private DatabaseHelper mHelper;
    private Button mBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);

        mHelper = AddressApplication.getInstance().getDatabaseHelper();

        mBtn = (Button)findViewById(R.id.btn);
        mBtn.setOnClickListener(this);

        String type = getIntent().getStringExtra(MainActivity.TYPE);

        Resources resources = getResources();
        if (type.equals(MainActivity.CREATE_DATA_TYPE)) {
            mBtn.setText(resources.getString(R.string.register));
        } else if (type.equals(MainActivity.UPDATE_DATA_TYPE)) {
            mBtn.setText(resources.getString(R.string.update));
            showAddress();
        }
    }

    @Override
    public void onClick(View view) {
        int id = view.getId();
        if (id == R.id.btn) {
            String type = getIntent().getStringExtra(MainActivity.TYPE);
            if (type.equals(MainActivity.CREATE_DATA_TYPE)) {
                registerAddressBook();
            } else if (type.equals(MainActivity.UPDATE_DATA_TYPE)) {
                updateAddressBook();
            }
        }
    }

    public void registerAddressBook() {
        String name = ((EditText)findViewById(R.id.nameEt)).getText().toString();
        String zipcode = ((EditText)findViewById(R.id.zipcodeEt)).getText().toString();
        String prefecture = ((EditText)findViewById(R.id.prefectureEt)).getText().toString();
        String city = ((EditText)findViewById(R.id.cityEt)).getText().toString();
        String other = ((EditText)findViewById(R.id.otherEt)).getText().toString();

        if (!checkDatum(name, zipcode, prefecture, city, other)) {
            return;
        }

        if (isExists(name)) {
            Toast.makeText(this, name + " has been registered already or some error occurs.", Toast.LENGTH_LONG).show();
            return;
        }

        Address address = new Address();
        address.setZipcode(zipcode);
        address.setPrefecture(prefecture);
        address.setCity(city);
        address.setOther(other);
        Person person = new Person();
        person.setName(name);
        person.setAddress(address);
        try {
            mHelper.getPersonDao().create(person);
            finish();
        } catch (SQLException e) {
            Toast.makeText(this, "cannot register address.", Toast.LENGTH_LONG).show();
            Log.e(TAG, e.getMessage());
        }
    }

    public void updateAddressBook() {
        String name = ((EditText)findViewById(R.id.nameEt)).getText().toString();
        String zipcode = ((EditText)findViewById(R.id.zipcodeEt)).getText().toString();
        String prefecture = ((EditText)findViewById(R.id.prefectureEt)).getText().toString();
        String city = ((EditText)findViewById(R.id.cityEt)).getText().toString();
        String other = ((EditText)findViewById(R.id.otherEt)).getText().toString();

        if (!checkDatum(name, zipcode, prefecture, city, other)) {
            return;
        }

        try {
            Person person = mHelper.getPersonDao().queryForEq("name", name).get(0);
            Address address = person.getAddress();
            address.setZipcode(zipcode);
            address.setPrefecture(prefecture);
            address.setCity(city);
            address.setOther(other);
            mHelper.getAddressDao().update(address);
            mHelper.getPersonDao().update(person);
            finish();
        } catch (SQLException e) {
            Log.e(TAG, e.getMessage());
        }
    }

    public void showAddress() {
        String type = getIntent().getStringExtra(MainActivity.TYPE);
        if (!type.equals(MainActivity.UPDATE_DATA_TYPE)) return;

        String name = getIntent().getStringExtra("name");

        List<Person> list = null;
        Person person;
        Address address;

        try {
            list = mHelper.getPersonDao().queryForEq("name", name);
            person = list.get(0);
        } catch (SQLException e) {
            // ありえない
            Log.e(TAG, e.getMessage());
            return;
        }

        address = person.getAddress();

        String zipcode = address.getZipcode();
        String prefecture = address.getPrefecture();
        String city = address.getCity();
        String other = address.getOther();

        ((EditText)findViewById(R.id.nameEt)).setText(name);
        ((EditText)findViewById(R.id.nameEt)).setEnabled(false);
        ((EditText)findViewById(R.id.zipcodeEt)).setText(zipcode);
        ((EditText)findViewById(R.id.prefectureEt)).setText(prefecture);
        ((EditText)findViewById(R.id.cityEt)).setText(city);
        ((EditText)findViewById(R.id.otherEt)).setText(other);
    }

    public boolean isExists(String name) {
        List<Person> list = null;
        try {
            list = mHelper.getPersonDao().queryForEq("name", name);
            if (list.isEmpty()) {
                return false;
            } else {
                return true;
            }
        } catch (SQLException e) {
            Log.e(TAG, e.getMessage());
            return false;
        }

    }

    public boolean checkDatum(String name, String zipcode, String prefecture,
                           String city, String other) {
        if (TextUtils.isEmpty(name)) {
            Toast.makeText(this, "name is empty.", Toast.LENGTH_LONG).show();
            return false;
        }
        if (TextUtils.isEmpty(zipcode)) {
            Toast.makeText(this, "zipcode is empty.", Toast.LENGTH_LONG).show();
            return false;
        }
        if (TextUtils.isEmpty(prefecture)) {
            Toast.makeText(this, "prefecture is empty.", Toast.LENGTH_LONG).show();
            return false;
        }
        if (TextUtils.isEmpty(city)) {
            Toast.makeText(this, "city is empty.", Toast.LENGTH_LONG).show();
            return false;
        }
        if (TextUtils.isEmpty(other)) {
            Toast.makeText(this, "other is empty.", Toast.LENGTH_LONG).show();
            return false;
        }
        return true;
    }
}

新規にアドレスを入力するケースでは、

        Address address = new Address();
        address.setZipcode(zipcode);
        address.setPrefecture(prefecture);
        address.setCity(city);
        address.setOther(other);
        Person person = new Person();
        person.setName(name);
        person.setAddress(address);
        try {
            mHelper.getPersonDao().create(person);
            finish();
        } catch (SQLException e) {
            Toast.makeText(this, "cannot register address.", Toast.LENGTH_LONG).show();
            Log.e(TAG, e.getMessage());
        }

のように、まず、Addressオブジェクトを作り、住所を入れ、次に、Personオブジェクトを作り、名前と住所を入れます。そして、mHeperのPersonに関するDaoオブジェクトを得て、createメソッドの引数にpersonを渡して、データ登録をします。

データ更新のケースでは、簡単のため、名前の変更はないものとして、次のようにデータ更新を行います。

        try {
            Person person = mHelper.getPersonDao().queryForEq("name", name).get(0);
            Address address = person.getAddress();
            address.setZipcode(zipcode);
            address.setPrefecture(prefecture);
            address.setCity(city);
            address.setOther(other);
            person.setAddress(address);
            mHelper.getAddressDao().update(address);
            mHelper.getPersonDao().update(person);
            finish();
        } catch (SQLException e) {
            Log.e(TAG, e.getMessage());
        }

名前の変更はないし、同じ名前を入力しようとすると、警告を表示してデータ登録をできない実装にしているため、メイン画面で名前をタップすると、当然データがあるので、mHelper.getPersonDao().queryForEq(“name”, name)は1個しか要素を含まないListを返すので、get(0)でPersonオブジェクトを得ます。次に、Personオブジェクトから、getAddressメソッドで住所を得ます。そして、更新のため入力された住所情報でAddressオブジェクトを更新シ、さらに、更新された住所でPersonオブジェクトを更新します。mHelper.getAddressDao().update(address)とmHelper.getPersonDao().update(person)でデータベースのデータを更新します。

作ったサンプルを

andropenguin/AddressBookORMlite

に公開します。

参考サイト
天下一「AndroidのORM」武道会
Ormlite for Android Using Ormlite for clean and efficient database managment
OrmLite調査メモ

RedmineでGmailにメール送信するための設定

RedmienでGmailにメール送信するための設定について、以前ハマったので、メモ。Remdineのバージョンは、2.5.1。

config/configuration.ymlで次のようにする。user_nameとpaaswordを適宜変更する。

production:

# specific configuration options for development environment
# that overrides the default ones
# —————————————————————————-
email_delivery:
delivery_method: :smtp
smtp_settings:
#tls: true
enable_starttls_auto: true
address: “smtp.gmail.com”
port: 587
domain: “smtp.gmail.com” # ‘your.domain.com’ for GoogleApps
authentication: :plain
user_name: “hoge@gmail.com”
password: “himitsu”
# ————————————————————————–
development:

WebブラウザでのRedmineの設定は、ググって。あと、メールがスパムメールボックスに入っていることがあるので、注意する。
(フォーマッタがうまく機能しないorz。ymlの書式に従って、行頭にスペースを入れる必要がある。)

Ruby on RailsチュートリアルのサンプルのRails 4対応作業補足

Ruby on Railsチュートリアルのサンプルアプリ sample_appのRails 4対応作業。

$ bundle exec rspec spec/models/user_spec.rb

で、エラー、

/usr/local/rvm/gems/ruby-2.0.0-p353@global/gems/selenium-webdriver-2.0.0/lib/selenium/webdriver/common/zipper.rb:1:in `require’: cannot load such file — zip/zip (LoadError)

が出る。ググると、http://www.workabroad.jp/posts/1095 が引っかかる。selenium-webdriver gemのバージョンを新しいのに上げるといいらしい。ところで、Everyday Rails RSpecによるテスト入門では、JavaScriptを含む複雑なwebアプリ上の操作をFirefox経由で行うときに、Seleniumを使うが、Firefoxが頻繁にアップデートするため、Seleniumを使ったテストが動かなくなることが多く、selenium-webdriver gemは最新版にアップデートした方がいいとのこと。そこで、それを、2.40.0にあげたら、エラーはなくなった。テストは失敗するが。

$ bundle exec rspec spec/models/user_spec.rb

テストが失敗するが、Factoryが登録されていないと言われるので、factories.rbを規定の場所にコピー。

再度、

$ bundle exec rspec spec/models/user_spec.rb

チュートリアルによると、”protected_attributes gem (リスト12.1) の動作がRails 3.2の「アクセス可能な属性」と異なっているため、マスアサインメントのセキュリティエラーをテストするコードは4.0では動作しなくなりました。”ということなので、失敗しているテストを削除。再度、テスト。OK。

同様に、micropost、relationshipモデルのテストの失敗する部分も、削除する。

user_pages_spec.rb の修正では、

it { should have_selector(‘title’, text: ‘All users’) }

it { should have_title(‘All users’) }

などと置き換えないといけない。hav_titleメソッドで、text: を書くとダメ。

チュートリアルの、Rails 4対応の説明の通りにやると、これで全テストが通るのだけど、app/asssetsの内容をコピーすると、

undefined method `environment’ for nil:NilClass when importing Bootstrap

と言われる。ググると、http://stackoverflow.com/questions/22392862/undefined-method-environment-for-nilnilclass-when-importing-bootstrap が引っかかり、sass-railsが4.0.0だとダメということで、4.0.2に変更したら、テストが通るようになった。