Arrayに対してfields_forを適用する


Railsを使い始めて3日目、詰まった・・・

問題

eventとparticipantのように1対nの関係を持ったモデルを作成して、
それらを同時に一つのフォームで入力したい場合にfields_forを使うと思うのだが、
participantがparticipantsのような場合にfields_forを使って配列をPostする方法がわからなかった。

とりあえずぐぐってみたが、いい結果を見つけることができなかった・・・
まぁ検索方法が悪いのかもしれないけど
探すなら一番確かなのは本家のマニュアルを見ることだと心得ているので、以下のAPIマニュアルを見ることにした。
http://api.rubyonrails.org/
うーんわからん。でも手がかりはあった。

解決方法1

<% for i in 0..@participants.size -1 do %>
<%= fields_for @participants[i] do |p| %>
<div class="field">
<p>participant's name</p>
<%= p.text_field :name %>
</div>
<% end %>
<% end %>

泥臭い感じがするが、とりあえず以下のような出力を得ることができる

<div class="field">
<p>participant's name</p>
<input id="participants_0_name" name="participants[0][name]" size="30" type="text" />
</div>

<div class="field">
<p>participant's name</p>
<input id="participants_1_name" name="participants[1][name]" size="30" type="text" />
</div>
...

配列でparticipantが出力されている

解決方法2

解決方法1よりも少しだけ綺麗にする

  <% for i in 0..@participants.size-1 do %>
  <%= fields_for :participants, @participants[i], { :index => i } do |p| %>
    <div class="field">
      <p>participant's name</p>
      <%= p.text_field :name %>
    </div>
  <% end %>
<% end %>

終わり

もっといい方法ないかな

Xeon E3-1275V2マシン

現在のCore2マシンがだんだんと使いにくくなってきたので、思い切ってPC自作することにした
そしてパーツ選びに迷い、いろいろ調べてメモった

  • 予算:7万
  • 用途:「OpenStackで遊ぶ」+「通常のPC」
  • 備考:なるべく安く。ドライブは現在のマシンのものを流用する

CPU

CPUはどうせならいいのを積んでおきたい。そこで選択候補に上がったのが、以下の2つである。(値段は2012年6月17日の価格COMの最安値)

これらはほぼ同じ機能を持っていて、値段も似通っている。

Intelの公式ページで比較を行った。http://ark.intel.com/ja/compare/65523,65729,65726
大きな違いはXeonIntel VT-dに対応していることだ。
インテルが提供する仮想化支援には(1)Intel VT-xと(2)Intel VT-dの2つの機能があり、
VT-xはCPUを仮想化し、VT-dはI/Oを仮想化する。

両方搭載されているXeonを選択する。
(実はcore i7 3770(kがつかない製品)もIntel VT-dが付いていることに購入後に気づいた・・・)
Xeon E3-1275V2よりも値段が安いXeon E3-1245V2(¥23,800)に決めた。

マザーボード(MB)

チップセットはZ77 or H77
Z77が上位機種であり、Z77を搭載すればオーバクロックとグラフィックボード二枚刺しが可能になるらしい。
http://www.btopcshop.com/info/parts/z77h77z68.html

とりあえずH77マザーボードを探すことにした。
安いMBを探して、公式ページでXeon E3-1245V2に対応しているか確認した。
探していて安かったGIGABYTEのGA-H77-D3Hに決めた。

メモリ

合計16GBあれば足りるだろうと踏んだ。
価格COMで探して売れ筋だったCFD W3U1600HQ-4Gに決めた。(2枚セットが¥3273でこれを2セット買うと¥6546)

