Log4j 2を使った。おまけで他のloggingも用意した。

はじめに


今はRails使いですが、数年前まではJava使いでした。
何やらlog4jがメジャーバージョンアップ始めたという噂を聞いたので試してみました。
因みに、自分が最後に使っていたのは「slf4j + logback」です。

コード類


コードはgithubに置きました。
どれも必要最低限のライブラリしか使っていませんので、動作の参考になるかと思います。

特にivy, maven, gradleを使うと必要以上のライブラリまで取ってくるので、解決に困っている方がいれば役立つかも・・・

# 自分が偶に忘れるなんてことは言えない・・・

log4j 2.x


本家曰く、何やらこんなことが増えたらしい。

  • APIの分離
  • パフォーマンス(性能)改良
  • multiple API(commons-logging, slf4j)のサポート
  • 設定の自動リロード
  • logbackライクなFilter設定
  • plugin機構の追加

後は、地味に細かい改良があったりします。。。

セットアップ


ライブラリ(2013/12/22時点)は、以下のものがあればよいようです。

  • log4j-api-2.0-beta9.jar
  • log4j-core-2.0-beta9.jar

slf4jと連携させたいときは、以下を追加します。

  • log4j-slf4j-impl-2.0-beta9.jar
  • slf4j-api.1.7.5.jar

Gradleだとこんな感じですね。(transitiveをつけるのは余計なライブラリをダウンロードさせないため)

1
2
3
4
5
6
dependencies {
    compile ('org.apache.logging.log4j:log4j-core:2.0-beta9') {transitive = false}
    compile ('org.apache.logging.log4j:log4j-api:2.0-beta9') {transitive = false}
    compile ('org.apache.logging.log4j:log4j-slf4j-impl:2.0-beta9') {transitive = false}
    compile ('org.slf4j:slf4j-api:1.7.5') {transitive = false}
}

このあたりを参考にするといいかも

設定ファイルの置き場ですが、こちらは「クラスパス」が通ったところに置けばよいでしょう。
# eclipseとかだとsrcの直下の置けばよいです。

因みに、この設定ファイルですが以下に変更となったようです。

  • log4j2.xmlもしくはlog4j2.jsonのどちらかを設定ファイルとする
  • propertiesファイルでは記載できなくなった
  • ファイル名の規則が「log4j2」になった

# Javaの特徴という感じだったのでプロパティファイル結構好きだったんだけどなぁ(日本語書くとアレなのは置いておいて)
# 複雑なことは書けないという致命傷もあったので、設定が複雑なことには向かなかったというのも分かる
# jsonのサポートは今風かも。

ログの設定にjsonを使う場合は、以下のライブラリを追加する必要があるようです。

  • jackson-core-2.2.2.jar
  • jackson-databind-2.2.2.jar

設定


ログって基本、一度設定してしまうと何か特別な理由が無い限り設定変更ってしませんよね。
# ログ分析が必要になったとか、ログレベルの変更をする必要が出たとか

  • デフォルトはERRORとFATALのみ表示(ログレベルは、FATAL > ERROR> WARN > INFO > DEBUG > TRACE)
  • 指定できる出力フォーマット
  • appenderは、コンソールやファイルの出力するフォーマット定義
  • loggersは、どのappendersを使うかを指定することでログの出力方式を決定できます
  • 最低限、appenderとloggersを設定すれば動きます。

以下のような感じでlog4j2.xmlを設定します。

  • Consoleとログファイルの両方にログを出力するようにしています
  • ログファイルの出力は、JSONを設定しています
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
  <appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</pattern>
      </PatternLayout>
    </Console>

    <RollingFile name="RollingFile" fileName="log4j2_sample_log.log" filePattern="log4j2_sample_log_%d{yyyy-MM-dd}.log.gz">
      <JSONLayout complete="true" charset="UTF-8" />
      <Policies>
        <TimeBasedTriggeringPolicy />
      </Policies>
    </RollingFile>
  </appenders>

  <loggers>
    <root level="trace">
      <appender-ref ref="Console" />
      <appender-ref ref="RollingFile" />
    </root>
  </loggers>
    
</configuration>

他にも出来そうなこと

  • 「<property name="LOG_PATH" value="" />」のように定義することもできるような感じなので柔軟に出来そうですね。
  • appenderを追加すれば、状況に応じたログ出力が出来そう(普通のログとJSONのログ等)
  • JSONでログ出力すれば、ログの分析がしやすそう(分析基盤(Fluentdとか)を使ったり作ればですが)

おまけ(その1)


いつもSLF4jを使用するときの組み合わせを忘れるので、ここにメモします。 SLF4jはFacadeパターンを採っていまして、以下のメリットがあるようです。

  • 柔軟な切り替え
  • 効率化

これのお陰で、移行とかがスムーズにできるというわけです。 以下の表に組み合わせをメモしておきます。

Log4j Log4j2 Logback Logging Simple
SLF4J/コア slf4j-api.jar slf4j-api.jar slf4j-api.jar slf4j-api.jar slf4j-api.jar
SLF4J/アダブタ slf4j-log4j12.jar log4j-slf4j-impl logback-classic.jar, logback-core.jar slf4j-jdk14.jar slf4j-simple.jar
ロギング・ライブラリ log4j-1.2.x.jar log4j-core, log4j-api logback-classic.jar, logback-core.jar java.util.logging slf4j-simple.jar

# どちらかと言うとこちらのほうが必要だったかもw

おまけ(その2)


ついでといってはなんですが、他にJavaでよく使いそうなloggingも用意しました。
どれも必要最低限のライブラリしか使っていませんので、動作の参考になるかと思います。

  • Commons Logging + SimpleLog
  • SLF4j + Log4j
  • SLF4j + Logback
  • SLF4j + Log4j2

ここに置きました。

Gradleがあれば使えるようにしてあります。

現場で感じた理想と現実

はじめに


この投稿は DevLOVE Advent Calendar 2013 12月21日の記事です。
前日は nnasakiさんでした。

自己紹介


kurobaraと言います。

