守破離でいこう! -Let's go with SyuHaRi!-

2008/07/17

Rails 2.1.0 をインストールしてみる

久しぶりに、Railsの環境を更新しようと下記のコマンドこ実行すると、エラーがでました。
やはり、すんなりといきませんねぇ~。

> gem update --system
> gem update rails
Updating installed gems
Updating rails
ERROR:  While executing gem ... (Gem::InstallError)
 invalid gem format for c:/ruby/lib/ruby/gems/1.8/cache/activesupport-2.1.0.gem

いろいろ調べてみると、activesupportを手動でインストールすることで解決できるようです。

http://rubyforge.org/projects/activesupport/

Download gem to a local file, then

sudo gem install --local activesupport-2.1.0.gem

All fixed.

参考:Riding Rails: Rails 2.1: Time zones, dirty, caching, gem dependencies, caching, etc

ラベル:

automatically translated by Google Translate Hack!

naoki 20:43 | 0 comments |
HaloScan: |

2008/04/25

ホスト名とOpenID

Rails で OpenID を試してみようと思って思わぬところで躓いたのでメモです。

ホスト名に使用できる文字は、英数字文字(a-zA-Z0-9)および、ハイフン(-)となっています。

ホスト名について

ホスト名(hostname)は,[RFC1034]の3.及び[RFC1123]の2.1 で示される形式をとる。すなわち,"."によって分離されたドメインラベルの列であって,各々のドメインラベルは,英数字文字(alphanum)で開始及び終了し,"-"文字を含んでもよい。完全限定ドメイン名の最も右にあるドメインラベルは,数字で始まってはならない。そのために,IPv4アドレスとは構文的に区別されるドメイン名になる。

Uniform Resource Identifiers (URI): Generic Syntax: Main(日本語)

テストのために自分のOpenIDを取得したのですが、これがまた、上で述べたルールに反したものを取得してしまいました・・。

取得したIDは、「ishikawa_rs.openid.ne.jp」なのですが、アンダーバーが入っています><

このIDをOpenIDのプラグインであるopen_id_authenticationに通すと、「ishikawa_rs.openid.ne.jp is not an OpenID URL」と言われてしまいます。

エラーが発生するところをirbで再現してみると下記のようになります。

irb(main):001:0> require 'uri'
=> true
irb(main):002:0> url = "ishikawa_rs.openid.ne.jp"
=> "ishikawa_rs.openid.ne.jp"
irb(main):003:0> uri = URI.parse(url.to_s.strip)
=> #
irb(main):004:0> uri = URI.parse("http://#{uri}") unless uri.scheme
URI::InvalidURIError: the scheme http does not accept registry part: ishikawa_rs
.openid.ne.jp (or bad hostname?)
        from c:/ruby/lib/ruby/1.8/uri/generic.rb:195:in `initialize'
        from c:/ruby/lib/ruby/1.8/uri/http.rb:78:in `initialize'
        from c:/ruby/lib/ruby/1.8/uri/common.rb:488:in `new'
        from c:/ruby/lib/ruby/1.8/uri/common.rb:488:in `parse'
        from (irb):4
irb(main):005:0>

bad hostnameです。

そもそも、なぜこのIDになったかというと、「ishikawa.rs」にしようとしたところ、「ドット(.)はだめです。アンダーバー(_)は使えるよ。」と言われたからでした・・。

OpenIDを取得する際、ユーザIDがホスト名になる場合はご注意ください。

※そして、困ったことにopenid.ne.jpではアカウントの削除ができないようです。

ラベル: ,

automatically translated by Google Translate Hack!

naoki 21:57 | 0 comments |
HaloScan: |

2008/03/28

Rails と AIR で、付箋紙アプリ

Stickynotes

Rails の最新は 2.0.2 ですし、AIR は正式版の 1.0 が公開されました。
情報はある程度追ってはいるものの、やはり実際に試してみないとなかなか身につきません。

というわけで、以前から気になっていた、[Think IT] 第1回:付箋紙アプリケーションを作ろう!を参考に、Ruby on RailsとAIRによるデスクトップ付箋紙アプリケーションを作ってみました。

Adobe AIR のインストールはこちらから。

