2012/12/29

[rails]レガシーなDBにつなぐ際のRakeタスク

Railsアプリを開発する際に、レガシーなDBを扱う場合を考えます。
この場合のアプリケーションはDBのスキーマ情報を定義しないため、RSpecを導入してrake specを実行した場合、 schema.rbが空であるため、db系のrakeタスクが動かずに、エラーとなります。
これを回避するために、db系のrakeタスクをアプリケーションで上書きして、 何もしないタスクに置き換えることができます。

アプリケーション定義Rakeタスクファイルとして、

{Rails.root}/lib/tasks/db.rake
を作成し、次のように実装します。

module Rake::TaskManager
  def remove_task(task_name)
    @tasks.delete(task_name.to_s)
  end
end

Rake.application.remove_task "db:create"
Rake.application.remove_task "db:drop"
Rake.application.remove_task "db:fixtures:load"
Rake.application.remove_task "db:migrate"
Rake.application.remove_task "db:migrate:status"
Rake.application.remove_task "db:rollback"
Rake.application.remove_task "db:schema:dump"
Rake.application.remove_task "db:schema:load"
Rake.application.remove_task "db:seed"
Rake.application.remove_task "db:setup"
Rake.application.remove_task "db:structure:dump"
Rake.application.remove_task "db:test:prepare"

namespace :db do
  desc "do nothing."
  task :create => :environment do
    puts "This task does nothing."
  end

  : 以下、全てのタスクを空に。
end

rake -Tを実行すると、db系タスクが上書きできていることが確認できます。 これで、レガシーDBを扱うアプリケーションでも、DBを変更する危険もなく、 RSpecもスムーズに実行することができます。

2012/11/05

mongodbの設定 - mongodb.conf

mongodbの設定は、mongodb.confで行います。
aptを使用して、

  $ sudo apt-get install mongodb-10gen
でmongodbをインストールすると、
/etc/mongodb.conf
が作成され、また、インストール時に作成される起動スクリプト
/etc/init.d/mongodb
は、このmongodb.confを参照しています。
ここでは、mongodb.confでの設定項目を纏めます。


ここからは、 Configuration File Options の内容を訳しつつ、まとめてみます。

Settings

verbose

Default: false
標準出力やlogpathのログファイルに出力する内部レポートの出力量を調節する。

v

Default: false
verboseの代替形式。

vv
Default: false
出力やログの情報をさらに増やす。
vvv
Default: false
出力やログの情報をさらに増やす。
vvvv
Default: false
出力やログの情報をさらに増やす。
vvvvv
Default: false
出力やログの情報をさらに増やす。
quiet

Default: false
出力を制限したquiteモードで、mongodまたはmongosインスタンスを実行する。
このオプションは、

  • drop、dropIndex,diagLogging,validate、cleanのデータベースコマンドの出力
  • レプリケーションの活動
  • コネクションの受け入れ完了イベント
  • コネクション切断イベント
を抑止する。

port

Default: 27017
mongodとmongosインスタンスが待ち受けるTCPポート番号を指定する。 Unix-likeなシステムでは、1000より小さいポートへのアクセスは、root権限が必要。

bind_ip

Default: 全インタフェース
アプリケーションが接続するmongodやmongosインスタンスがバインドするアドレスを設定する。
どのインタフェースを指定しても構わないが、パブリックなアクセスが可能なインタフェースを指定する場合は、 然るべき認証を実装するか、ファイアウォールによる制限でデータベースとの連携を保護すべき。

mongodにバインドするIPアドレスは、カンマ区切りで複数指定することもできる。

maxConns

Default: システムの制限(ulimitやFD)に依存。このオプションを設定しない限りMongoDBは制限しない。
mongod、mongosが受け付ける同時接続数を指定する。この設定値がOSで設定している最大接続数を超える場合は、無効になる。

このオプションは、コネクション切断ではなくタイムアウトするような複数のコレクションを生成するようなクライアントを相手とする場合に特に有効。maxConnsを設定した場合、誤接続がシャードクラスターの各要素に伝播しないよう、コネクションプールのサイズ、または総コネクション数よりも若干大きくなるようにする。

objcheck

Default: false
不正なBSONデータセットがデータベースに追加されないよう全てのクライアントからのリクエストを検証する場合にtrueを設定。mongodはリクエストのオーバーヘッドの観点から、この機能をデフォルト無効としている。

logpath

Default: None(i.e /dev/stdout)
全てのログ情報を記録するファイル名へのパスを指定する。

未指定の場合、mongodはログを標準出力に出力する。logappendがtrueでない場合、プロセスが再起動されるたびに、ログファイルを上書きする。

