ラジオの製作


http://tokyodevices.jp/products/detail.php?product_id=106
我が家の子供(小学2年)に電子工作をさせたくて、いろいろ悩んだあげくラジオの製作から入門してみることになりました。鉱石ラジオなどでもよかったのですが、まったく音が鳴らないと興味も引いてしまうと思い、まずは、東京デバイセズさんから販売されているレフレックス方式ラジオを購入することに決定。はたしてうまく受信できるか心配です。
昨日は、電波がどういうものかを説明するのにだいぶ苦労しましたが、これの購入後には各部品の役割や、はんだ付けの説明に苦労しそうです。

ActiveRecordクラスに動的にバリデーションコードを追加する

RailsWay 250ページあたりに出てくる話題として、ActiveRecordでレコードを読み出す時に動的にバリデーションコードを定義するという話題があり、場合によっては非常に便利だが、RailsWayのコードそのままだとちょっとアレでうまくいかないので、正しいコードに修正してみた。

もともとは、こんな感じ:

class Account < ActiveRecord::Base 
... 
private 
  def after_find 
    singleton = class << self; self; end 
    singleton.class_eval(config) 
  end 
end 

これで基本は問題ないのだが、configの部分に投入するバリデーションコードは

validates_numericality_of :product_code, :only_integer => true 
validates_length_of :product_code, :is => 10 

ではなく、

ActiveRecord::Base.validates_numericality_of :product_code, :only_integer => true 
ActiveRecord::Base.validates_length_of :product_code, :is => 10 

のようにActiveRecord::Baseを指定してあげないとエラーが出る事もなくバリデーションが正しく実行されないという現象になる。

この例ではafter_findコールバックを使っているけど、before_validationなどで使うほうが一般的かもしれない。

ちなみになんらかの工夫をしないと多重にバリデーションを追加してしまう事態になるので要注意。

Railsでの遅延トランザクションと並行性

経験上、楽観的ロックの場合はトランザクションを始める前に対象レコードをすべて読み込んでしまい、必要な処理を施し(カラムデータの変更など)、その後で、ActiveRecord::Base.transactionでトランザクションを開始し、レコードをまとめて保存する(save!を使う)。すると、複数テーブルを同時に更新する際の原子性を確保しやすい。

あらかじめ、関連するレコードをロックしてからレコードの読み出しを行うような場合は(悲観的ロックを併用する場合は)トランザクションブロックで、sample_model = SampleModel.find(:first, :lock => true)のようにロックをかけてからレコードを読み込む。InnoDBの場合、レコード単位にロックがかかり便利。

いずれにせよ、Railsアプリケーションで複数ユーザが同時に共通のリソースにCRUDするなら、常にこういった並行性問題を考えなければならないのだが、作業量として大変な場合が多々あり、しかも並行状態を作るテストが難しい。

和書 RailsWay 発売開始

長期間の監修作業の末についにRails Way (Obie Fernandez 著, 豊田 祐司 監修)の出版にこぎつけました。先週末あたりから、書店でも並べられているようです。原書はRails 2.0がリリースされる直前に出版されており、2.0で強化されたREST関連のトピックが結構くわしく解説されています。また、ActiveRecordに関しては、アソシエーションの詳細など、かなり高度なトピックまで網羅しています。このあたりが中心で、ページ数もわりと多く取られているのですが、変わったところとしては後半でNginxによるサーバ構築や、Monitの解説が出てくるところなどがおもしろいかもしれません。

Railsの初心者というよりは中級者へのステップアップとして有益な内容が多いと思います。監修作業ではRails 2.0.2を使って検証作業を行っています。

Railsエンジニアとして実力をつけたいと思っている方は是非読んでみてはいかがでしょうか。



Ruby Way [第2版] 監修作業完了

最近数ヶ月間、Ruby Way 第2版の監修作業に没頭しており、昼夜問わず文章チェック、コードチェックに追われていました。
Ruby Way 第2版の原著は「The Ruby Way, Second Edition」で、著者はHal Fultonという人物です。

前作の第1版は2002年の11月に翔泳社より出版されました。今回はこれの改訂第2版となり、同じく翔泳社から出版されます。来週早々(4月14日の週)には書店に並ぶはずです。

原著はRuby 1.8系列を対象に記述されているのですが、翻訳時点で入手可能だったRuby 1.9.0-0ですべてのサンプルコードの動作を確認しています。必要に応じて1.9.0を使う場合の注記やサンプルも記載しました。Railsのことを考えると現時点では1.8.6を使用する読者が多いと思いますが、本書は1.8.6を使う読者でも、最新の1.9.0を使いたいという読者でもどちらでも大丈夫です。ただし、1.9.0は開発バージョンですので、最新仕様の確認をお忘れなく。

目次は次のようになっています。

第1章 Rubyの概要
第2章 文字列の操作
第3章 正規表現
第4章 Rubyの国際化
第5章 数値計算
第6章 シンボルと範囲
第7章 時刻と日付の操作
第8章 配列、ハッシュ、そのほかの列挙可能オブジェクト
第9章 高度なデータ構造
第10章 I/Oとデータストレージ
第11章 RubyにおけるOOPと動的機能
第12章 Rubyのスレッド
第13章 スクリプティングとシステム管理
第14章 Rubyとデータフォーマット
第15章 テストとデバッグ
第16章 コードのパッケージ化と配布
第17章 ネットワークプログラミング
第18章 分散Ruby
第19章 Rubyの開発ツール

実際の開発業務をカバーできるほど広範囲なトピックを扱っているにも関わらず、結構深い内容も記載されているのが本書の特徴です。興味のある方はぜひ読んでみてください。