サンプル付箋紙アプリをお試しいただく場合は、こちらから。
ちなみに、このアプリはユーザ管理はしておりません。ので、大変ソーシャルな付箋アプリです(汗

Download Stickynotes AIR

基本的な動作は、[Think IT] 第1回:付箋紙アプリケーションを作ろう!と、Ruby on RailsとAdobe AIRでデスクトップアプリを作る - Pokeal.COMをベースに、なんとなくタスクトレイアイコンも使ってみました。ついでに、常に前面表示も可能です。

Railsに関しては、実質、次の2行のみです。これでRestfulなバックエンドアプリのできあがりです・・。

 $ ruby script/generate scaffold sticky body:text x:float y:float width:integer height:integer
 $ rake db:migrate

なお、Railsアプリは、先日紹介した、Herokuで構築しています。

AIRでは、透過するTextFieldを作るのにちょっと苦労しました。
ポイントは、blendModeをLAYERにしたSpriteでした。

dynamic textfield alpha problem - kirupaForum

I am not sure if this is quite what you are looking for but here is a solution I found for adjusting alpha on a TextField object.

// create an empty sprite
var rect:Sprite = new Sprite();
rect.blendMode = BlendMode.LAYER;

addChild(rect);

var txtField:TextField = new TextField();
txtField.txtColor = 0x000000;
txtField.alpha = .5;
txtField.appendText("SOME TEXT");

rect.addChild(txtField);

the key is to set the blendMode property on the sprite to LAYER.

以下、参考までにソースコードです。

Menu.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="300" height="200" creationComplete="onCreationComplete();" closing="closing(event);">
  <mx:Script>
 <![CDATA[
   import mx.core.BitmapAsset;

   [Embed(source="icons/broken-16x16.png")]
   private var icon16:Class;

   private var stickies:Array = [];

   private function create():void {
  setStatus(">> Create new sticky");

  var sticky:Sticky = new Sticky();

  sticky.setAlwaysInFront(isAlwaysInFront.selected);
  sticky.save();
  sticky.show();
  stickies.push(sticky);

   }

   private function onCreationComplete():void {
  setStatus(">> Initializing...");

  // is supports system tray icon
  if (NativeApplication.supportsSystemTrayIcon) {

    var images:Array = [];
    images.push((new icon16() as BitmapAsset).bitmapData);
    nativeApplication.icon.bitmaps = images;

    var systemTrayIcon:SystemTrayIcon = (nativeApplication.icon as SystemTrayIcon);

    systemTrayIcon.tooltip = "Stickynotes";

    var nativeMenu:NativeMenu = new NativeMenu();
    var menuItemNew:NativeMenuItem = new NativeMenuItem("New Stickynote");
    menuItemNew.addEventListener(Event.SELECT, function(e:Event):void {
   create();
    });
    var menuItemReload:NativeMenuItem = new NativeMenuItem("Reload Stickynotes");
    menuItemReload.addEventListener(Event.SELECT, function(e:Event):void {
   load();
    });
    var menuItemRestore:NativeMenuItem = new NativeMenuItem("Restore window");
    menuItemRestore.addEventListener(Event.SELECT, function(e:Event):void {
   restore();
    });
    var menuItemExit:NativeMenuItem = new NativeMenuItem("Exit");
    menuItemExit.addEventListener(Event.SELECT, function(e:Event):void {
   exit();
    });
    nativeMenu.addItem(menuItemNew);
    nativeMenu.addItem(menuItemReload);
    nativeMenu.addItem(menuItemRestore);
    nativeMenu.addItem(menuItemExit);
    systemTrayIcon.menu = nativeMenu;

  }

  load();
   }

   private function load():void {
  setStatus(">> Loading from http://stickynotes.heroku.com ...");

  closing(null);

  var request:URLRequest = new URLRequest("http://stickynotes.heroku.com/stickies.xml");
  request.method = 'GET';
  var loader:URLLoader = new URLLoader();
  loader.addEventListener(Event.COMPLETE, function(e:Event):void {
    messages.text += e.target.data + "\n";

    var xml:XML = new XML(e.target.data);
    for each (var element:Object in xml.sticky) {
   var sticky:Sticky = new Sticky();
   sticky.id = element.id;
   sticky.editor.text = element.body;
   sticky.window.x = element.x * Capabilities.screenResolutionX;
   sticky.window.y = element.y * Capabilities.screenResolutionY;
   sticky.window.width = element.width;
   sticky.window.height = element.height;
   sticky.setAlwaysInFront(isAlwaysInFront.selected);
   sticky.updateStatus();
   sticky.show();

   stickies.push(sticky);
    }

    clearStatus();
  });
  loader.load(request);
   }

   private function setStatus(s:String):void {
  this.status = s;
   }
   private function clearStatus():void {
  this.status = "";
   }

   private function closing(e:Event):void {
  for each (var sticky:Sticky in stickies) {
    if (sticky) {
   sticky.window.close();
    }
  }
  stickies = [];
   }

   private function saveAll():void {
  setStatus(">> Save stickies");

  for each(var sticky:Sticky in stickies) {
    sticky.save();
  }
   }

   private function onChangeHandle():void {
  for each (var sticky:Sticky in stickies) {
    sticky.setAlwaysInFront(isAlwaysInFront.selected);
  }
   }

 ]]>
  </mx:Script>
  <mx:Button left="10" top="10" label="New" id="new_btn" click="create();" />
  <mx:Button left="60" top="10" label="Reload" id="load_btn" click="load();" />
  <mx:Button right="10" top="10" label="Save" id="save_btn" click="saveAll();" />
  <mx:CheckBox left="10" top="40" label="always in front" id="isAlwaysInFront" change="onChangeHandle();" />
  <mx:TextArea right="10" top="70" left="10" bottom="10" id="messages" />
</mx:WindowedApplication>

Sticky.as

package {
  import flash.text.*;
  import flash.display.*;
  import flash.events.*;
  import flash.system.*;
  import flash.net.*;
  import mx.controls.*;

  public class Sticky {
  public var window:NativeWindow; // sticky window
  public var editor:TextField; // sticky edit area
  public var id:Number; // primary key
  private var button:SimpleButton = new SimpleButton(); // cloase button
  private var resizeHandle:SimpleButton = new SimpleButton(); // resize handle
  private var x:Number; // sticky x
  private var y:Number; // sticky y
  private var height:Number; // sticky height
  private var width:Number; // sticky width
  private var body:String; // sticky body
  private var sprite:Sprite = new Sprite();
  private static var RESIZE_HANDLE_SIZE:int = 20;

  /* create sticky window */
  public function Sticky():void {
 var initOptions:NativeWindowInitOptions = new NativeWindowInitOptions();
 initOptions.systemChrome = NativeWindowSystemChrome.NONE;
 initOptions.transparent = true;
 initOptions.type = NativeWindowType.LIGHTWEIGHT;

 window = new NativeWindow(initOptions);
 window.alwaysInFront = false;
 window.stage.align = StageAlign.TOP_LEFT;
 window.stage.scaleMode = StageScaleMode.NO_SCALE;

 // layer for transparent editor
 sprite.blendMode = BlendMode.LAYER;
 window.stage.addChild(sprite);

 // for edit area
 editor = new TextField();
 editor.x = editor.y = 0;
 editor.selectable = true;
 editor.border = false;
 editor.type = TextFieldType.INPUT;
 editor.multiline = true;
 editor.background = true;
 editor.wordWrap = true;
 editor.backgroundColor = 0xE6E082;
 editor.alpha = 1;
 sprite.addChild(editor);

 // window position of center
 window.x = 0.5 * Capabilities.screenResolutionX - 300 * 0.5
 window.y = 0.5 * Capabilities.screenResolutionY - 100 * 0.5

 // size for window and edit area
 window.width = editor.width = 300;
 window.height = editor.height = 100;

 // resize for window and edit area
 window.stage.addEventListener(Event.RESIZE, function(e:Event):void {
   resized();
 });

 // move and resize
 window.stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void {
   var x:Number = e.stageX;
   var y:Number = e.stageY;
   if (y > window.height - RESIZE_HANDLE_SIZE && x > window.width - RESIZE_HANDLE_SIZE) {
  //window.startResize(NativeWindowResize.BOTTOM_RIGHT);
   } else {
  window.startMove();
   }
 });

 window.stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void {
   if (isChanged()) {
  save();
   }
 });

 editor.addEventListener(FocusEvent.FOCUS_OUT, function(e:FocusEvent):void {
   if (isChanged()) {
  save();
   }
 });

  } // end of function Sticky

  // show window
  public function show():void {
 // create close button
 button.x = window.width - 15;
 button.y = 5;
 button.upState = createBox(0xE6E082, 10, 0.3);
 button.overState = createBox(0xE6E082, 10, 1);
 button.downState = createBox(0xCCCCCC, 10, 1);
 button.hitTestState = button.upState;
 button.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void {
   var request:URLRequest = new URLRequest("http://stickynotes.heroku.com/stickies/" + id + ".xml");
   request.method = 'DELETE';
   var loader:URLLoader = new URLLoader();
   loader.load(request);
   window.close();
 });
 window.stage.addChild(button);

 // create resize handle
 resizeHandle.x = window.width - 15;
 resizeHandle.y = window.height - 15;
 resizeHandle.upState = createResizeHandle(0xE6E082, 10, 0.3);
 resizeHandle.overState = createResizeHandle(0xE6E082, 10, 1);
 resizeHandle.downState = createResizeHandle(0xCCCCCC, 10, 1);
 resizeHandle.hitTestState = button.upState;
 resizeHandle.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void {
   window.startResize(NativeWindowResize.BOTTOM_RIGHT);
 });
 window.stage.addChild(resizeHandle);

 window.visible = true;
  }

  // call after window resized
  public function resized():void {
 editor.width = window.width;
 editor.height = window.height;
 button.x = window.width - 15;
 button.y = 5;
 resizeHandle.x = window.width - 15;
 resizeHandle.y = window.height - 15;
  }

  // save sticky
  public function save():void {
 // create URL of sticky
 var request:URLRequest
 if (!id) {
   request = new URLRequest("http://stickynotes.heroku.com/stickies.xml");
   request.method = 'POST';
 } else {
   request = new URLRequest("http://stickynotes.heroku.com/stickies/" + id + ".xml");
   request.method = 'PUT';
 }

 // create paramater for edit
 var variables:URLVariables = new URLVariables();
 variables['sticky[x]'] = window.x / Capabilities.screenResolutionX;
 variables['sticky[y]'] = window.y / Capabilities.screenResolutionY;
 variables['sticky[width]'] = window.width;
 variables['sticky[height]'] = window.height;
 variables['sticky[body]'] = editor.text;
 request.data = variables;

 // send
 var loader:URLLoader = new URLLoader();
 loader.addEventListener(Event.COMPLETE, function(e:Event):void {
   var xml:XML = new XML(e.target.data);
   if (!id) {
  id = xml.id;
   }
   updateStatus();
 });
 loader.load(request);
  }

  public function setAlwaysInFront(value:Boolean):void {
 window.alwaysInFront = value;
 if (value) {
   editor.alpha = 0.85;
 } else {
   editor.alpha = 1;
 }
  }

  public function updateStatus():void {
 // sticky status
 x = window.x;
 y = window.y;
 width = window.width;
 height = window.height;
 body = editor.text;
  }

  /* private methods ***********/

  // close button
  private function createBox(color:uint, radius:Number, vis:Number):Shape {
 var xShape:Shape = new Shape();
 xShape.graphics.lineStyle(1, 0x000000);
 xShape.graphics.beginFill(color);
 xShape.graphics.drawRect(0, 0, radius, radius);
 xShape.graphics.moveTo(0, radius);
 xShape.graphics.lineTo(radius, 0);
 xShape.graphics.moveTo(radius, radius);
 xShape.graphics.lineTo(0, 0);
 xShape.graphics.endFill();
 xShape.alpha = vis;
 return xShape;
  }

  private function createResizeHandle(color:uint, radius:Number, vis:Number):Shape {
 var xShape:Shape = new Shape();
 xShape.graphics.lineStyle(1, 0x000000);
 xShape.graphics.beginFill(color);
 xShape.graphics.moveTo(0, radius);
 xShape.graphics.lineTo(radius, 0);
 xShape.graphics.lineTo(radius, radius);
 xShape.graphics.lineTo(0, radius);
 xShape.graphics.endFill();
 xShape.alpha = vis;
 return xShape;
  }

  // is paramater changed
  private function isChanged():Boolean {
 return (x != window.x || y != window.y ||
   width != window.width || height != window.height ||
   body != editor.text);
  }

  } // end of class Sticky

} // end of package