logappend
syslog
pidfilepath
keyFile
nounixsocket
unixSocketPrefix
fork
auth
cpu
dbpath
diaglog
directoryperdb
journal
journalCommitInterval
ipv6
jsonp
noauth
nohttpinterface
nojournal
noprealloc
noscripting
notablescan
nssize
profile
quota
quotaFiles
rest
repair
repairpath
slowms
smallfiles
syncdelay
sysinfo
upgrade
traceExceptions

Replication Options

replSet
oplogSize
fastsync
replIndexPrefetch

Master/Slave Replication

master
slave
source
only
slavedelay
autoresync

Sharding Cluster Options

configsvr
shardsvr
noMovePranoia
configdb
test
chunkSize
localThreshold

2012/10/18

[rails]kaminariで「もっと見る」を作る。

Rails3でのページングを実装する上で、非常に便利なgemにkaminariがあります。
コレを使うと、最近のWebサイトではお馴染みの「もっと見る」なども簡単に実装できることに気づきました。

ビューで、

<%= link_to_next_page(@some_objects,
                      'more',
                      :remote => true) %>

とすることで、@some_objectsの次のページへのリンクが出力されます。
通常、「もっと見る」はAjaxとあわせて使われることが多いと思います。
ので、:remote => true として、Ajaxなリクエストを投げるリンクとします。

2012/09/20

[socket.io] Authorizing和訳

Authorizing · LearnBoost/socket.io Wikiを和訳してみます。
素人の訳ですので、間違いもありますし、訳せていない文もあります。
間違いにお気づきの方は、よろしければコメント欄からご指摘下さい。

Authorizing

Socket.IOは2つの異なる認証方式をサポートします。 初期化/ハンドシェイクの過程でグローバルな認証を行い適用するか、 または個々のネームスペースごとに認証を行うことができます。
グローバルな認証とネームスペースごとの認証は、一緒に利用することもできますし、 各々独立して利用することもできます。 双方の間で唯一共通しているのは、 handeShakeData オブジェクトです。 このデータオブジェクトは、ハンドシェイクによるリクエストデータで生成されます。 したがって、認証処理がどのように動作するかをうまく理解するために、 まずハンドシェイクのプロセスを理解する必要があります。

Handshaking

クライアントがSocket.IOサーバーと永続的なリアルタイム通信を確立しようとする場合、 ハンドシェイク処理を開始しなくてはなりません。 ハンドシェイクは、XHRリクエストかJSONPリクエスト(クロスドメインリクエストの場合)のいずれかを使って開始されます。
サーバーが接続を受信したとき、そのリクエストから、後に必要となるデータの収集を開始します。 そうするには、2つの理由があります。
  1. ユーザーは、リクエストヘッダかIPアドレスのいずれかで、クライアントの認証を行う場合があります。
  2. サーバーとの接続を確立しようとする場合、ヘッダを送信しない通信もあるので、内部的にハンドシェイクデータを格納し、それによりクライアントが接続した際にその情報を再利用できるようになります。
例えば、クッキーヘッダからセッションIDを読み取り、接続されたソケットに対してExpressのセッションを初期化したい、という場合があるかもしれません。
handshakeData オブジェクトは、次の情報を持っています:
{
  headers: req.headers
 ,time:    (new Date) + ''
 ,address: socket.address()
 ,xdomain: !!headers.origin
 ,secure:  socket.secure
 ,issued:  +date
 ,url:     request.url
 ,query:   data.query
}
address は、socket.address() APIに従います。
全ての情報を読み込んだ後、グローバルな認証機能が設定されているかチェックします。 設定されている場合は、handShakeDataオブジェクトとコールバック関数を認証関数に渡します。 コールバック関数が実行されるとき、handShakeDataは内部に格納しているので、後のconnectionイベントの間にsocket.handshakeプロパティを通じてアクセスすることができます。コールバックの応答に基づいて、403か500、または成功時は200を応答します。

Global authorizing

グローバル認証は、コンフィグレーションメソッドでauthorizationを設定することで有効に成ります。
io.configure(function (){
  io.set('authorization', function (handShakeData, callback) {
    callback(null, true); // error first callback style
  });
});
上記の認証関数中、2つの引数をとります:
  1. handShakeData, ハンドシェイクの過程で生成したhandShakeDataオブジェクト。
  2. callback, ハンドシェイク処理がデータベースのルックアップを必要とする場合など。これは、undefinedか、エラー時には文字列が入るerrorとクライアントが認証されたか否かを示すbool値authorizedの2つの引数をとります。

エラーを送出するか、authorized引数にfalseを設定することで、ともにクライアントに対して、サーバーへの接続を許可しないようにします。
handshakeDataは認証処理の後に保存されているので、このオブジェクトに対して、データを追加したり削除することができます。
var io = require('socket.io').listen(80);

