ラベル rails の投稿を表示しています。 すべての投稿を表示
ラベル rails の投稿を表示しています。 すべての投稿を表示

2011/02/23

Rails 3, MySQL, jQuery, Shoulda, FactoryGirl

Rails 3 で、MySQL, jQuery, Shoulda, FactoryGirl を使うときのプロジェクトメモ。

MySQL でプロジェクトを作成

デフォルトでは Sqlite3 を使用するので、オプションで MySQL を使用するように指定。

$ rails new appname -d mysql

jQuery, Shoulda, FactoryGirl の設定

jQuery, Shoulda, FactoryGirl を使用するために、各ファイルを編集する。

environment.rb
require 'openssl'
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
Gemfile
gem 'jquery-rails'
gem 'rails3-generators', :group => :development
group :test do
  gem 'shoulda'
  gem 'factory_girl_rails'
end
application.rb
config.generators do |g| 
  g.test_framework :shoulda, :fixture => true
  g.fallbacks[:shoulda] = :test_unit
  g.fixture_replacement :factory_girl, :dir => "test/factories"
end

インストール

$ bundle install

Shoulda / FactoryGirl が適用されているか確認。

$ rails g scaffold --help
...

Shoulda options:
  [--dir=DIR]                   # The directory where the model tests should go
                                # Default: test/unit
  [--fixture-replacement=NAME]  # Fixture replacement to be invoked
                                # Default: factory_girl

jQuery をインストール

generator で jQuery に置き換えます。

$ rails g jquery:install

2011/02/14

Rails 3 で OpenSSL::SSL::SSLError

Rails 3 でプロジェクト作成後、まずは jquery を使うようにしようと思い

$ rails g jquery:install

すると、早速エラー発生。

    fetching  jQuery UJS adapter (github HEAD)