ラベル: ,

automatically translated by Google Translate Hack!

naoki 21:51 | 0 comments |
HaloScan: |

2008/02/24

ブラウザだけでRuby on Rails - Heroku -

以前、TechCrunchの記事Herokuを知って、すぐにベータテスタとして登録したのですが、先日アカウント登録のメールが届いたのでさっそく使ってみました。

Herokuとは、ブラウザの中だけで、Railsのアプリケーションを開発できるサービスです。また、作成したアプリは、Amazon EC2上で簡単にホストすることもできるようです。

気になるRailsのバージョンは「2.0.2」になるようです。また、データベースはPostgresなようです。

ほんとうに簡単に開発からデプロイまでできるので、ちょっとしたアプリを作るにはもってこいですね。
それに、後に公開されるプレミアム版では、サブドメインではなく、独自ドメインも使用可能になるようですので、要注目です。

ラベル:

automatically translated by Google Translate Hack!

naoki 21:51 | 0 comments |
HaloScan: |

2007/06/26

Rails API を読む

Railsに限らず、Documentは非常に重要で、とても参考になるものです。
いつもはRails Framework Documentを参考にするのですが、他に以下のようなサイトもあるようです。知らなかった・・・w

Ruby on Rails Manual
過去のバージョンのDocumentも揃っています。