Rails 2.0.1への移行

以前作成したRails1.2.6用のコードをRails 2.0.1に移行した時の作業記録。1.2.6に移行してから実施したので、この作業はそれほど時間はかかなかった。

1. environment.rbでRAILS_GEM_VERSIONを2.0.Xに変更

RAILS_GEM_VERSION = '2.0.1' unless defined? RAILS_GEM_VERSION
この修正は次のrakeを実行する前に行う必要がある。(rakeがバージョンを参照するので)

2. 現在のディレクトリのRailsファイル群を更新

rake rails:update

これにより、config/boot.rb、public/javascriptsディレクトリのJavascriptファイル、scriptディレクトリのスクリプトファイルが更新される。

3. セッションキーとシークレットの設定

以下のようにセッションキーとシークレットをconfig/environment.rbに追加する。どのように生成されるかは空のrailsプロジェクトを生成するとよくわかる。

Rails::Initializer.run do |config|
      ...
  config.action_controller.session = {
    :session_key => '_yourapp_session',
    :secret      => '0192838746074358285ab981273b1123 ..... '
  }
       ...
end

4. application.rbのセッションキー

1.2.XまでのRailsではapplication.rbでセッションキーの設定を行っていたが、上記のようなRails.Initializer.runで設定するように変わったのでコメントアウトする。

#  session :session_key => '_cathay_session_id'

代わりに、新しいRailsではCSRFのためにapplication.rbのデフォルトテンプレートが生成するコードをapplication.rbに反映させる。(ここでは入れておくだけ。必要に応じて設定する)

# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.

class ApplicationController < ActionController::Base
  helper :all # include all helpers, all the time

  # See ActionController::RequestForgeryProtection for details
  # Uncomment the :secret if you're not using the cookie session store
  protect_from_forgery # :secret => '852af24c2b356e52abc35dc3721d4ced'
end


この段階でそのまま./script/serverなどで動作を確認できるはず。

acts_as_authenticatedの使い方 - その3

前回はautheticated_mailerによって生成されるActionMailerの設定が完成したが、authenticated_mailerは、ユーザ登録時に、ユーザが登録したemailアドレスにメールを送信し、そのメールを参照したユーザが記載されているURLにアクセスして初めてユーザ登録を完了するプロセスになる。これを実現するためには、前々回のauthenticatedジェネレータが生成したusersテーブルにactivation_codeとactivated_atという2つのカラムを追加する必要がある。

class AddActivationToUserTable < ActiveRecord::Migration
  def self.up
    add_column :users, :activation_code, :string, :limit => 40
    add_column :users, :activated_at, :datetime
  end

  def self.down
    remove_column :users, :activation_code
    remove_column :users, :activated_at
  end
end

上記のコードをdb/migrate/002_add_activation_to_user_table.rbという名前で保存し、rake db:migrateを実行する。
app/models/user_notifier.rbでは、前回ActionMailのテストのためにsignup_notificationメソッドの一部をzzzzzという仮コードに変更してあった。これは上のようにactivation_codeというカラムがテーブルに追加されたので元の状態に戻す。

#    @body[:url]  = "http://localhost:3000/account/activate/zzzzzzzzzz"
    @body[:url]  = "http://localhost:3000/account/activate/#{user.activation_code}"

ユーザアクティベーション用のコードをmodels/user.rbに追加する。
以下のコードをUserクラス定義の最初の部分、たとえばbefore_save :encrypted_passwdの次に追加する。

  # User Activation
  before_create :make_activation_code

続けて、以下のコードを既存のself.authenticationメソッドの定義と入れ換える。

  # Authenticates a user by their login name and unencrypted password.  Returns the user or nil.
  def self.authenticate(login, password)
    # User Activation - hide records with a nil activated_at
    u = find :first, :conditions => ['login = ? and activated_at IS NOT NULL', login]
    u && u.authenticated?(password) ? u : nil
  end

新規で追加となるメソッドは以下の通り。

  # User Activation - Activates the user in the database.
  def activate
    @activated = true
    update_attributes(:activated_at => Time.now.utc, :activation_code => nil)
  end

  # User Activation - Returns true if the user has just been activated.
  def recently_activated?
    @activated
  end

最後部のprotectedメソッドにも1つメソッドを新規で追加する。

    # User Activation
    def make_activation_code
      self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
    end

次にapp/controllers/account_controller.rbを修正する。
まず、既存のsignupメソッドを以下のコードと置き換える。

  # User Activation - need replace signup
  def signup
    @user = User.new(params[:user])
    return unless request.post?
    @user.save!
    redirect_to(:action => 'signup_notification')
    flash[:notice] = "Thanks for signing up! Please check you mail box."
  rescue ActiveRecord::RecordInvalid
    render :action => 'signup'
  end

そして以下のactivateメソッドを追加する。

  # User Activation
  def activate
    @user = User.find_by_activation_code(params[:id])
    if @user and @user.activate
      self.current_user = @user
      flash[:notice] = "Your account has been activated."
    else
      flash[:notice] = "Invalid activation code."
    end
  end

app/views/accountでは、以下の内容で2つのビューテンプレートを作成しておく。1つが、activate.rhtmlで、もう1つがsignup_notification.rhtmlとする。flashのメッセージを表示する簡単なもので、2つのファイルとも内容は同じでよい。

<%= flash[:notice] %>

これでacts_as_authenticatedおよびメールによるユーザ認証コードの設定は一通り完了となる。localhost:3000/accountにアクセスし、ユーザを登録すると、指定したメールアドレスに確認用メールが配信されること、そこに記載されたURLにアクセスするとユーザ登録が完了することを確認してほしい。