c:/ruby/lib/ruby/1.8/net/http.rb:586:in `connect': SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (OpenSSL::SSL::SSLError)

ソースを github から取得する際に、SSL 接続でエラーということか。

Rails3 で jQuery を使う を参考に、environment.rb に下記を追記。

require 'openssl'
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE

無理やり SSL 非接続にしてなんとか回避。とりあえずは、よしとします。

2009/10/16

[rails] Mysql で Incorrect datetime value が発生

Rails 2.3.4 と Mysql 5.0 (Windows) の環境で、Unit Test を実行すると下記のエラーが発生。

>rake test:units
  1) Error:
test_should_authenticate_user(UserTest):
ActiveRecord::StatementInvalid: Mysql::Error: Incorrect datetime value: '2009-10-16 07:56:13 UTC' for column 'remember_token_expires_at' at row 1: INSERT INTO `users` (`salt`, `updated_at`, `crypted_password`, `remember_token_expires_at`, `id`, `remember_token`, `login`, `created_at`, `email`) VALUES ('356a192b7913b04c54574d18c28d46e6395428ab', '2009-10-15 07:56:13', '39e3509c8a1dff4e5b35850d970992321c4f1358', '2009-10-16 07:56:13 UTC', 1, '77de68daecd823babbb58edb1c8e14d7106e83bb', 'quentin', '2009-10-10 07:56:12', 'quentin@example.com')

Mysql の sql-mode を空白に設定することで解決しました。

my.ini
# Set the SQL mode to strict
#sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
sql-mode=''

参考:

Now, open the my.ini file located at c:program files/MySQL/MySQL
Server X.x/my.ini.
Add a # at the begining of the line: sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
Type sql_mode='' on the next line and save the file.
restart mysql

fixed it

[Rails] Re: restful_authentication rspec failures "Mysql::Error: Incorre

2008/11/11

[Rails][Plugins] multimodel-forms プラグインがすごい便利

has_many な関連を、ひとつのフォームで登録したいことはよくありますが、そんなときにおすすめなのが、 multimodel-forms プラグインです。



たとえば、ひとつの記事に複数のコメントの場合は、以下のようになります。



まずは、ベースプロジェクトを作成です。



rails sample
cd sample
ruby script/generate scaffold article title:string body:text
ruby script/generate model comment body:text article_id:integer
rake db:migrate


# ---------- app/models/article.rb ----------
class Article < ActiveRecord::Base
has_many :comments
end


# ---------- app/models/comment.rb ----------
class Comment < ActiveRecord::Base
belongs_to :article
end


multimodel-forms プラグインをインストール




ruby script/plugin install git://github.com/sudothinker/multimodel-forms.git


model を修正


# ---------- app/models/article.rb ----------
class Article < ActiveRecord::Base
has_many_with_attributes :comments
end


view を修正


# ---------- app/views/layouts/articles.html.erb ----------
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Articles: <%= controller.action_name %></title>
<%= stylesheet_link_tag 'scaffold' %>
<%= javascript_include_tag :defaults %>
</head>
<body>

<p style="color: green"><%= flash[:notice] %></p>

<%= yield %>

</body>
</html>


# ---------- app/views/articles/show.html.erb ----------
<p>
<b>Title:</b>
<%=h @article.title %>
</p>

<p>
<b>Body:</b>
<%= simple_format(h(@article.body)) %>
</p>

<h2>Comments</h2>
<ol>
<% @article.comments.each do |c| %>
<li><%= simple_format(h(c.body)) %> <small>(commented <%= time_ago_in_words(c.created_at) %> ago)</small></li>
<% end %>
</ol>

<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>


# ---------- app/views/articles/new.html.erb ----------
<h1>New article</h1>

<% form_for(@article) do |f| %>
<%= f.error_messages %>

<%= render :partial => 'form', :locals => { :f => f } %>
<p>
<%= f.submit "Create" %>
</p>
<% end %>

<%= link_to 'Back', articles_path %>


# ---------- app/views/articles/edit.html.erb ----------
<h1>Editing article</h1>

<% form_for(@article) do |f| %>
<%= f.error_messages %>

<%= render :partial => 'form', :locals => { :f => f } %>
<p>
<%= f.submit "Update" %>
</p>
<% end %>

<%= link_to 'Show', @article %> |
<%= link_to 'Back', articles_path %>


_form.html.erb を追加


# ---------- app/views/articles/_form.html.erb ----------
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>
<p>
<%= f.label :body %><br />
<%= f.text_area :body, :rows => 7 %>
</p>

<h3>Comments</h3>
<div id="comments">
<%= render :partial => 'comments/comment', :collection => @article.comments %>
</div>

<%= add_link "Add Comment", :comment %>


comments/_comment.html.erb を追加


# ---------- app/views/comments/_comment.html.erb ----------
<p class="comment">
<% fields_for_associated :article, comment do |comment_form| %>
<%= comment_form.text_area :body, :rows => 2, :index => nil %>
<%= delete_link_for(comment, "Delete", comment_form) %>
<% end %>
</p>


さっそく動作確認です。




非常に便利な、 multimodel-forms プラグインですが、 acts_as_list をサポートしているので、並び替えも簡単にできるようになります。

2008/09/04

Windows で git



Rails の Plugin をインストールしようとしたら、.git な GitHub(ぎっとはぶ)のもので、インストールできませんでした。


GitHub とは

git のホスティングサービス。Rails で作成されており、使いやすいインターフェイスが特徴。


Rails や RSpec 等、また http://gems.github.com/ の Rubygems のレポジトリソース等、Ruby 関係のライブラリのコードを中心とした様々なオープンソースの開発の場所ともなっている。無料で 100M 使えるアカウントを作ることができ、git レポジトリの作成、公開が可能。もちろん Web 上から変更履歴等も参照可能である。


またワンクリックで github で公開されている OSS のコードを fork して開発することが可能で、自分の変更を加えたレポジトリを pull してほしい、等の要望もワンクリックで可能である。


参考:githubとは - はてなキーワード



というわけで、Rails 界では今後 GitHub に移行していくようなので、後ればせながら、git をインストールしました。


git は、 MOONGIFT: » WindowsでGitをはじめるなら「msysGit」:オープンソースを毎日紹介 を参考に、 msysGit をインストール。


これで、 GitHub にあるプラグインもインストールできるようになります。


ruby script/plugin install git://github.com/rubypond/semantic_form_builder.git

2008/08/29

Rails で、アプリケーションから ER図 を生成できる RailRoad を試してみる



Rails には、アプリケーションのモデル・コントローラの内容や関係が記述されたクラス図を、リバースエンジニアリングして生成してくれる RailRoad という便利なツールがあります。


設計は軽くすませて、すぐにプログラミングしていくことが多い Rails アプリケーションですが、全体像を把握したい場合や、他の人に見せたい場合などは、こういうツールがあると便利ですね。


というわけで、実際に使ってみました。


インストール


Graphviz をまずはインストール


railroad をインストール


gem install railroad

Rake タスクとして実行できるようにする


lib/task/diagrams.rake
namespace :doc do
namespace :diagram do
desc "Generate Model diagrams."
task :models do
# SVG
sh "railroad -i -l -a -m -M | dot -Tsvg | sed 's/font-size:14.00/font-size:11.00/g' > doc/models.svg"
# PNG
sh "railroad -i -l -a -m -M | dot -Tpng > doc/models.png"
end

desc "Generate Controller diagrams."
task :controllers do
# SVG
sh "railroad -i -l -C | neato -Tsvg | sed 's/font-size:14.00/font-size:11.00/g' > doc/controllers.svg"
# PNG
sh "railroad -i -l -C | neato -Tpng > doc/controllers.png"
end
end
desc "Generate Model and Controller diagrams."
task :diagrams => %w(diagram:models diagram:controllers)
end

クラス図の生成


rake doc:diagrams

すばらしいですね。

2008/08/28

Rails で migration 時に、Column Comment を設定する

Railsで、マイグレーション作成時に、カラムにコメントを設定し、それをデータベースに設定する ColumnComments という便利なプラグインがあります。

というか、標準では設定できないんですね・・・。


インストールは次の通りです。



  1. こちらより、ZIPファイルをダウンロード

  2. 解凍して column_comments ディレクトリを vendor/plugins へコピー


使い方は、マイグレーション時に次のように記述します。


Example migration:

def self.up
create_table "users" do |t|
t.column "first_name", :string, :comment => "The member's given name."
end

column_comment "tags", "id", "The unique ID of any tag in the system."
end

そしてさらに、テーブルの情報を model と fixture にコメントとして書き込んでくれる annotate_models プラグインもバンドルされています。が、 annotate_models.rb が古いので、こちらは本家のものと置き換えて、コメントを表示するように修正します。


ついでに、 annotate_modelsにindexの情報を付加する - Hello, world! - s21g を参考に、インデックス情報もつけちゃいます。


vendor/plugins/column_comments/lib/annotate_models.rb - self.get_schema_info
  def self.get_schema_info(klass, header)
info = "# #{header}\n#\n"
info << "# Table name: #{klass.table_name}\n#\n"

# index info by http://blog.s21g.com/articles/318
indices = {}
klass.connection.indexes(klass.table_name).each do |index|
index.columns.each do |column_name|
indices[column_name] ||= []
indices[column_name] << "#{index.name}"
indices[column_name].last << "(unique)" if index.unique
end
end

max_size = klass.column_names.collect{|name| name.size}.max + 1
klass.columns.each do |col|
attrs = []
attrs << "default(#{quote(col.default)})" if col.default
attrs << "not null" unless col.null
attrs << "primary key" if col.name == klass.primary_key
if index = indices[col.name]
attrs << index.join(' ')
end

col_type = col.type.to_s
if col_type == "decimal"
col_type << "(#{col.precision}, #{col.scale})"
else
col_type << "(#{col.limit})" if col.limit
end
info << sprintf("# %-#{max_size}.#{max_size}s:%-15.15s %s", col.name, col_type, attrs.join(", ")).rstrip
info << "\n"
# column comment
unless col.comment.blank?
info << "# #{col.comment}"
info << "\n"
end
end

info << "#\n\n"
end

使い方は、次の Rake タスクを実行します。


rake annotate_models

これでもう、テーブルのカラム名を調べるためにデータベースを見る必要はなくなりそうです。

2008/08/27

Rails で Database から Fixture を抽出したい

Rails で、データベースから yaml(やむる) 形式のフィクスチャを抽出するには、ar_fixtures が有名です(手元の Ruby on Rails 逆引きクイックリファレンス Rails 2.0対応 でも紹介されています)。


ですが、実際使ってみると、日本語(UTF8)がうまく表示されなかったり、項目の並びが適当だったりと、あまりよろしくありませんでした。


そこで、データベースからテストフィクスチャを抽出する(to_yaml 不使用) - Rails で行こう! - Ruby on Rails を学ぶ で紹介されている Rake タスクを使うとばっちりうまくいきます。



ar_fixtures は内部的に to_yaml というメソッドを使っていて、これが UTF-8 の文字列をうまく扱えない。そこで、



日本語をto_yamlするとエンコードされてしまう問題を安直な方法で解決する


to_yamlでUTF-8な日本語がbinaryになってしまう問題を回避するRailsプラグイン


みたいな hack が必要になってくる。


そんなわけで、Chad Flower「Rails レシピ」のレシピ41「生データからのテストフィクスチャの抽出」(p155) のソースコードをベースに次のような Rake タスクを作ってみた。to_yaml は使ってないので、UTF-8 文字列にまつわる頭痛とも無縁だ。興味があれば、使ってみてほしい。



参考:データベースからテストフィクスチャを抽出する(to_yaml 不使用) - Rails で行こう! - Ruby on Rails を学ぶ


2008/08/26

Aptana RadRails で test_helper.rb の LoadError が発生する



Edge Rails (2.1.0) と、Aptana RadRails (1.0.3.200807071913NGT)の環境下で、ツールバー上のテストボタンでユニット・テストの実行をすると、以下のエラーが発生します。


c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require': no such file to load -- test_helper (LoadError)

コマンドでのテストは問題ないので、「rake test:units」とすればいいのですが、次のようにすれば解決できます。



There is an open issue on the Aptana issue tracker, but it doesn’t seem to be resolved yet.  Some people have suggested prefixing your requires with the test directory when requiring the test_helper file at the start of your test files -but I don’t think that’s a good solution, especially if you’re working with multiple developers.  Frustrated by not getting my daily green bar fix, I found this work-around:


  1. From the Eclipse Preferences option, choose Ruby | Installed Interpreters

  2. Select your interpreter (I use the Standard VM default interpreter named usr) and choose ‘Edit’. 

  3. Add -Itest to the Default VM Arguments option.  Don’t forget the leading dash!


  4. Click ‘OK’. 


参考:Chris Cruft » Blog Archive » Aptana RadRails and the test_helper.rb LoadError


2008/08/18

Rails で、一つのフォームで複数のモデルを扱う

先日のRails講習で、いいことを学んだので忘れないうちにメモです・・。


Rails では、基本、一つの Form に一つの Model なのですが、 fields_for というヘルパーを使用することで複数のモデルを扱えます。


一対一のモデルを一度に更新する場合などに使えそうです。




  <% form_for @person, :url => { :action => "update" } do |person_form| %>
First name: <%= person_form.text_field :first_name %>
Last name : <%= person_form.text_field :last_name %>

<% fields_for @person.permission do |permission_fields| %>

Admin? : <%= permission_fields.check_box :admin %>
<% end %>
<% end %>

参考:Rails Framework Documentation


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


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ではアカウントの削除ができないようです。

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

2008/02/24

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


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


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


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





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

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!これはいい!

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

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


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


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

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