io.configure(function (){
  io.set('authorization', function (handshakeData, callback) {
    // findDatabyip is an async example function
    findDatabyIP(handshakeData.address.address, function(err, data) {
      if (err) return callback(err);

      if (data.authorized) {
        handshakeData.foo = 'bar';
        for(var prop in data) handshakeData[prop] = data[prop];
        callback(null, true);
      } else {
        callback(null, false);
      }
    });
  });
});

ちなみに、グローバル認証とネームスペース認証とで、handshakeDataは共有し合うので、handshakeDataオブジェクトからヘッダ情報や、重要なデータを削除してしまうと、ネームスペース認証の内部で、そのデータにアクセスすることができなくなります。一方で、新たに追加したデータは、ネームスペース認証においても参照できるようになります。
handshakeDataへのアクセスは、ネームスペースだけでなく、接続したソケットからもアクセスできます。 つまり、ハンドシェイクの過程で保存したデータは、ネームスペースのconnectionイベント内で、↓の例のようにscoket.handshakeプロパティを通じてアクセスできます:
io.sockets.on('connection', function (socket) {
  console.log(socket.handshake.foo == true); // write 'true'
  console.log(socket.handshake.address.address); // write 127.0.0.1
});

How does the client handle the global authorization

ソケットのerrorイベントを補足することで、クライアントサイドで認証が失敗したことを検知できます。 ネームスペース認証でも同様ですが、but there more error messages emitted there that could be related to errors in your namespace and not to the connection of socket.
接続に成功したソケットについては、connectイベントを補足することができます。
var sio = io.connect();

sio.socket.on('error', function (reason){
  console.error('Unable to connect Socket.IO', reason);
});

sio.on('connect', function (){
  console.info('successfully established a working connection \o/');
});

Namespace authorization

認証は、ネームスペース単位でも行うことができます。これは、例えば、パブリックな用途のチャットボックスや、登録メンバーに対するチャットボックスの拡張サポート用などのネームスペースにおいて、よりフレキシブルになります。
あらゆるネームスペースは、authorizationメソッドが利用できます。このチェーン可能なメソッドは、ネームスペースにおける認証方法を登録するために使用できます。この関数の引数は、グローバル認証機能と同じです。
var io = require('socket.io').listen(80);

io.of('/private').authorization(function (handshakeData, callback) {
  console.dir(handshakeData);
  handshakeData.foo = 'baz';
  callback(null, true);
}).on('connection', function (socket) {
  console.dir(socket.handshake.foo);
});

How the client handle the namespace authorization

認証失敗のハンドリングの仕方が、グローバル認証の場合と若干異なります。 errorイベントを送出するかわりに、connect_failedイベントを送出します。 ですが、認証をパスした場合は、期待通りにconnectイベントを送出します。

Example

var sio = io.connect()
  , socket = sio.socket;

socket.of('/example')
  .on('connect_failed', function (reason) {
    console.error('unable to connect to namespace', reason);
  })
  .on('connect', function () {
    console.info('sucessfully established a connection with the namespace');
  });

2012/09/09

[rails] before_filterに引数をとるメソッドを指定する。

Railsで、before_filterに引数を取るメソッドを指定したい場合があります。
例えば、複数のコントローラで、あるクエリパラメータを使用したフィルタを適用したい、 という場合で、クエリパラメータの名前が違う、といったケースが該当すると思います。

具体的には、次のような場合です。

 GET /hoge?uid=:uid
 GET /users/:id
uidをユーザーIDとした場合、上記のURIはともにユーザーIDをパラメータとして期待しています。 前者はparams[:uid]に、後者はparams[:id]にユーザーIDが入る、ということです。

ユーザーIDを使って、何らかのフィルタをかけるとなった時、フィルタメソッドは、

  def some_filter(user_id)
    # do something..
  end
みたいに書けます。
このメソッドをbefore_filterに指定するにはどうするか、というのがこのエントリの趣旨ですが、 簡単なことでした。
↓を参考にします。
http://stackoverflow.com/questions/5507026/before-filter-with-parameters

先ほどの、GET /hoge?uid=:uid のコントローラに対しては、

class HogeController < ApplicationController

  before_filter { |c| some_filter(params[:uid]) }

  :

  # GET /hoge?uid=:uid
  def index
  end

  :
end
と書くことができ、一方 GET /users/:id のコントローラに対しては、
class UsersController < ApplicationController

  before_filter { |c| some_filter(params[:id]) }

  :

  # GET /users/:id
  def show
  end

  :
end
と書けます。

2012/08/18