こんな感じのことをやったり、考えたりしてます

  • C/Java/AndroidをSIerとしてやってきて、現在はPHP/Rubyを使うwebエンジニア
  • 趣味で、Java系の何かとか,Googleのプロダクト扱ったり、Goをやったりと幅広で技術的なこと
  • プロダクトと技術指向な感じで何か色々とやってる感じ
  • 今後は事業領域やデータ分析的なこともやりたいような予感

経歴というほどの経歴ではないですが、経歴を知りたい方は過去のブログ記事を参照してください。。。

今回の記事は、同僚にぜひにと誘われましたので、良い機会だと思ったので書いています。

前置き


正直なところ、これまで「現場」という感覚で捉えたことが無かったので考えるきっかけになりました。
恐らく、仕事をしてる中では、いかによくやるかとは考えて行動しているはずです。。。

某映画に有名にセリフがあります。

「事件は会議室で起きてるんじゃない。現場で起きてるんだ。」

これは、その通りかなと思っています。
このセリフが出る映画の上映当時、自分はガキだったので「( ´_ゝ`)フーン」って感じでした。

今では、その言葉の意味とか言ってしまいたい気持ちもわかります。

自分にとっての現場とは


思わず前置きを書いてしまいましたが、気を取り直して本題に移ります。

自分は、IT業界での仕事現場しか分からないので、他の業界での現場というものはわかりません。
ただ、敢えて自分の感じる現場というものを一言で表すと「理想と現実の狭間で戦うところ」かなと思います。

ここでいう理想とは、「技術者が技術的にもやれることにも満足している」ような状態。
現実は、「ビジネス的要因があって、技術的なことにチャレンジもできない」状態。
(出来ないは言い過ぎですが、要するにビジネスが先行し、技術者が思う理想ができないような感じのことです)

理想を感じた要因は?

これは難しいですね。
経験というものしかないかと思います。

そもそもとして、以下が出来れば自分の満足度があがるかと思います。

  • プロダクトが価値を提供出来る
  • 悩んでいる人に課題を解決出来る
  • プロダクトで喜んでくれる人がいる

# 褒められると嬉しいというやつですね。(自分もよくあります。)

そのため、に技術者が出来ることは、以下の内容かな?と自分は考えます。
そして、これが理想的に出来れば理想に近いかなと思います。

  • プロダクトの開発サイクルを上げ、価値を出来うる最速で提供できること
  • プロダクトに対して、きっちりTDD/BDDが回せて品質まで担保できること
  • プロダクトに対する技術的な負債を限りなく0まで減らしていけること
  • プロダクトの開発サイクルや開発機能そのものの改善が出来ていること
  • プロダクトに対して、技術的なチャレンジが出来、成長できること
  • 技術者が今思う、トレンドに触れていられること

どれも、当たり前の発想だし当たり前に出来ていることが理想型だと私自身は感じています。

理想はまぁなんとなく理解したが、現実は?

これはもう、大体分かることかなーと思います。
数値が出れば出るほど特に出来なくなりつつあるという感じですね。

わかりやすいところでいうとこんな感じかも

  1. 実装はできるが、時間に余裕が無い
  2. 機能の割にテストを書いてる余裕がなくなる
  3. 技術負債が出来上がる
  4. 追加要件が出てくるが、技術負債のお陰ですすまない
  5. 技術負債のお陰で余裕があってもテスト書いてられない
  6. 開発サイクルが下がってくる・・・
  7. (ry

上に挙げた技術者としての理想が出来なくなってくるという状況

これを改善するには・・・
ということになるとビジネスサイドに人間に対して0から説明する必要が出てきます。

説明が面倒とは言いません。
寧ろやるべきだと思います。(最終判断は、ビジネス要件も絡みますので落とし所は着くかと思います。)
技術的な負債などを抱えるとモチベーションが下がり、価値の提供に繋がらなくなってきつつあるのかなと思います。

最後は現状に対する悪態しか出てこないとかに陥ったりするかもしれないですね。
(こうなると、技術だけでなくプロダクトそのものを貶すことになるので)

他に挙げるとすると、技術的に満足度が高い状況でやっていてもビジネスサイド側の要求で出来なくなるというのもあるかもしれないです。
(現場からすると謎な社内パワー等)

# これは元々の自分の経験なので・・・

まとめ


色々と長くなりましたが、自分の思うところをツラツラと書いてしまいました。

何が言いたいかというと

「現実は往々にしてあるので、如何に理想に近づけていけるか」

これが、自分の思う現場かな?と思います。(最近はよく思います)

そのために、技術者側も以下をやっておく必要があるのではないか、そう思います。

  • 事業のことについて多少なりとも知っておく
  • 事業サイド側にコミットメントをする
  • 数値分析や事業サイド側の数値を知る
  • 事業の目的や目標、到達する先を知る

明日は・・・


明日は、storywriterさんです。
storywriterさん、よろしくお願いします。

ActiveAndroidを使ってみた

はじめに


久しぶりにオレオレ用のAndroidアプリを書きました。
その際、DBを使ったのですが、面倒くさいことはしたくなかったので
Android向けO/R MapperであるActiveAndroid使いました。

その時のメモを備忘録な感じで残します。

準備


以下のようにして、ライブラリを自分でビルドします。
ビルド完了後、プロジェクトのビルドパスにライブラリを追加します。

1
2
3
$ git clone https://github.com/pardom/ActiveAndroid.git
$ cd ActiveAndroid
$ sh gradlew build

設定


2種類の方法で利用準備ができます。

  1. AndroidManifest.xmlに設定する
  2. Applicationクラスを継承した独自のクラスを用意する

1のやり方は以下のような形で定義します。

  • meta-dataタグを用意し、そこにDB名前とバージョンを記載
  • android:nameに「com.activeandroid.app.Application」を記載
1
2
3
4
5
6
7
8
9
10
11
<application
        android:allowBackup="true"
        android:name="com.activeandroid.app.Application">
        ・・・

        <meta-data android:name="AA_DB_NAME" android:value="test.db" />
        <meta-data android:name="AA_DB_VERSION" android:value="1" />

        ・・・

</application>

2のやり方は、以下のようにして設定内容を組み立て、起動と停止処理を組み込みます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyApplication extends Application {

  @Override
  public void onCreate() {
    super.onCreate();
    Builder builder = new Configuration.Builder(getBaseContext());
    builder.setCacheSize(1024*1024*4);
    builder.setDatabaseName("test.db");
    builder.setDatabaseVersion(1);
    ActiveAndroid.initialize(builder.create(), true);
  }

  @Override
  public void onTerminate() {
    super.onTerminate();
    ActiveAndroid.dispose();
  }

上記の内容では、以下のものを設定しています。

  • キャッシュサイズ4MB
  • DBを「test.db」
  • バージョンを「1」
  • ActiveAndroidのログを有効化(initializeメソッドのtrueのこと)

個人的には、以下の理由から組み立てて実行するほうがよいかと思います。

  • ActiveAndroidの設定に引きづられず、自分のアプリに影響が出ないこと
  • DBのキャッシュサイズを決められる(デフォルトが1KBのため)
  • progurdによる難読化ができる(AndroidManifest.xmlに書けば見え見えになりますが、コードだと難読化ができる)

尤も、最後のはコード上でstaticに文字列として定義するとあまり意味がないですが・・・

モデル定義


以下のような感じでアノテーションをつける形で定義します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Table(name = "Items")
public class Item extends Model {

  @Column(name = "id", notNull = true)
  public int id;

  @Column(name = "name", unique = true, onUpdate = ForeignKeyAction.NO_ACTION)
  public String name;

  @Column(name = "category", onNullConflict = ConflictAction.REPLACE)
  public String category;

  public List<Test> tests() {
    return getMany(Test.class, "test");
  }
}

アノテーションに付けれる内容は以下の表のような感じのものです。
殆どがAndroid本家のページのConstants(CONFLICT_XXXと記載されたもの)なので
こちらの説明を読んで設定すればよいかと思います。

定義先 定義内容 意味
クラス name 対応するテーブル名
変数 name 対応するカラム名
length 長さの制約(デフォルト -1(無制限?))
notNull not null制約(デフォルト false)
onNullConflict null制約違反時の動作を設定する(デフォルトはFAIL, 設定内容はAndroid公式参照)
onDelete 削除時の外部キーの動作設定(CASCADE, RESTRICT等が設定可能)
onUpdate 更新時の外部キーの動作設定(CASCADE, RESTRICT等が設定可能)
unique ユニーク制約(デフォルト false)
onUniqueConflict ユニーク制約違反時の動作を設定\n(デフォルトはFAIL, 設定内容はAndroid公式参照)

意味が必ずしも正しいかと言われると少し自信がありませんが、多分こんな感じかと。

また、1対Nのような関係を作るときは、getMenyメソッドを実装したヘルパーメソッドを作る必要があるようです。

クエリ

クエリについて、書いていきます。

以下を使ったことがあればすんなり理解できるかと・・・

  • JavaのO/R Mapper(s2jdbc等)
  • RubyのActiveRecord

挿入


以下のような感じで記載します。
ActiveRecordと殆ど同じですね。

1
2
3
Item item = new Item();
item.name = "test";
item.save();

以下のようにすれば、バルクインサートもできます。 また、トランザクションも併せて記載します。

1
2
3
4
5
6
7
8
9
10
11
ActiveAndroid.beginTransaction();
try{
  for(int i = 0; i < 1000; i++ ){
    Item item = new Item();
    item.name = "test_" + i;
    item.save();
  }
  ActiveAndroid.setTransactionSuccessful();
}finally{
  ActiveAndroid.endTransaction();
}

トランザクション中にエラーが出てもthrowされないので、注意して下さい。
コンフリクト周りでエラーが出る場合は、モデルのアノテーションに定義した内容が適用されます。

削除


やり方は、以下の3種類です。

  1. オブジェクトをロードしてから削除する方法
  2. 静的メソッドを使用して削除する方法
  3. クエリを組み立てて削除する方法

1のやり方は、以下です。
一度、selectしてから削除という感じです。

1
2
Item item = Item.load(Item.class, 1);
item.delete();

2のやり方は、以下です。
主キーさえ分かっていれば、こちらのほうが無駄なクエリを発行しない分早いです。

1
Item.delete(Item.class, 1);

3のやり方は、以下です。
複数の行をまとめて削除できたりします。

1
new Delete().from(Item.class).where("Id = ? and name = ?", 1, "test").execute();

参照


これも直感的に記述できます。
使い方は、s2jdbcに似ています。

1
2
3
4
5
Item result = new Select().from(Item.class).executeSingle();

List<Item> resultSet = new Select("id", "name").from(Item.class).execute();

List<Item> resultSet = new Select().from(Item.class).where("Id = ? and name = ?", 1, "test").execute();

上記のような形で実行できます。

  • 先頭1つのカラムデータのみ取得
  • カラムを指定した取り方
  • 条件付き(s2jdcのようにandメソッドとかはありません)

テーブル結合時は、以下のような形で記述します。
現状では、以下の方法しか手段がないようです。

  1. ActiveAndroidでクエリオブジェクト作成
  2. 1で作成したクエリオブジェクトからsqlを作成
  3. 2のsqlを直接実行しCursorクラスのオブジェクトを取得
  4. Cusorクラスからデータを取得する(whileループ等で・・・)
1
2
3
From query = new Select().from(Item.class).innerJoin(Hoge.class).on("Hoge.id = Item.id");

Cursor cursor = Cache.openDatabase().rawQuery(query.toSql(), query.getArguments());

感想


こんな感じな印象でした。簡単なものだと今後も使うかも。

  • ActiveRecordライクなので、扱い易い
  • Modelクラスを継承したクラスを作るだけで、面倒な処理を大幅に軽減できる
  • テーブル定義にコードが引きづられやすくなるので注意
  • 複雑なクエリの場合、通常のSQLiteのDB操作と変わらなくなる

資料


身内向けの勉強会で、ActiveAndroidについて発表してきました。 その時、書いた資料とかも残しておきます。

Time Capsule(NAS)をMacとLinuxの両方から使えるようにする

はじめに


糞お高いNASを買ってしまいました・・・
というか、買わざるを得ませんでしたorz

とりあえず、こんな理由で選定

  • 自宅の外付けHDDが故障気味
  • Macも自動でバックアップ取っておきたい

勿論、QNAPでも良かったのですが、以下の理由で諦め

  • HDDが別売とかでお高い(故障交換ができるのは強みですが・・・)
  • サイズが大きい
  • ルーター機能までついてるので省スペース

で、バックアップだけではもったいないため、 よく使うOSで表題のようにNAS利用を使用と思いました。

やってみる


やるまでに結構苦労しました。
そもそも、どうやって手をつけるのか若干悩みましたw

障壁

Mac側(よしなにやってくれるが故の問題)

  • GUI操作で自動的にマウントされる
  • 自動的にマウントされるが故に何処にマウントされたか不明
  • マウント先不明の為、コマンドでlsとかcd実行できない

Linux側

  • 使用プロトコルが分からない
  • マウントするためのファイルシステムは???
  • そもそもマウント先のIPとかディレクトリが不明
解決編

AirMacユーティリティをGUI操作して、以下を設定

  • ファイル共有にチェック
  • 共有ディスクのセキュリティをアカウントに変更(デフォルトはディスクパスワード)

他の項目は、すぐにわかりました

  • IPはすぐに発覚(GW + 末尾に数字の1追加)
  • プロトコルはafpもGUI操作時の情報で発覚

Mac側は、afpをマウントする際のコマンド(mount_afp)を参照することでわかりました

以下のコマンドを実行するとよいようです。

1
$mount -t afp afp://YOUR_USERNAME:YOUR_PASSWORD@<TimeCapsuleのIP>/Data <マウント先ディレクトリ

umountコマンドで普通に実行できます。

Mac側が判明したので、Linux側もほぼ同じようにしてできました。 但し、ファイルシステムを「cifs」に設定することやディスクの指定方法が異なりました。

以下のようにすればできます。

1
$mount -t cifs -o user="YOUR_USERNAME",password="YOUR_PASSWORD" //<TimeCapsuleのIP> <マウント先ディレクトリ>

シェル化


一々、上記の内容を手打ちするのは面倒なので、シェル化しました。

こんなことをします。

  1. マウント先のディレクトリをチェック
  2. ディレクトリがなければ作成
  3. ディレクトリにマウント実行

以下の環境で動作確認しています。

  • MacOSX 10.8.5
  • CentOS6.4 x86_64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh

# TimeCapsuleの共有ディスクをアカウント認証にしておくこと
USER="YOUR_USER_NAME"
PASSWORD="YOUR_PASSWORD"
MAC_DIRECTORY="/Volumes/timecapsule"
LINUX_DIRECTORY="/mnt/timecapsule"
TC_DIRECTORY="YOUR_TIMECAPSULE_DIRECTORY"

if [ `echo "${OSTYPE}" |grep "linux*"` ]&&[ ! -d "$LINUX_DIRECTORY" ]; then
    mkdir -p "$LINUX_DIRECTORY"
elif [ ! -d "$MAC_DIRECTORY" ]; then
    mkdir -p "$MAC_DIRECTORY"
fi

if [ `echo "${OSTYPE}" |grep "linux*"` ]; then
    # for Linux
    # use cifs-utils(RedHat)
    mount -t cifs -o user="$USER",password="$PASSWORD" //"$TC_DIRECTORY" "$LINUX_DIRECTORY"
else
    # for Mac
    # ユーザ:パスワード@TimeCapusleのIPアドレス
    mount -t afp afp://"$USER":"$PASSWORD"@"$TC_DIRECTORY" "$MAC_DIRECTORY"
fi

Rails(ActiveRecord)でBULK INSERTする方法

はじめに


RailsでRake Taskなんか作ったときは、バッチ処理するようなことを書くと思います。
多分ですが・・・

この前、バルクインサートをすることがあったのでやり方を残しておきます。

因みに、普通にrubyスクリプトの中でも使うこともできると思います(これも多分w)

やり方


activerecord-importというGemを使用します。

Gemfileに以下を追加すれば、Rake Taskで使用することができます。
ぶっちゃけRake Taskだけではなく、普通に使うこともできますが・・・

1
gem 'activerecord-import'

利用できるデータベースは以下のものです。

  • mysql(アダプターはmysql, mysql2の両方で使用可能)
  • postgresql
  • sqlite3

前提


かなり簡単です。

以下のようなモデルを用意したとします。

  • モデル名:Hoge
  • カラム(主キー): id
  • カラム(名前): name
  • カラム(テキスト): text

このモデルを10個まとめてバルクインサートしたいとします。

やり方


かなり簡単です。。。

  1. バルクインサートしたい数だけモデルオブジェクトを格納したリストを用意する
  2. 該当のモデルのimportメソッドに1で作成したリストを渡す

コードに起こすとこんな感じです。。。

1
2
3
4
5
6
hoge_list = []
10.times do |i|
  hoge_list << Hoge.new(id: i, name: "hoge #{i}", text: "fugafuga")
end

Hoge.import hoge_list

実行するとこんな感じのSQLが一気に発行されます。。。

1
2
INSERT INTO `hoge` (`id`,`name`,`text`,`created_at`,`updated_at`) VALUES (1,'hoge 1','fugafuga','2013-11-25 00:30:30','2013-11-25 00:30:30') ON DUPLICATE KEY UPDATE `messages`.`updated_at`=VALUES(`updated_at`)
INSERT INTO `hoge` (`id`,`name`,`text`,`created_at`,`updated_at`) VALUES (2,'hoge 2','fugafuga','2013-11-25 00:30:30','2013-11-25 00:30:30') ON DUPLICATE KEY UPDATE `messages`.`updated_at`=VALUES(`updated_at`)

因みに、以下のようなオプションがあります。 利用する場合、importメソッドの引数にハッシュで指定するだけです。

  • 「:on_duplicate_key_update」: ユニークキーが重複したカラムを更新したい場合に設定
  • 「:timestamps」: falseを設定すると、自分でcreated_at,created_on,update_at,update_onを設定
  • 「:validate」: falseを設定すると、モデル検証をスキップする(デフォルトはtrue)

こんな感じで設定します。。。

1
2
columns = [:id, :name, :text]
Hoge.import hoge_list, :on_duplicate_key_update => columns, :timestamps => false, :validate => false

使用感


結構、問題ない感じで使えました。。。

自分が使用した感じだと10万件の更新に1分程度かかったので、
データが増えていくようなモデル(テーブル)に対して、多用するのは危険かなと思いました。

コードベースで、バルクインサートやバルクアップデートができるところがいいですね。
データをまとめて引っ張って→データ処理→まとめて更新なんかの使い方では便利かもw

ActiveRecordメモ その2

前回書いたので、メモをまたまた残しておく

今回は、以下の3つを書き起こす。

  • 既存行の更新処理
  • データ保存系メソッドの違い
  • 既存行の削除処理
  • コールバック

既存行の更新


1.更新する行を特定してから更新するやり方

1
2
3
table = Tables.find(12)
table.name = "column name"
table.save

2.属性の値を変更してからモデルオブジェクトを取得するやり方

1
2
table = Tables.find(12)
table.update_attribute(:name, "column_name")

上はシンボルを使って、特定の1カラムのみの更新の場合

まとめて行を更新する場合、ハッシュを使いますね。

1
2
table = Tables.find(12)
table.update_attributes(name: "column_name", text: "ActiveRecord")

因みに、Railsだとこんな感じで使ってますね。

1
2
3
4
5
6
7
8
def update
  table = Tables.find(params[:id])
  if table.update_attributes(params[:table])
    redirect_to action: :index
  else
    redirect_to action: :edit
  end
end

3.行の読み込みと更新を一度に行う方法

これは特に言うこともないですね。
指定した行を更新し、結果をDBに反映、そのままオブジェクトを取得します

1
table = Tables.update(12, name: "column_name", text: "ActiveRecord")

因みに、update_allメソッドを使えばまとめて更新することができます。
第1引数にupdate文、第2引数にwhere句を指定という形でやります。

1
table = Tbales.update_all("name = column_name, text = ActiveRecord", "id = 12")

戻り値は、データベースのアダプターによって異なるようです。
大体、更新行数が返るようですが、Oracleだけ何故か違うみたいです。。。

データ保存系メソッドの違い


メソッド 違い
save レコードが保存された場合、trueを返し、そうでなければnilを返す
save! 保存が成功した、trueを返し、そうでなければ例外を発生
create 保存に成功したかどうかは関係なく、ActiveRecordオブジェクトを返す。データが保存されたかどうかの確認は検証チェックが必要
create! 成功時は、ActiveRecordオブジェクトを返し、そうでなければ例外を発生

なので、使い分けはこんな感じに行う

1
2
3
4
5
if table.save
  検証と保存に成功
else
  検証エラー
end
1
2
3
4
5
6
7
begin
  table.save!
rescue ActiveRecord::RecordInvalid => error
  検証エラー
rescue ActiveRecord::RecordNotSaved => error
  保存に失敗(コールバックで保存が出来なかった)
end

既存行の削除処理


これは2種類の方法があります。
1つ目、データベースを直接操作する感じのやり方

こんな感じで、id指定で削除できます。
idを格納した配列を指定することで、まとめて削除もできます。

1
Tables.delete(12)

SQLのwhereを使ってまとめて削除する方法

1
Tables.delete_all(["id = 12"])

これもupdate_allメソッドと同じで削除した行数が返ってきます。

もう一つのやり方は、destroyメソッドを使う方法です。
こいつは、削除というよりもデータの凍結ですね。

特定の行のみ凍結

1
Tables.destroy(12)

まとめて凍結する方法

1
Tables.destroy_all("name = ?", "column_name")

因みにdeleteメソッドを使った場合、
ActiveRecordの検証メソッドが実行されないことに注意かも。

コールバック順序


コールバックで独自に処理を入れたい場合ってあると思うので、ついでにメモしておく

新規レコードでmodel.saveを実行した場合、以下の順序でコールバックされる

  1. before_validation
  2. 検証処理
  3. after_validation
  4. before_save
  5. before_create
  6. 挿入処理
  7. after_create
  8. after_save

更新の場合だと以下になる

  1. before_validation
  2. 検証処理
  3. after_validation
  4. before_save
  5. before_update
  6. 更新処理
  7. after_update
  8. after_save

削除(model.destroy)だと、以下になる

  1. before_destroy
  2. destroy処理
  3. after_destory

ここまでそれなりに書いたので、もうちょい参照系も書いておきたいなw

ActiveRecordメモ

使ってるけど、意外と知らなかったのでメモ

抽出方法は全部同じ


whereの条件指定の方法ですね。

以下の3つは、全て同じSQLを発行します

1.Hashで条件設定する方法(AR独自チックな感じで大体使う方法)

1
Hoge.where(:title => "title", :id => 1)

2.プレースホルダで地道にパラメータ設定する方法(どの言語でも通用する)

1
Hoge.where("title = ? and id = ?", "title1", 1)

3.シンボルで対応付けをわかりやすくする方法

1
Hoge.where("title = :title and id = :id", {:title => "title", :id =>1})

抽出結果がなかったらレコードの新規作成をする


これ、一発でできるメソッドあったんですね。
知らなかった。。。

どうやらfirst_or_createというメソッドを使えばよいみたい。
で、ブロックを取ることができるので、ブロック内で挿入する値を自由に設定できる

以下のような感じで書くとできる模様。

1
2
3
Hoge.where(:title => "title").first_or_create do |p|
    p.body = "hogehoge"
end

結局は裏で2回SQL発行してるだけなんですけどね。(selectとinsert)

と、まぁ色々とありますがこんなところで。

EdTech Hackathonに行ってきた

久しぶりにHackathonに行ってきた気がする。

成果は・・・

「これからのIT業界を支える未来のエンジニアを育成できました!!」

これだけだと、「Hackathonなのにコードの成果がなくないか?」
ってことになりかねないし何の事かさっぱり分からないと思うので当日の詳細を以下に載っけます。

アイデア出し


通称、「Ideathon」なんだけども・・・
前日に人が集まらずだったので、チームとか作る内容もあんまりいい感じには決まらなかった。

例外は、自分を含めたチームぐらいでした。
一応、大雑把なアイデアを持っていたので、募集かけたらすんなりと決まりました。

チームメンバーというか役割は、以下の5人です。

  • プログラム 自分
  • プログラム 大学生
  • デザイン + ディレクター 本職の方
  • 素材、画像作成、プレゼン : 中学生

こんな構成でした。今回は中学生がいるんですよ。中学生!!
最年少参加でした。。。

所謂、学生チームですね。

# 自分がHackathonに初参加してた時よりもまだ若いのですよ。
# 時代は進むものです。

ディレクターとの合言葉は、「俺達、老害なんで彼らの成長を支えよう」ということでした(ぉぃ

# 因みに、後で知ったことなのですが・・・
# チーム内で自分が一番最年長でした(> <;)

プログラマー側の打ち合わせでは、以下のことを決めました。

  • 使用言語はJavaにする(大学生たっての希望)
  • フレームワークは、Seaser2(大学生が使用したことあるそうなので・・・)
  • サーバは、Tomcat7.x
  • DBはmysql 5.6.13
  • 上記、開発環境の準備をしてくること

SIer等でよくありがちな、Javaを使ったWebアプリ開発ですね。

# 僕もいい大人(?)なので、希望は叶えてあげたいものです
# 主催者が、学生に対して、自分をJavaエンジニアとして学生に推薦したのもありますが・・・

Hackathon当日の開始前


色々とドタバタしました。
手持ちで持ってきたモニターがディレクターさんのPCで使えないなどのハプニング。

元々のコンセプトのすり合わせを開始前のこの時点で話し合いました。

ハプニングの最中にディレクターさんと2人で本日のゴールを何処に落としこむかを話していました。

# 学生さん達には秘密会議的にやってて申し訳なかった
# 実はこの時点で、ある程度本日の着地点(現実的に出来そうな範囲)の見積もりを立ててました。

Hackathon当日の午前


開始と同時に、「こういう機能でこういう表示方法にしましょう」ということを
ディレクターさんとワイヤーフレームモデルを使って合意を取りました。

# ここは勝手に進めて学生さんに申し訳なかった・・・。

デザイナーとして活躍してくれる中学生は、ディレクターさんに任せ、プログラマー側はプログラマー同士で作業をすすめることにしました。

プログラマー側では、大学生側の開発環境が出来ていなかったので、開発環境を構築することにしました。

tomcatのインストールやmysqlのインストール&設定、eclipseの設定を教えながら進めました。

インストールの待ち時間の間、作成するものの以下の内容について説明してました。

  • アプリケーションの説明
  • 表示する画面の構成要素の説明&簡単な構成案
  • DB設計

この中で以下のついてどうやって見つければ良いかの説明を行いました。

  • どうやればDB設計ができるか
  • 画面の構成要素はどう決めればよいか
  • 暗黙的な要件の見つけ方(画面に現れない&前提としている内容等)

そうこう説明しているうちに午前中が終了でお弁当タイムになりました。
唐揚げ弁当うまかったなー

Hackathon当日の午後


午前中に開発環境の構築や設計周りの教示に費やしていたので、
当初のゴールまで難しいことが、この時点で判明。

ディレクターと話をして、「彼らのスキルアップというか育成に費やそう」ということにしました。

# EdTechということで教育がテーマだからいいかーということでお互いに合意しました。

はじめは、自分で進めてみようということで、お互いの機能を実装することに専念しました。
自分の担当した機能は実装と簡単な動作試験まで完了することができました。

# この間、1時間無いぐらい。

この後、終了時間30分前までは、大学生につきっきりになり、以下のことをマンツーマンで教示しました。
所々、ペアプロも含んでいます。

  • リーダブルコード的なこと(メソッド名の命名規則や付随すること諸々)
  • スコープの話と(自分の考える)ポリシー(publicにしたら何処からでも使われるので、javadoc書く等)
  • Seaser2の設計思想とMVC的な話
  • Seaser2の実装方法や設定の説明&解説
  • コードの書き始めはどうしたらよいのか(逐次的に処理概要をTODOで書いてみるなどのやり方の話)
  • コードのリファクタリング等
  • eclipseのショートカットキーや設定全般

ざっと覚えてるのは、この辺りだけどもっとなにか色々と偉そうに言ってたような気がする。。。

終了まで残り30分のところで、自分が作った部分の機能とデザインをマージ作業しました。
マージ作業中は、大学生放置しましたけど、許して下さいm( )m

久しぶりにwebapp/WEB-INF周りを弄ったので、jsp含んでた場合リソースの扱いに注意すること忘れてトラブりました。

# 今回の場合だと、「webapp」ディレクトリ直下に置かないと認識されませんでしたw

最後のプレゼンは、中学生が頑張ってくれました!!
自分には中々できないプレゼンや将来的にこういう機能もあればいいかもーみたいな案も考えてくれていました!!

感想


久しぶりにいい刺激を受けました。
社内にいてもいい刺激を受けることが多いですが、外部のエンジニアとの交流もいいものです。
何よりものを作り上げるのは楽しいです。

今回のHackathonに関してはものを作るよりも、教育していたほうが長かったです。
ですが、自分にも得るものがありましたし、復習の機会になりました。
大学生の彼が、自分の拙い説明で何かを得てくれれば自分も嬉しいと思います。

# 自分が初めてHackathonに出た時のようにですが・・・

Hackathonで構築した開発環境と簡単な設計メモ


以下に、説明に使用したメモを残します。
こいつを参考にするとTomcat7.x + Eclipse + Seaser2な開発環境は構築できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
★tomcatインストール
$brew install tomcat

1.ホームディレクトリの.bash_profileに以下を追加

export CATALINA_HOME='/usr/local/Cellar/tomcat/7.0.42/libexec'
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.7.0_25.jdk/Contents/Home
PATH=${JAVA_HOME}/bin:$PATH
PATH=$PATH:${CATALINA_HOME}

2.ターミナル上で$source .bash_profileを実行
3.tomcat起動「/usr/local/Cellar/tomcat/7.0.42/libexec/bin/startup.sh」
4.tomcat停止「/usr/local/Cellar/tomcat/7.0.42/libexec/bin/shutdown.sh」


Homebrewが無ければ、インストールして下さい
使用しているShell(bash以外)が違う場合は適宜読み替えて下さい

★eclipse
1.「http://www.eclipsetotale.com/tomcatPlugin.html」から最新のプラグインをダウンロード
2.ダウンロードしたzipを解答して、できたディレクトリごと
eclipseのディレクトリの下に移動
3.eclipseを再起動
4.設定→tomcat
5.tomcatのバージョンを7に変更
6.HOMEを/usr/local/Cellar/tomcat/7.0.42/libexec
7.適用を押す


これでeclipse上からtomcat操作ができる


★mysql
brew install mysql

DBの保存先とキャッシュの保存場所の設定
mysql_install_db --verbose --user=`whoami` --basedir="$(brew --prefix mysql)" --datadir=/usr/local/var/mysql --tmpdir=/tmp

rootパスワードの設定
mysql.server start
mysqladmin -u root password '好きなパスワード'
mysql -uroot -p

何でもできるユーザーの追加(本当はここはしっかりと考える必要あるけど今回はハッカソンなのでパス)
GRANT ALL PRIVILEGES ON *.* TO edtech@localhost IDENTIFIED BY 'edtechhackathon' WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON *.* TO edtech@'%' IDENTIFIED BY 'edtechhackathon' WITH GRANT OPTION;
mysql -uedtech -pedtechhackathon

データベースの作成
create database edtechhackathon;


これで開発をします。
ユーザー名:edtech
パスワード:edtechhackathon

jdbcドライバはこんな感じかな
jdbc:mysql://localhost:3306/edtechhackathon
接続ユーザー:edtech
接続パスワード:edtechhackathon



★Doltengのインストール(一応)
したがって行う
http://dolteng.sandbox.seasar.org/
→DBからエンティティを自動生成するので使う

★eclipsemarket placeからインストール
プロパティエディタのインストール
maven integrationプラグイン

★Devloder
mkdir -p /usr/local/Cellar/tomcat/7.0.42/libexec/server/lib
$cp /Applications/eclipse/plugins/com.sysdeo.eclipse.tomcat_3.3.0/DevloaderTomcat7.jar /usr/local/Cellar/tomcat/7.0.42/libexec/lib/

★mysql追加
mysql.server stop
cp /usr/local/Cellar/mysql/5.6.13/my.cnf /usr/local/Cellar/mysql/5.6.13/my.cnf.20131026
「port = 3306」を記述
mysql.server start

★確認用データ投入
mysql -uedtech -p -hlocalhost edtechhackathon
CREATE TABLE sample(id int auto_increment primary key, data varchar(255) NOT NULL UNIQUE, created_at datetime NOT NULL) ENGINE=InnoDB; 
insert into sample(data, created_at) values('test', '2013-10-26');



★usersテーブル
入力項目は、以下のもの
・名前
・メール
・パスワード(アプリ側でBlowfish形式で暗号化しておくこと、暗号化の鍵はedtechとパスワード入力値とする。初期でinsertする内容は「test」にする)、
・Roleは1(教師)、0(親)、2(管理者)で表現(アプリ側で数値を見てRoleの判断する)

create table users( user_id int auto_increment primary key, name varchar(255) not null, mail varchar(255) not null, password varchar(255) not null, role int not null) ENGINE=InnoDB; 

★fixture
insert into users(name, mail, password, role) values('hoge', 'hogeATgmail.com', '0791e4e0ade0f161', '2');
insert into users(name, mail, password, role) values('fuga', 'fugaAtgmail.com', '0791e4e0ade0f161', '1');
insert into users(name, mail, password, role) values('hogehoge', 'hogehogeAtgmail.com', '0791e4e0ade0f161', '0');

Octpressで記事を一度公開停止→再公開をしたい場合の方法

何個か前の記事を加筆修正したときの手順をまとめます。
但し、このやり方はpush内容を残したくないときのやり方に加えて、
ハッシュ値を知ってれば参照できてしまうという点があります。
(公開されているページは修正されたものが表示されます)

記事の公開停止方法


以下の順序で変更を行います

  1. octpressでgithubにpushしているディレクトリに移動する
  2. gitコマンドで記事をpushしたコミットまで遡って、 変更取り消しを行う

それぞれコマンドは以下のようになります

1
2
$cd ~/octpress/_deploy
$git push -f origin master:HEAD^^

これで、2つ前の記事公開の状態まで変更することができました。 同じ方法をとれば、記事の順序を入れ替え(削除)した状態で公開することも出来ると思います。
(git rebase後にpush origin masterなどすればです)

再公開方法


普通に記事を生成して再度サーバにpushして下さい。
ファイルの日付や記事の日付を修正していなければ、 公開停止前と同じURLになります。

packerを使用して、VirtualMachineで動くVagrant用のBoxファイルを作成した

昨日(日付の上では、一昨日)にあったGo Conference 2013 autumnに行ってきました。

野良Hackathonがあったので、久々のGo慣らしも兼ねてpackerで1つ環境用意してやるかといった感じでやってました。
勿論、イメージ作ってる待ち時間の間にpackerのソースも読んでました
正直、ドキュメント読んで(?)ってなったところもあったので読んだとかそんな感じです。

Google I/O以来に会った@ymotongpooさんにLTやらね?って誘われた。
然しながら、Goのネタが今手元に無かったので、LT時刻までにpackerで環境用意できたらやるって言ったけど、出来なかったのでLT諦めた。
後で、すごい誤った。(完成したのは、LT終了して暫くしてからだった)
@ymotongpooさん、もし見てたら次回のGoConではLTやれるように準備しておきます。

という訳で、代わり(?)に表題の内容をやります。

最終ゴール


以下のコマンドでVirtualBox上で動作するCentOS6.4(64bit)が起動してログインできるようにする

1
$vagrant up

但し、Vagrant側では、特にアプリケーションのインストールを実施せず、packer側で以下の2点を満たすこと

  • 最小インストールしたパッケージを全て最新化すること
  • Gitをインストールすること

前提


前提がないとお話にならないので、以下が入ってることにします。

  • Vagrant 1.3.4
  • VirtualBox 4.2.16
  • Go 1.1.2

こいつらが入ってない場合は、入れて下さい。

あと、実行環境はMacでやってますが、Linuxも似たような感じでできると思います。
Windowsは知らないですが多分できるんじゃないかなぁと思います。

packerのインストール


packerをインストールします。
公式の手順に従えば1発でインストールできます。

1
2
$brew tap homebrew/binary
$brew install packer

OSインストール用のkickstartファイルを用意する


kickstartは、RHEL系(CentOSなど)OS のインストール&セットアップが自動化できることを指します。
で、一応OSインストールした時の構成がOS側で記録されていまして、「/root/anaconda-ks.cfg」がkickstartファイルになります。
他にも、system-config-kickstart.noarchをインストールすることでGUIで設定ファイルを用意できるようになっています。

以下のような感じで最小構成インストールファイルを用意します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
install
cdrom
lang en_US.UTF-8
keyboard us
network --bootproto=dhcp
rootpw --iscrypted $1$FB.fOroc$b2.YcN30BjrYxlUbECUxO1
firewall --enabled --service=ssh
authconfig --enableshadow --passalgo=sha512
selinux --disabled
timezone UTC
bootloader --location=mbr

text
skipx
zerombr

clearpart --all --initlabel
autopart

auth  --useshadow  --enablemd5
firstboot --disabled
reboot

%packages --nobase
@core
%end

%post
/usr/bin/yum -y install sudo
/usr/sbin/groupadd moonstruckdrops
/usr/sbin/useradd moonstruckdrops -g moonstruckdrops -G wheel
echo "moonstruckdrops"|passwd --stdin moonstruckdrops
echo "moonstruckdrops        ALL=(ALL)       NOPASSWD: ALL" >> /etc/sudoers.d/moonstruckdrops
chmod 0440 /etc/sudoers.d/moonstruckdrops
%end

最小インストールを実施した後にsudoをインストールし、ログインユーザーを新規に追加しています。

因みに、rootパスワードは以下のコマンドで生成します。
生成された文字列をrootpwに記述します。

1
$openssl passwd -1

jsonファイルを作成する


細かい解説は抜きにして、以下のような感じで作成します。
大体、ドキュメントに書いてあるので直感的に分かるかと思います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
    "builders": [
        {
            "type": "virtualbox",
            "vm_name": "centos_box",
            "guest_os_type": "RedHat_64",
            "disk_size": "20000",
            "format": "ovf",
            "hard_drive_interface":"sata",
            "boot_wait": "5s",
            "iso_url": "http://ftp.iij.ad.jp/pub/linux/centos/6.4/isos/x86_64/CentOS-6.4-x86_64-minimal.iso",
            "iso_checksum_type": "md5",
            "iso_checksum": "4a5fa01c81cc300f4729136e28ebe600",
            "ssh_username":"root",
            "ssh_password":"vagrant",
            "ssh_port": 22,
            "shutdown_command": "shutdown -h now",
            "guest_additions_path": "VBoxGuestAdditions_.iso",
            "virtualbox_version_file": ".vbox_version",
            "vboxmanage":[
                ["modifyvm", "", "--memory", "1024"],
                ["modifyvm", "", "--cpus", "2"]
            ],
            "http_directory": "./builders/",
            "boot_command": [
                "<tab> text ks=http://:/ks.cfg<enter><wait>"
            ]
        }
    ],
    "provisioners": [{
        "type": "shell",
        "inline": [
            "sleep 30",
            "sudo yum -y update",
            "sudo yum -y install git"
            ]
    }],
    "post-processors": [{
        "type": "vagrant",
        "output": "centos.box"
    }]
}

ビルドしてイメージを作成する


作成したjsonに誤りがないことを確認する

以下のコマンドを使用して確認する

1
$packer validate

以下の実行結果になれば、エラーなく記述できている

1
Template validated successfully.

エラーだと、こんな感じでどこが誤っているか教えてくれる

1
Failed to parse template: Error in line 14, char 8: invalid character '"' after object key:value pair
ビルドを行う

うまくいけば、期待した結果の仮想マシンができている

1
$packer build -only=virtualbox centos.json

vagrantを使ってインストールしたOSを起動する


やることは非常に簡単です。

まず、初期化します。

1
$vagrant init

vagrantの設定ファイルを編集します。

  • 「config.vm.box_url = “your_boxfile_path”」に生成したファイルのパスを記述
  • 「vb.gui = true」 を有効化(コメント削除)

最後に起動します。

1
$vagrant up

うまくいけば、最終ゴールの内容で起動します。

嵌ったビルドエラー


ビルドを行うと以下のようなビルドエラーに遭遇します。
エラーになると、以下のようなエラーになります。

1
2
3
4
5
==> virtualbox: Waiting for SSH to become available...
==> virtualbox: Timeout waiting for SSH.
==> virtualbox: Unregistering and deleting virtual machine...
==> virtualbox: Deleting output directory...
Build 'virtualbox' errored: Build was halted.

解決方法ですが、「”ssh_wait_timeout”」の値を長めに設定する、もしくはjsonに記述しない(デフォルトは20min設定のため)こと
少し考えれば理解できた話なんですが、そもそもこのsshはどこで使用するかというとOSインストール完了後、即ちprovisionersで利用します。
そのため、インストールしたOSに接続するためsshが必要になるということです。(ゴールの場合だと、updateとgitインストール)
こういうことから、OSインストールが終わるまでssh接続できないからsshがタイムアウトしていたということです。

そもそも、公式のドキュメントに「”ssh_wait_timeout”:”30s”」と記述してあり、sshがどこで使われるのか言及していないことも嵌る原因だと思います。

感想というか思うところ


ドキュメントもかなり充実してるんで、BOXファイルを作成するまでは割りとすんなりいけるかなぁと思います。
一部、嵌まりどころもありますが・・・。

一方で、仕組み上仕方ない部分もありますが、kickstartやその他OS自動インストールの知識がないと使いづらい印象を受けました。
尤も、こういうツールに手を出す人は、知識持ちの人かもしれませんが・・・

「packer + Vagrant + Chef」を組み合わせれば、かなりプラットフォーム及び開発環境系の自動化ができるなぁと思いました。

しかし、これがGoで作られているのがすごいよなぁ・・・。
Goマジイケてると思います。