Rails API with the AJAX flavor
AjaxでSuggestしてくれるので便利!

gotAPI/HTML - Instant search in HTML and other developer documentation
AJAX and Frameworks(Prototype.js等)や、HTML/CSS/Javascript等、Rails以外のも多数載ってます。こちらもAjaxでSuggest!これはいい!

ラベル:

automatically translated by Google Translate Hack!

naoki 9:01 | 0 comments |
HaloScan: |

2007/06/21

Edge Railsとruby-Gettext

先日、新しいRailsプロジェクトを作る機会がありまして、せっかくなのでRESTfulなRailsでいこうと思い、Edge Rails使うことを決意。

ですが、さっそく躓いたので、以下問題と解決です。ご参考までに。

アプリを起動するとエラーが発生し、development.logに以下のエラーが。

DISPATCHER FAILSAFE RESPONSE (has cgi) Wed Jun 20 21:18:28 +0900 2007
  Status: 500 Internal Server Error
  You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
    C:/ruby/lib/ruby/1.8/cgi.rb:1165:in `[]'
    C:/ruby/lib/ruby/gems/1.8/gems/gettext-1.9.0-mswin32/lib/gettext/locale_cgi.rb:26:in `system'
    C:/ruby/lib/ruby/gems/1.8/gems/gettext-1.9.0-mswin32/lib/gettext/locale.rb:88:in `system'
    C:/ruby/lib/ruby/gems/1.8/gems/gettext-1.9.0-mswin32/lib/gettext/locale.rb:96:in `default'
    C:/ruby/lib/ruby/gems/1.8/gems/gettext-1.9.0-mswin32/lib/gettext/rails.rb:276:in `render_file'
    :
    :

[gettext-u-en] Problem with gettext 1.9.0 and RESTful rails appを参考に、問題解決(ここまでくるのに、数日の間Google先生に質問攻めでした・・・)

--- /opt/local/lib/ruby/gems/1.8/gems/gettext-1.9.0/lib/gettext/locale_cgi.rb 2007-05-28 16:11:27.000000000 +0300
+++ Desktop/locale_cgi.rb 2007-05-28 16:11:49.000000000 +0300
@@ -23,7 +23,7 @@ module Locale
     def system
       return @@default_locale unless @@cgi
       cgi_ = cgi
-      if ret = cgi_["lang"] and ret.size > 0
+      if not cgi_.params.empty? and ret = cgi_["lang"] and ret.size > 0
       elsif ret = cgi_.cookies["lang"][0]
       elsif lang = cgi_.accept_language and lang.size > 0
  num = lang.index(/;|,/)

上記問題が発生したのは、
svn co http://dev.rubyonrails.org/svn/rails/trunk rails
でとってきたEdge Railsでした。
今しがた、再度Edge Railsを取り直したら上記問題は発生せずでした・・・
rake rails:freeze:edge TAG=rel_1-2-3

なんだったんだろw

ラベル:

automatically translated by Google Translate Hack!

naoki 9:38 | 0 comments |
HaloScan: |

2007/03/24

logに残されるパスワードをフィルタリングする

Railsアプリを作成していて、動作の確認等で非常に重要なログファイルですが、このログには様々な情報が残されます。
例えばユーザ登録画面等で送信したパラメータもログに残されます。
ユーザ登録の際は、パスワードを入力したりしますので、パスワードがそのまま平文で残されるのはちょいとまずいものです。

Processing UserController#signup (for 192.168.0.25 at 2007-03-24 10:41:08) [POST]
  Session ID: 42d8820cd16b8a672b86f737d8d6b4e8
  Parameters: {"user"=>{"password_confirmation"=>"testpassword", "lastname"=>"Tarou", "firstname"=>"Test", "login"=>"tester", "password"=>"testpassword", "email"=>"test@test.com"}, "commit"=>"Signup", "action"=>"signup", "controller"=>"admin/user"}

そこで、以下のようにするとログに残されるパスワードをフィルタリングすることができます。
filter_parameter_logging(*filter_words) {|key, value| ...}

class ApplicationController < ActionController::Base
  filter_parameter_logging "password"
end

すばらしい!

Processing UserController#signup (for 192.168.0.25 at 2007-03-24 10:43:50) [POST]
  Session ID: 42d8820cd16b8a672b86f737d8d6b4e8
  Parameters: {"user"=>{"password_confirmation"=>"[FILTERED]", "firstname"=>"Test", "lastname"=>"Tarou", "password"=>"[FILTERED]", "login"=>"tester2", "email"=>"test2@test.com"}, "commit"=>"Signup", "action"=>"signup", "controller"=>"admin/user"}
Filtering Sensitive Logs

ラベル:

automatically translated by Google Translate Hack!

naoki 10:18 | 0 comments |
HaloScan: |

2007/03/13

J-PHONE/3.0で、postがうまくいかない

携帯向けアプリを構築していて、特定の端末(おそらく)の場合だけ、post送信時、sessionが維持されない現象を確認。
post送信されるパラメータで、「"_session_id"=>"901cbb6b5a6d515a99a06373d20ba2f4?page=1"」というように、セッションIDのなかになぜか他のパラメータが混入する。
これでは当然、「そんなセッションありません」、ということになってsessionが維持できなくなります。

で、あれこれ試行錯誤した結果、どうやら、formのアクションで、自動でURLパラメータとしてsession_idを付与しているところが問題のよう。
formのアクションを直打ちして解決しました。

ちなみに自動でsession_idを付与してくれるのは、ActiveHeartのTransSidのお陰です。

参考:
RubyOnRails を使ってみる 【第 5 回】 ActiveHeart

ラベル:

automatically translated by Google Translate Hack!

naoki 19:41 | 0 comments |
HaloScan: |

2007/02/14

file_column and capistrano

Railsで画像ファイルをアップロードする場合、file_columnというプラグインがとても使えます。
このpluginは、画像のアップロード、保存、サムネイル作成といった面倒な処理を劇的に簡略化してくれます。

データベースにファイル名を保存するカラムを追加し、

add_column :entry, :image, :string

モデルで、file_column pluginを指定します。

class Entry < ActiveRecord::Base
  file_column :image
end

この場合の保存先は、
public/[model_name]/[attribute_name]/[id]/[file_name].jpg
になります。

サムネイルを作成する場合は、次のようにします。

class Entry < ActiveRecord::Base
  file_column :image, 
    :magick => {
      :versions => {
        :thumb => "50x50",
        :midle => "100x100",
        :large => "800x600"
      }
    }
end

この場合はそれぞれ、
public/[model_name]/[attribute_name]/[id]/[file_name].jpg
public/[model_name]/[attribute_name]/[id]/thumb/[file_name].jpg
public/[model_name]/[attribute_name]/[id]/midle/[file_name].jpg
public/[model_name]/[attribute_name]/[id]/large/[file_name].jpg
に保存されます。

ファイルをアップロードするには以下のhelperを使用します。

<%= file_column_field "entry", "image" %>

アップロードした画像を表示するには、以下のhelperを使用します。

<%= url_for_file_column "entry", "image" %>

サムネイルを表示するには、
<%= url_for_file_column "entry", "image", "thumb" %>

上記のように簡単に、非常に簡単に画像処理を扱えるようになるわけですが、capistranoでデプロイしている場合、画像の保存先がデフォルトのままだと、デプロイするたびに画像ファイルをコピーしなければなりません。
これではあんまりですが、ちゃんと回避策がありました。
file_columnのオプションに、root_pathというのがありまして、デフォルトの保存先を変更できます。
以下のように、capistranoが自動でリンクしてくれるsystemディレクトリに画像を保存するように指定すれば、いちいちコピーしなくてもよさそうです。

class Entry < ActiveRecord::Base
  file_column :image, 
    :magick => {
      :versions => {
        :thumb => "50x50",
        :midle => "100x100",
        :large => "800x600"
      }
    }, 
    :web_root => "system/files/", 
    :root_path => File.join(RAILS_ROOT, "public", "system", "files")
end


参考:
HowToUseFileColumn

ラベル:

automatically translated by Google Translate Hack!

naoki 9:20 | 0 comments |
HaloScan: |

2007/01/31

増加するログファイルへの対処

Railsでは、ログファイルに絶えず情報が追加されていき、ものすごい勢いで肥大化していくわけですが、でかいログファイルは様々な面でよくありません。
ですので、定期的にログファイルのローテーションをするわけですが、主にlogrotateを使用する方法と、Loggerを使用する方法があるようです。
ただし、FastCGIをマルチで使用している場合は、各FastCGIプロセスにてLoggerインスタンスが存在し、同一ログをローテートしてしまうため、Loggerの使用は控えたほうが良いようです。
というわけで、logrotateを使用します。

/path/to/your/app/log/*.log {
  daily
  missingok
  rotate 14
  compress
  delaycompress
  notifempty
  copytruncate
  create 0666 daemon daemon
}
参考:
DeploymentTips in Ruby on Rails

ラベル:

automatically translated by Google Translate Hack!

naoki 16:00 | 0 comments |
HaloScan: |

2007/01/30

ActiveRecodStoreな場合のセッションの掃除

Railsでは、セッションが自動生成されるのですが、自動削除はしてくれません。
ですので、自分で掃除をしなければいけません。

session_cleaner.rb

class SessionCleaner
  def self.remove_stale_sessions
    CGI::Session::ActiveRecordStore::Session.destroy_all(['updated_on < ?', 1.days.ago])
  end
end
上記クラスをlibあたりに保存し、cronで毎日自動実行させます。
0 0 * * * ruby /full/path/to/script/runner -e production "SessionCleaner.remove_stale_sessions"

参考:
Removing Stale Rails Sessions

ラベル:

automatically translated by Google Translate Hack!

naoki 15:15 | 0 comments |
HaloScan: |

2007/01/23

jpmobile使用時のfunctionalテスト

[03/18追記]

下記は何れも既に対応されております。ご苦労様です。
詳しくはdara日記 [jpmobile]をご覧下さい。

dara日記 - jpmobile - A Rails plugin for Japanese mobile-phones

jpmobileというプラグインを使用すると、携帯向けのサイト構築が非常に楽になります。
jpmobileを使用すると、以下のことができるようになります。

  • 携帯電話の判別
  • 端末位置情報の取得
  • 端末製造番号、契約者番号等の取得
  • IPアドレスの検証(キャリアが公開しているIPアドレス帯域からのアクセスか判定)

ものすごく便利なプラグインなのですが、functionalテスト時にはまりました。

NoMethodError: undefined method `mobile?' for #
mobile?そんなメソッドありません。とのことです。
で、悩んだ挙句導き出した答えは、
ActionController::TestRequest.class_eval { include Jpmobile::CgiRequestExpansion}
を、テストクラスにて記述します。
きっと、テスト用のリクエストにjpmobileの機能をincludeするってことだと思います(w)。

それと、ソフトバンク携帯からの実機確認時に、なぜか携帯端末と判定されないという問題が発生。
こちらは、vendor/plugins/jpmobile/lib/jpmobile/cgi_request_expansion.rb のSoftbank端末判定部分を

when /^Softbank/
から、
when /^SoftBank/
に修正しました。

ラベル:

automatically translated by Google Translate Hack!

naoki 13:50 | 0 comments |
HaloScan: |

2007/01/18

DateHelperでid属性を使いたい

RailsにはDateHelperという便利なものがありまして、これを利用すると簡単に日付や時刻のセレクトボックスを作成できます。
ですが、なぜかidや、class等の属性が無視されてしまいます。(Rails 1.1.6にて。最新のものはどうなの?)
javascriptで操作したいときなど、idがないと困ります。
そんなときは、下記のようにするとよいです。(bad hack!)

<%= select_day(Date.today, :prefix => 'search[date][day]" id="day_field', :discard_type => true) %>

参考:
Rails Forum / Ruby on Rails Help and Discussion Forum / How to give an ID to an input when using select helpers?

ラベル:

automatically translated by Google Translate Hack!

naoki 15:54 | 0 comments |
HaloScan: |