[node] node.js+matador+stylusの環境構築

nodeを使ったwebアプリの開発を実験中ですが、当初expressを使って作り始めましたが、 MVCアーキテクチャに則って作るべきと思い、妥当なフレームワークを探してました。
そこで見付けたのが、matadorというフレームワークです。
MVCアーキテクチャで実装でき、mongodbと相性が良さそうです。

matadorのインストールは、npmを使って簡単に出来ます。

 $ nvm use v0.8.4
 $ npm install matador -g      # matador コマンドのインストール
 $ matador init your_app_name  # アプリの生成
 $ cd your_app_name
 $ npm install matador         # matador moduleのインストール
 $ node all/server.js          # 起動

この段階で、node+matadorのアプリ開発環境ができた状態です。
ここにstylusを投入したくて、stylusインストール後、server.jsを修正して、以下のようにしました。

stylusインストール
 $ cd your_app_name
 $ npm install stylus
server.js

   :
app.configure(function () {

  app.set('view engine', 'html');
  app.register('.html', matador.engine);

  // Use the cache helper's no-cache middleware.
  app.use(app.getHelper('Cache').auditHeadersMiddleware);
  app.use(app.getHelper('Cache').noCacheMiddleware);

  app.use(matador.cookieParser());
  app.use(matador.session({secret: 'boosh'}));

  // TODO: Add JSON body parser middleware
  app.use(app.requestDecorator());
  app.use(app.preRouter());

  app.use(express.bodyParser());
  // stylusを使うために、↓を追加。
  app.use(require('stylus').middleware(__dirname + '/app/public'));
});
   :
この状態で、.cssがなければnode起動時に.stylを.cssに変換してくれるんですが、 一度作られると、.stylを編集しても.cssに変換されませんでした。
なので、node_devを使えば、良さそうです。

express
http://expressjs.com/
matador
http://obvious.github.com/matador/
stylus
http://learnboost.github.com/stylus/
2012年に使いたいJavaScriptのMVCフレームワーク14選
http://memo.yomukaku.net/entries/8nZdDFu
node.jsの開発時に役立つモジュール
http://blog.asial.co.jp/794

2012/08/11

[emacs] jade-mode, stylus-mode

node(node.js)+expressを使ったWebアプリケーションを書く環境を整えています。
僕はEmacsを使っているんで、expressのテンプレートエンジンであるJadeとCSSメタ言語Stylusを Emacsで書くために、jade-modeをインストールしました。
ので、その設定を書いときます。


jade-modeは、 https://github.com/brianc/jade-mode/ からもってきます。
jade-modeがstylusモードを内包していました。