HDD(SSD

Intel 330 128GBが一万円ぐらいで安いと感じたので即決

筐体と電源

筐体はANTEC ONEHUNDRED
電源は剛力短2プラグイン SPGT2-500P

まとめ

以上を踏まえて以下のパーツを買った。購入先はAmazonツクモ

  • CPU Xeon E3-1245V2 ¥23,800
  • Memory CFD W3U1600HQ-4G(2枚組) x 2 ¥ ¥6,940(3,470x2)
  • MB GIGABYTE intel H77 LGA1155 ATX GA-H77-D3H ¥ 8,890
  • SSD Intel 330 Series 120GB ¥ 9,960
  • 筐体 ANTEC ONEHUNDRED ¥ 4,836
  • 電源 剛力短2プラグイン SPGT2-500P \5,880

合計金額:¥60,306(送料込み)
ちなみにE3-1225V2を搭載したBTOマシンが6万円で売られている。
http://bto-pc.jp/btopc-com/select/xeon-cg-cad-bto-60k.html
自分の構成に比べると

  • 上記のマシンはOSとDVDドライブを搭載している
  • 自分のマシンはCPUが上位、SSD搭載、筐体が良い

自分の用途にあったマシンを作れたし、パーツもそこそこのものを組み込めたので満足である。
パーツが届くまでしばしの辛抱である

続buildrootによるinitramfsのビルド(2)

gccがデフォルトではインストールされていなかったので、gccを追加するオプションを探したところ、
なぜか見つからない

ググったところ、
"development files in target filesystem"のような項目があるので'y'としておくとgccを追加するオプションが現れるようだ

これでgccが使えるようになった
だけど、コンパイルして実行するとSegmentation faultが発生する
これはgccに--staticオプションを追加すると回避できるけど、動的リンクで実行できるようにしたい

現在放置中

続buildrootによるinitramfsのビルド

udevを使うとカーネルパニックが発生する問題を解決

udevを使うとカーネルパニックが発生する現象に遭遇したので
使用するbuildrootのバージョンを上げた.ステーブルなバージョン(2011.11,2011.08)だとうまく行かないので,スナップショットを持ってきてビルドしたところ起動するようになった.

/initの中で発生するエラー

Arch linuxから持ってきた/initがエラーを吐く
まず/runが無いと怒られる.これはディレクトリがないだけなので作る
次に/configがないと言われる.よくわからんのでconfigを使う箇所をコメントアウト
(その後,Arch linuxのinitramfsを解凍して入手したconfigを入れた)
最大の問題が/dev/disk/by-uuidが作成されないこと

UUIDをサポート

make busybox-menuconfigでいろいろ設定を変更
まずuuid関連を全て'y'にする
でもだめ

findfsを追加.
でもだめ

適当にblockdev,acpid,support for old /etc/mtabを追加
でもだめ

blkidを実行してみると何も結果を返さない!これが諸悪の根源だろう

Print filesystem type ,Filesystem/Volume identificationにextとsysvを追加
blkidが結果を返すようになったが,root=/dev/disk/by-uuid/... は使えない.

Filesystem/Volume identificationにlinux swap filesystemを追加
procも追加

依然として/dev/disk/by-uuidは作成されないが,
findfs UUID="disk uuid"とすれば/dev/sda*と表示されるのでfindfsが問題なく動いている

GRUBの記述方法にはroot=/dev/disk/by-uuid/の他にroot=UUID=という指定方法があるので,
そっちも試してみたがだめ.うーん


Archから持ってきた/initに問題があるかも
/init_functionsというスクリプトファイルの中にあるresolve_devices()を見るとUUID=による記述が使えるきがする.
でも現実はそうじゃない.どういうことだ?

resolve_device() {
    local major minor dev device=$1

    case $device in
        # resolve tag name to block device
        UUID=*|LABEL=*)
            dev=$(blkid -lt "$device" -o device)
            [ -n "$device" ] && device=$dev
            ;;
    esac

    case $device in
        /dev/*)
            if poll_device "$device" "$rootdelay"; then
                echo "$device"
                return 0
            fi

            # block device, e.g. (/dev/sda1) -> /sys/class/block/sda1/dev
            if [ -e /sys/class/block/${device:5}/dev ]; then
                IFS=':' read major minor < "/sys/class/block/${device:5}/dev"
            fi
            ;;
        [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]|[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])
            # hex encoded major/minor, such as from LILO
            major=$(( 0x0$device >> 8 ))
            minor=$(( 0x0$device & 0xff ))
            ;;
        0x[0-9a-fA-F][0-9a-fA-F]*)
            major=$(( $device >> 8 ))
            minor=$(( $device & 0xff ))
            ;;
    esac

    if [ -n "$major" -a -n "$minor" ]; then
        device=$(major_minor_to_device "$major" "$minor" || echo '/dev/root')

        if [ ! -b "$device" ]; then
            msg "Creating device node with major $major and minor $minor." >&2
            mknod "$device" b "$major" "$minor"
        fi
        echo "$device"
        return 0
    fi

    return 1
}

blkidを-ltと-oをつけて使用すると結果がおかしい気がする
まず-o deviceによって本来の結果は

/dev/sda1
/dev/sda2
/dev/sda3
/dev/sda4

と表示されるはずなのにオプションを付けなかった時と同じ表示になってしまう.
"-lt"で値が一致するものだけを表示するはずのところが,付けなかった時と同じ表示になる.
つまりblkidのオプションが全く動作していない.なんてことだ.

オプション群はおそらくlibblkで提供されていると思うので,調べてみるが,驚愕の事実が・・・

$ grep libblkid . -R -I
./output/build/busybox-1.19.3/e2fsprogs/old_e2fsprogs/blkid/tag.c: * Tag iteration routines for the public libblkid interface.
./output/build/busybox-1.19.3/e2fsprogs/old_e2fsprogs/blkid/dev.c: * dev iteration routines for the public libblkid interface.
./output/build/busybox-1.19.3/e2fsprogs/old_e2fsprogs/blkid/blkid.h: * blkid.h - Interface for libblkid, a library to identify block devices
./output/build/busybox-1.19.3/e2fsprogs/old_e2fsprogs/blkid/blkidP.h: * blkidP.h - Internal interfaces for libblkid
./output/build/busybox-1.18.5/e2fsprogs/old_e2fsprogs/blkid/tag.c: * Tag iteration routines for the public libblkid interface.
./output/build/busybox-1.18.5/e2fsprogs/old_e2fsprogs/blkid/dev.c: * dev iteration routines for the public libblkid interface.
./output/build/busybox-1.18.5/e2fsprogs/old_e2fsprogs/blkid/blkid.h: * blkid.h - Interface for libblkid, a library to identify block devices
./output/build/busybox-1.18.5/e2fsprogs/old_e2fsprogs/blkid/blkidP.h: * blkidP.h - Internal interfaces for libblkid
./output/build/udev-173/NEWS:repository. Libvolume_id is merged with libblkid from the util-linux-ng
grep: ./output/target/etc/resolv.conf: No such file or directory
grep: ./output/target/etc/mtab: No such file or directory
grep: ./output/target/dev/log: No such file or directory
./package/util-linux/util-linux.mk:     $(if $(BR2_PACKAGE_UTIL_LINUX_LIBBLKID),,--disable-libblkid) \
./package/util-linux/Config.in: bool "build libblkid and blkid utilities"
grep: ./fs/skeleton/etc/resolv.conf: No such file or directory
grep: ./fs/skeleton/etc/mtab: No such file or directory
grep: ./fs/skeleton/dev/log: No such file or directory

なんと--disable-libblkidとなっている.なんでだ?

findfsコマンドをblkidの代りにしてやるか

resolve_device()を書き換え

- dev=$(blkid -lt "$device" -o device) 
+ dev=$(findfs $device)

GRUBのroot=をroot=UUID=ディスクのUUIDと設定したら起動できるようになった.

buildrootインストール

buildrootのインストール

buildrootは組み込み用途のLinuxを簡単にビルドするためのツール
今回はこのツールを使って高機能なinitramfsを作る

説明

initramfsはディスクをルートにマウントする前の一時的なファイルシステムだと思うけど,
initramfsに必要なものをいろいろ突っ込んでしまえば,そこそこ使えるはず
別の使い方としてはkdump用に作っておけば,デバッグの時にいろいろできるだろう

インストール

以下からダウンロードしてファイルを解凍して適当なディレクトリに展開しましょう
[](http://buildroot.uclibc.org/)
自分は/usr/src以下に展開しました

コンパイルLinuxカーネルと同じ方法で行う

$ cd /usr/src/buildroot/
$ make menuconfig
$ make 

使った計算機はx86_64なので,menuconfigではまずCPUのアーキテクチャx86_64を選択した.
次にBusyBoxの設定を変更することでいろんなツール(NFS,sshdなど)をinitramfsにインストールできる.
入れるツールはお好みで.
あとはinitramfs(initrd)の圧縮形式の選択でcpio.gzにした.これも好みの問題?
Linuxカーネルをビルドする必要はない.(initramfsをカーネルに組み込む場合はこの限りではない)

makeが完了するとoutput/images/にinitramfsがrootfs.cpioとrootfs.cpio.gzという名前で作成されている

/bootにコピーして,GRUBのメニューを書き換える.

$ cp output/images/rootfs.cpio /boot
$ sudo emacs /boot/grub/menu.lst
#(0)                                                                                                    
title   buildroot
root    (hd0,0)
kernel  /vmlinuz-2.6.38.7-MK root=/dev/ram0 ro iommu=soft
initrd  /rootfs.cpio.gz           

以上でとりあえず起動する.

この状態で以下の点が不満だった

  • ログインシェルが表示されないこと
  • 通信ができないこと

ログインシェルが出ないのでキーボードからユーザ名とパスワードを打ってログインすることできない.
そして通信ができないのでsshで接続することもできない.要するに使えない(シリアルケーブル経由ならいけるかも)

ログインシェルが表示されない問題は以下のページでよくある質問として扱われていた
()http://buildroot.uclibc.org/downloads/buildroot.html#custom_targetfs
make menuconfigにおいてSystem configuration->Port to run a getty (login prompt)を変更すればいいらしい
初期値はttyS0となっていたところをtty1に変更し,起動したところログイン可能になった

次に通信だが,これは通信の設定ファイルを書き換える必要がある.
initramfsに入れられるファイルはbuildroot/output/target以下にあるので,
それを編集してmakeを実行すればrootfs.cpio.*のファイルが書き換わる.
書き換えるファイルはbuildroot/output/target/etc/network/interfacesである.
以下のように設定すればethernetIPアドレスが振られて通信できるようになる

auto lo eth0
iface lo inet loopback
iface eth0 inet static
      address 123.45.67.890
      network 123.45.67.0
      netmask 255.255.255.0
      broadcast 123.45.67.255
      gateway 123.45.67.1
      dns-nameservers 123.45.2.1

今日はここまで

initrd/initramfs

initramfsの中身を調べる
方法としては initramfsを解凍する方法もあるが,改変することを考えるとソースとなるディレクトリを知る必要がある
使用したディストリビューションはArch Linux

ソースディレクトリの特定

カーネルビルドにおいてmake installをすると/boot以下にカーネル本体の他にinitramfsが生成される.
このinitramfsを生成するのはArch linuxではmkinitcpioコマンド(シェルスクリプト)である.
mkinitcpioの中身を確認する.

$ less `which mkinitcpio`

中身を見ると,initramfsの中身の所在は/lib/initcpio/にありそうだとわかる

$ ls /lib/initcpio

busybox* functions hooks/ init* init_functions install/ udev/

busyboxが入っているところを見るとビンゴっぽい

/initの中身を読んでみる

/lib/initcpio/initがinitramfsをマウントした時の/initになるわけで,

このスクリプトが仮のルートファイルシステム(initramfs)からリアルルートファイルシステムへ移行するための処理をするはずである.

読んでみる

$ emacs /lib/initcpio/init 
(抜粋)
mkdir -p /new_root
...
${mount_handler:-default_mount_handler} /new_root
...
for d in proc sys dev run; do
    if [ -d /new_root/${d} ]; then
        mount --move /${d} /new_root/${d}
    else
        umount /${d}
    fi
done
exec env -i TERM=$TERM /sbin/switch_root -c /dev/console /new_root ${init} "$@"

initの中では後にリアルルートになるファイルシステムを/new_rootとして作成する.
/new_rootがのちのリアルルートディレクトリになるもので,default_mount_handler(init_functionsに記述)によりnew_rootにディスク(ルートディレクトリとなるディスクパーティション)をマウントする.(まだ詳しくは調べていない)
最後にmount --moveによって/proc, /sys, /dev, /runを/new_root/以下に移動する.
以上で準備が完了するのでswitch_rootコマンドによってルートディレクトリを/new_rootに変更し,/sbin/initを実行する.

他にもいくつか処理があるので,調べるが,今回はここまで終わり

2>&1の意味

2>&1の意味を今まで知らなかった

例えば,あるコマンド(command)の標準出力と標準エラー出力をlessで表示するには以下のようにする.

$ command 2>&1 | less

まあおまじないみたいなものだと思ってきた

でもlessじゃなくてファイルに出力したい時に問題に直面した
例えば次のように書いたとする

$ command 2>&1 1>file(1)
$ command 1>file 2>&1(2)

なぜか(1)はうまく行かず(2)はうまくいく
僕と同じく(1)(2)の両方共うまく行くと思っていた人は2>&1の意味を知る必要がある.

答えはhttp://x68000.q-e-d.net/~68user/unix/に見つかる.