.emacsには、以下を追加します。

  (add-to-list 'load-path "/path/to/jade-mode")
  (require 'stylus-mode)
  (require 'jade-mode)    
  (add-to-list 'auto-mode-alist '("\\.styl$" . stylus-mode))
  (add-to-list 'auto-mode-alist '("\\.jade$" . jade-mode))
簡単すね。

2012/07/29

[nginx] アクセスログフォーマット

nginxはアクセスログとエラーログを出力してくれます。
このログの出力フォーマットのカスタマイズ方法について書きます。

nginxのログフォーマットに関する公式ドキュメントは、HttpLogModuleにあります。

コレを拙いながらも訳すと…


log_formatディレクティブは、ログのフォーマットを表現します。 フォーマットにおいて、一般的な変数と同様に、ログ出力の瞬間のみに有効となる変数も使用出来ます。
  • $body_bytes_sent, クライアントに転送したバイト数のうち、レスポンスヘッダのバイト数を引いたもの。この変数は、Apacheのmod_log_configの%Bパラメータと互換性があります(0.3.10以前のバージョンでは、$apache_bytes_sentと呼ばれていました。)。
  • $bytes_sent, クライアントに転送したバイト数です。
  • $connection, コネクション数です。
  • $msec, ログを出力した時点の時刻(マイクロ秒の精度)です。
  • $pipe, リクエストがパイプラインによるものである場合、"p"を出力します。
  • $request_length, リクエストボディの長さです。
  • $request_time, nginxがそのリクエストを処理するのにかかった時間で、ミリ秒精度を含んだ秒数です。(0.5.19以前のバージョンでは秒数のみ)
  • $status, 応答ステータスです。
  • $time_ios8601, ISO 8601フォーマットの時刻です。例. 2011-03-21T18:52:25+03:00(バージョン0.9.6で追加)
  • $time_local, ローカル時刻です。

クライアントから転送されたヘッダについては、$sent_http_content_rangeのように"sent_http_"プレフィクスがつきます。

他のモジュールが提供する変数も、ログに出力できます。例えば、upstreamのレスポンスヘッダは、"upstream_http_"プレフィクスをつけて、ログに出力できます。

以下は、"combined"と呼ばれる、予め定義されたログフォーマットです。

log_format combined 'remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';


となっています。
したがって、
log_format 名前 フォーマット;
を定義して、"フォーマット"の部分に含める変数を必要に応じて並べてあげればいいわけです。

2013/03/03追記。
日本語のnginxの本があったんですね。知りませんでした..。

2012/07/12

[rails] rspecでDBのロールバックをテストする。

Railsアプリで、ActiveRecord::Base.transactionを使って、 トランザクションを明示的に張り、異常がある場合にロールバックする、 という実装は大いに有りますよね。

僕はRSpecを使ってテストしています。
RSpecを使って、DBがロールバックされることを確認する方法を書いておきます。

例えば、コントローラに次のような処理を書いたとします。
モデルの変更後、メールを送信する、という処理で、ここでは、メールの送信に失敗した場合は、 変更をロールバックする、という仕様とします。(その仕様は果たして妥当か?はここでは考えない。)


def save_and_send_mail(model)
  begin
    ActiveRecord::Base.tansaction do
      model.save!
      SomeMailer.something.deliver
    end
  rescue => e
    logger.error(e.message)
    logger.error(e.backtrace)
  end
end

「メールの送信に失敗したら、DBをロールバックする」という振る舞いを確認するために、 実行前後でデータを引っ張り出して比較するというのも出来なくはなさそうですが、
ちょっと面倒に思えます。

なので、「ロールバックする」メソッドが呼ばれることを確認することにました。
この「ロールバックする」メソッドとは、

  • ActiveRecord::Base.connection.rollback_db_transaction
にあたるので、

describe 'some method' do

  before do
    # 前処理
  end

  it "should rollback transaction if fail to send mail." do
    # 送信を失敗させる。
    SomeMailer.stub(:something).and_raise StandardError
    # ロールバック処理が呼ばれること
    ActiveRecord::Base.connection.should_receive(:rollback_db_transaction).once
    # 実行
    @controller.save_and_send_mail(model)
  end

end

というexampleが書けます。
これで、RSpecを使って、簡単にロールバックのテストができます。

2012/06/29

[rails] ストアドファンクションをロードするrakeタスク

Railsアプリの開発環境下でMySQLのストアドファンクションを作成し、 rakeでロードできるようにしています。
おおよそ次の手順で実現しました。

ストアドファンクションを書く。

{Rails.root}/script/db/sql 配下に、拡張子sqlのファイルとして、 ストアドファンクションを作成。

rakeタスクを書く。

{Rails.root}/lib/task 配下に、db_functions.rakeを作成。
タスクは、

  • db:functions
  • db:functions:load
の2つ。
1つ目は、ロードするファンクションの一覧を表示するタスク。
2つ目は、実際にファンクションをロードするタスク。
内容は以下の通り。
namespace :db do
  namespace :functions do
    desc "List .sql files."
    task :list => :environment do
      path = Rails.root + "script/db/sql/*.sql"
      Dir.glob(path) do |sql|
        puts File.basename(sql)
      end
    end

    desc "Load stored functions to the database."
    task :load => :environment do
      begin
        cmd = build_load_cmd(ActiveRecord::Base.configurations)
        path = Rails.root + "script/db/sql/*.sql"
        Dir.glob(path) do |file|
          exec(cmd + " < " + file)
        end
      rescue => e
        puts e.message
        raise e
      end
    end
  end
end

def build_load_cmd(db_config)
  env = ENV['RAILS_ENV']
  env = 'development' if env.nil?

  conf = db_config[env]
  cmd = "mysql"
  cmd << " -h " + conf["host"]     if conf["host"]
  cmd << " -u " + conf["username"] if conf["username"]
  cmd << " -p"  + conf["password"] if conf["password"]
  cmd << " "    + conf["database"]
  cmd
end

これで、rake db:functions:load とすると、ファンクションが生成されます。
もちろん、sqlファイル内で、drop functionしてからcreate functionしています。

デプロイにCapistranoを使う場合、deploy:migrateタスクのafterフックにdb:functions:loadを追加すれば、 DBに変更がある場合に、ファンクションの更新も漏れ無く実行されます。

2012/06/17

[rails] Rails3でWebフォントを使う。

Webフォントは、サーバーサイドにフォントを配置し、サーバーサイドが指定したフォントを クライアントに使用させる仕組みです。
Rails3で、Webフォントを使用する場合の手順を書きます。
(本当にこれでいいのか自信がありませんが、それらしく動きました。)

  1. まず、フォントファイルを配置するディレクトリを作り、フォントファイルを配置します。
    僕は、{Rails.root}/app/assets/fonts というディレクトリを作りました。
    フォントファイルは、ttf形式のみです。(IEのこととかはここでは考えず。)
  2. CSSにフォントを定義します。
    まず、@font-faceの定義。(下はSCSSの記載です。)
    @font-face{
      font: {
        family: 'migmix-1p';
        weight: 'normal,bold';
        style:  'normal';
      }
      src: asset-url('migmix-1p-regular.ttf', font) format('truetype'),
           asset-url('migmix-1p-bold.ttf', font)    format('truetype');
    }
    
    asset-urlは、Rails3のassets pipelineで、assetに対するパスを解決してくれるヘルパです。
    次に、@font-faceの適用。
    全てのコンテンツにWebフォントを使用したかったので、body要素に上記フォントを指定。
    body {
      :
      font-family: 'migmix-1p';
      :
    }
    
  3. 最後に、application.rbを編集し、assetsパスにfontsを追加します。
    config/application.rbに、以下の設定を追加します。
    config.assets.paths << "#{Rails.root}/app/assets/fonts"
    
  4. Webフォントが適用されていることを確認します。

Rails3でWebフォントを利用する方法について書きました。
より適切な方法をご存じの方がいらしたら、コメントいただけると嬉しいです!

2012/06/14

[rails] jpmobileでスマートフォンページ振り分け

jpmobileを使うと、簡単に携帯向けページを作れる、 とのことで、スマートフォン向けのページをRails3で作った。

以下のサイトを参考にした。
今最もモテる組み合わせのRails3.1 + jp_mobile でサイトを作った際のメモ

要件としては、

  • PC向けページとスマートフォン向けを同じURLで
  • キャリアなどは細かく判定せず
  • Railsだけで振り分ける
というもの。
HTTPサーバによるUserAgentの判定などはしない。

手順は簡単。

  1. 普通に(PC向けに)MVCを実装する。
  2. Gemfileにjpmobileを追加。
    ※ Railsのバージョンとjpmobileのバージョンの対応に注意。 僕はRails3.1系を使っているので、jpmobileは2.0.8を使用。
    gem 'jpmobile', '2.0.8'
    
  3. スマートフォン用のリソースを作成(view,css,js)。
    スマートフォン用のリソースを、それぞれ、
    • {Rails.root}/app/views/layouts/application_smart_phone.html.erb
    • {Rails.root}/app/views/xxxxxxx/xxxxx_smart_phone.html.erb
    • {Rails.root}/app/views/xxxxxxx/smart_phone/xxxxx.html.erb
    • {Rails.root}/app/assets/stylesheets/smart_phone/application.css.scss
    • {Rails.root}/app/assets/stylesheets/smart_phone/xxxxx.css
    • {Rails.root}/app/assets/javascripts/smart_phone/application.js
    • {Rails.root}/app/assets/javascripts/smart_phone/xxxxx.js.coffee
    のように作成し、配置した。cssのインクルードや、jsのインクルードは、きちんと整理して実装する。
    jpmobileには、ViewSelectorという機能があり、内部でUserAgentを判定し、 しかるべきビューに振り分けてくれる。
    コレを使うには、コントローラに、
      include Jpmobile::ViewSelector
    
    を書く。
    僕は、ApplicationControllerに実装した。
    としておいて、例えばiPhoneからアクセスした場合、jpmobileがビューを、
    • xxxxx_smart_phone_iphone.html.erb
    • xxxxx_smart_phone.html.erb
    • xxxxx.html.erb
    の順で、範囲の狭い順に検索し、見つけたものをレンダリングしてくれる。
    ※ xxxxxは、action名。
    iPhoneかAndroidか、みたいな判定をしなくていい場合は、xxxxx_smart_phone.html.erbを用意すればいい。
  4. 最後に、asset:compileの対象に、stylesheets/smart_phoneとjavascripts/smart_phoneを追加する。
    {Rails.root}/config/environments/production.rbに、
    initializer :after_append_asset_paths,
    :group => :all,
    :after => :append_assets_path do
      config.assets.paths << Rails.root.join('app', 'assets', 'stylesheets', 'smart_phone')
      config.assets.paths << Rails.root.join('app', 'assets', 'javascripts', 'smart_phone')
    end
    
    を追記して、smart_phone配下のリソースをコンパイル対象に含める。

上記で、一応ビューの切り替えが実現できた。
Androidでアクセスしたらば、スマートフォン用ページが表示されました。

2012/06/01

[rails] htmlテンプレートをレンダリングしてjs.erb内で使用する。

Rails3のlink_toや、form_for、form_tagで:remote => trueを指定すると、 デフォルトでdataType=jsでリクエストが発行される。
この場合、コントローラは、jsとして応答したいが、jsでページの一部のコンテンツを更新したい時、 更新内容は、html.erbとして定義しておきたい、という場合がある。
js.erbの中で、html.erbをレンダリングした結果を使用するには、
  $("some_element").html("<%= escape_javascript(render(:partial => 'some_partial_html')) %>");
のように、escape_javascriptヘルパを利用する。

2012/05/29

[rails]ActiveRecordのソート

ActiveRecordには、ソートに関するメソッドが3つあります。

  • order
  • reorder
  • reverse_order

order

指定した文字列、もしくはシンボルでソートします。


Sample.order(:created_at)                 #=> ORDER BY created_at
Sample.order("created_at")                #=> ORDER BY created_at
Sample.order("created_at ASC, name DESC") #=> ORDER BY created_at ASC, name DESC

reorder

デフォルトスコープで指定したソート順を上書きし、reorderで指定したソート条件を適用します。


class Post < ActiveRecord::Base
  ..
  ..
  has_many :comments, :order => 'posted_at DESC'
end
 
Post.find(10).comments.reorder('name')

reverse_order

指定したソートの昇順・降順を入れ替えてソートします。 (以前こっちに書きましたが・・)


Sample.order(:created_at).reverse_order #=> ORDER BY created_at DESC

別段これは!ということは書いていませんが、reorderの存在を最近知ったので、 ちょっとまとめてみました。

そういえば、第4版が出ていましたね。

2013/11/01 追記。
Rails4におけるソートの新機能について、Rails4からActiveRecordのorderにハッシュを渡せるようなった。に書きました。

opensslで性能測定

opensslを誤解していて、鍵生成ツールだとばかり思っていたのですが、 実は様々なサブコマンドが用意されていて、性能測定までできてしまうことを今日知りました。

いつものように、

$ openssl --help
とやると、次のようなエラーメッセージが表示されます。
$ openssl --help
openssl:Error: '--help' is an invalid command.

Standard commands
asn1parse         ca                ciphers           cms               
crl               crl2pkcs7         dgst              dh                
dhparam           dsa               dsaparam          ec                
ecparam           enc               engine            errstr            
gendh             gendsa            genpkey           genrsa            
nseq              ocsp              passwd            pkcs12            
pkcs7             pkcs8             pkey              pkeyparam         
pkeyutl           prime             rand              req               
rsa               rsautl            s_client          s_server          
s_time            sess_id           smime             speed             
spkac             srp               ts                verify            
version           x509              

Message Digest commands (see the `dgst' command for more details)
md4               md5               rmd160            sha               
sha1              

Cipher commands (see the `enc' command for more details)
aes-128-cbc       aes-128-ecb       aes-192-cbc       aes-192-ecb       
aes-256-cbc       aes-256-ecb       base64            bf                
bf-cbc            bf-cfb            bf-ecb            bf-ofb            
camellia-128-cbc  camellia-128-ecb  camellia-192-cbc  camellia-192-ecb  
camellia-256-cbc  camellia-256-ecb  cast              cast-cbc          
cast5-cbc         cast5-cfb         cast5-ecb         cast5-ofb         
des               des-cbc           des-cfb           des-ecb           
des-ede           des-ede-cbc       des-ede-cfb       des-ede-ofb       
des-ede3          des-ede3-cbc      des-ede3-cfb      des-ede3-ofb      
des-ofb           des3              desx              rc2               
rc2-40-cbc        rc2-64-cbc        rc2-cbc           rc2-cfb           
rc2-ecb           rc2-ofb           rc4               rc4-40            
seed              seed-cbc          seed-cfb          seed-ecb          
seed-ofb          zlib              

-hや、--helpはサポートしてないけど、たくさんのサブコマンドを備えていることがわかります。 そこで気になったのが、「speed」と「s_time」というサブコマンド。 ヴァージョンの違いなどがあると思うので、詳しくはmanを参照していただきたいのですが、
  • speed Algorithm Speed Measurement.
  • s_time SSL Connection Timer.
って(僕のubuntuは)出力します。

で、使ってみました。

speed

speedサブコマンドは、暗号化アルゴリズムごとの処理速度を計測してレポートしてくれるコマンドです。 この結果を利用して何ができるかというと、例えばHTTPサーバにおいて、HTTPS接続をサポートする際に、 サーバで受け付けるSSLの暗号化方式のリストに反映するなどがあります。

s_time

s_timeサブコマンドは、SSLコネクションの接続速度を計測しレポートしてくれるコマンドです。 このコマンドでは、新規に接続する場合と、同一セッションで最接続する場合の速度を計測してくれます。 ですんで、サーバ側のSSLキャッシュの有効性などを検証することが可能です。

OpenSSL、もっと勉強しないと・・・(自戒)

2012/05/22

[mysql]テーブルスキャンを避ける方法

mysqlのドキュメントに「6.2.15 テーブルスキャンを避ける方法」っていうのがある。
一部コピペになるけれど、自分の理解のために書いてみる。

MySQLがあるクエリを実行する際に、テーブルスキャン(=フルスキャン)をするかどうかは、
EXPLAINを使うとわかる。EXPLAINの結果、
+----+-------------+------------+-------+・・・
| id | select_type | table      | type  |・・・
+----+-------------+------------+-------+・・・
|  1 | SIMPLE      | xxxxx      | const |・・・
|  1 | SIMPLE      | xxxxx      | ALL   |・・・
|  1 | SIMPLE      | xxxxx      | ALL   |・・・
+----+-------------+------------+-------+・・・
のように、type列が"ALL"になっているテーブルは、フルスキャンが実行され、パフォーマンスが劣化する場合がある。 typeがALLになる条件のは、
  • テーブルが小さい。
  • (INDEXを張っていても)WHERE句でINDEXを使用するに至らない。
  • (INDEXを張っていても)WHERE句でINDEXをキーとして除外する行数が小さい。
といった時になりそう(誤解してなければ・・)。 というわけで、まずは、適切にインデックスを張る、ことが最初の一歩だと確信している。

2012/04/24

[rails]establish_connectionで接続先DBを変更する。

開発中の一連のシステムは、Rails3によるアプリを複数立ち上げ、 全て同じデータベースを参照している。
データベースは、その中の1つが管理(schemaや、migration)し、 他のアプリは、そのデータベースに接続する。

データベースを管理しないアプリ側では以下のコードを実装し、 接続先を変更する。
{Rails.root}/lib/custom_connection.rb

  module CustomConnection

    def establish_connection_to_other
      case Rails.env
      when "development"
        establish_connection :other_development
      when "test"
        establish_connection :other_test
      when "staging"
        establish_connection :other_staging
      when "production"
        establish_connection :other_production
      end
    end

  end
  ActiveRecord::Base.send(:extend, CustomConnection)

{Rails.root}/config/initializers/config.rb

  require 'custom_connection'
  ActiveRecord::Base.establish_connection_to_other

config/database.ymlには、 other_development, other_test, other_staging, other_productionの 定義が必要になる。

2012/04/22

curlでBasic認証超え

curlで、Basic認証を超えるには、
  $curl --basic --user <username>[:password] url
のオプションをつける。

2012/04/05

[rails]kaminari

rails3で、リスト形式の画面をページングしたかった。
will_paginate というgemがあることは知っていたが、
rails3系(実際に適用したかったのは3.1.3)で使えるのかわからなかったので、
amatsuda氏の「kaminari」を使ってみた。

恐ろしく簡単で、2hほどでページングが実装できた。
便利だと思ったのは、ARのscopeに対してページングができること。
ちっともハマらずAjaxなリクエストにも容易に対応できた。
これはスグレモノですね!

2012/02/16

ストロベリーナイト

原作を読み始めました。(ドラマは見てません)
結構面白い。
短絡的に「推理小説」を思い出しつつ、「マークスの山」が浮かんでます。
まだ、106ページ。
今週で読み終わるかな。

2012/02/15

[Rails]reverse_orderで降順ソート

RailsでActiveRecord::Baseの派生クラスを使ってQueryを実行する際に、
ソートする、のは当たり前にあります。

例えば、 「ユーザ」を現すモデル「User」があって、登録日時でソートする、
という場合は、


  @users = User.order(:created_at)


のように、orderメソッドを実行します。
この例では、created_atの昇順でソートされます。

降順でソートする場合は、どうしますか?
これまで私は、次のように書いていました。


  @users = User.order("created_at desc")


ですが、 reverse_orderという 便利なAPIがあったんですね、これを使えば、シンボルを使えるわけです。
なので、

  @users = User.order(:created_at).reverse_order


というように書けました。
すっきりしました!

2012/01/17

[emacs] scss-mode

Rails3.1から採用されたscssをemacsで読み書きしたい。
ということで、scss-modeを導入しました。

※ scssについては、こちらを。
   http://sass-lang.com/

1. scss-modeの取得
    githubからcloneしました。
    https://github.com/antonj/scss-mode/

2. .emacsの編集

 (add-to-list 'load-path "path/to/scss-mode")
 (require 'scss-mode "scss-mode")
 (add-to-list 'auto-mode-alist '("\\.scss\\'" . scss-mode))

3. emacs再起動
    emacsを再起動します。