Archive for 1月, 2010

ActiveRecord::Base#find(_by_id)? のお話

最近Ruby on Railsばかりなので、今回もRailsのお話。

何も考えずに特定のIDのレコードを見つける際は、「ActiveRecord::Base#find」という超基本メソッドを使うけれど、同時に、XXXというフィールドに対して「ActiveRecord::Base#find_by_XXX」なるメソッドも定義されているので、「ActiveRecord::Base#find_by_id」でも同じようにidに紐付けて見つけ出せる。

以上は当たり前の話だが、この同じような2つ、重要なところが違う。すなわち、そのIDに紐付くレコードが存在しない場合。

ActiveRecord::Base#find」は例外が生じる。

1
2
3
4
5
6
7
Post.find(1)
#=> ActiveRecord::RecordNotFound: Couldn't find Post with ID=1
#	from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1586:in `find_one'
#	from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1569:in `find_from_ids'
#	from /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:616:in `find'
#	from (irb):6
#	from :0

ActiveRecord::Base#find_by_id」は nil を返す。

1
2
Post.find_by_id(1)
#=> nil

このお話はそれなりに有名なようですが、find_by_idを使えば、例えばこういうコードが書けて気分がいいですね。

Read the rest of this entry »

 

Railsのdb:migrateは、最新バージョンより古くてもさかのぼる

どういうことかと言うと、

rails(1) でつくる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[udzura@udzra.jp rails]$ rails -v
Rails 2.3.5
[udzura@udzra.jp rails]$ rails -d mysql migtest
      create
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      create  config/environments
...
      create  log/server.log
      create  log/production.log
      create  log/development.log
      create  log/test.log

マイグレーションを作る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[udzura@udzra.jp rails]$ cd migtest/
[udzura@udzra.jp migtest]$ script/generate model User name:string
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/user.rb
      create  test/unit/user_test.rb
      create  test/fixtures/users.yml
      create  db/migrate
      create  db/migrate/20100122041512_create_users.rb
[udzura@udzra.jp migtest]$ script/generate model Blog user_id:integer entry:text
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/blog.rb
      create  test/unit/blog_test.rb
      create  test/fixtures/blogs.yml
      exists  db/migrate
      create  db/migrate/20100122041555_create_blogs.rb

2099年とかすっごい未来にリネームする。

1
2
3
4
5
6
[udzura@udzra.jp migtest]$ ls -l db/migrate/
合計 8
-rw-rw-r--  1 udzura udzura 193  122 13:15 20100122041512_create_users.rb
-rw-rw-r--  1 udzura udzura 217  122 13:15 20100122041555_create_blogs.rb
[udzura@udzra.jp migtest]$ mv db/migrate/20100122041555_create_blogs.rb \
  db/migrate/20990122041555_create_blogs.rb

マイグレートする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[udzura@udzra.jp migtest]$ rake db:create
(in /var/web-devel/rails/migtest)
[udzura@udzra.jp migtest]$ rake db:migrate
(in /var/web-devel/rails/migtest)
==  CreateUsers: migrating ====================================================
-- create_table(:users)
   -> 0.0038s
==  CreateUsers: migrated (0.0041s) ===========================================
 
==  CreateBlogs: migrating ====================================================
-- create_table(:blogs)
   -> 0.0032s
==  CreateBlogs: migrated (0.0035s) ===========================================
 
[udzura@udzra.jp migtest]$ mysql -uroot -pxxxx -e "SHOW TABLES" migtest_development
+-------------------------------+
| Tables_in_migtest_development |
+-------------------------------+
| blogs                         |
| schema_migrations             |
| users                         |
+-------------------------------+
[udzura@udzra.jp migtest]$ rake db:version
(in /var/web-devel/rails/migtest)
Current version: 20990122041555

マイグレーションを加える。当然、さっき作った二つのマイグレーションの間に入ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[udzura@udzra.jp migtest]$ script/generate model UserFriend user_id:integer friend_user_id:integer
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/user_friend.rb
      create  test/unit/user_friend_test.rb
      create  test/fixtures/user_friends.yml
      exists  db/migrate
      create  db/migrate/20100122041954_create_user_friends.rb
[udzura@udzra.jp migtest]$ ls -l db/migrate/
合計 12
-rw-rw-r--  1 udzura udzura 193  122 13:15 20100122041512_create_users.rb
-rw-rw-r--  1 udzura udzura 249  122 13:19 20100122041954_create_user_friends.rb
-rw-rw-r--  1 udzura udzura 217  122 13:15 20990122041555_create_blogs.rb

こういう場合でもさかのぼってマイグレーションを実行してくれます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[udzura@udzra.jp migtest]$ rake db:migrate
(in /var/web-devel/rails/migtest)
==  CreateUserFriends: migrating ==============================================
-- create_table(:user_friends)
   -> 0.0067s
==  CreateUserFriends: migrated (0.0071s) =====================================
 
[udzura@udzra.jp migtest]$ mysql -uroot -pxxxx -e "SHOW TABLES" migtest_development
+-------------------------------+
| Tables_in_migtest_development |
+-------------------------------+
| blogs                         |
| schema_migrations             |
| user_friends                  |
| users                         |
+-------------------------------+

何が嬉しいの?

例えば、実表のマイグレーションを通常の作成時の日次で、ビュー表を作成するマイグレーションを「2099年」の日次で、という風にしておくと、「必ずすべての実表が作られてからビュー表が定義される」ことが保障され、なおかつ、ビュー表の定義を変更した際、作り直しが rake(1) で容易にできます。

ポイントは rake db:migrate:down VERSION=2099XXXXXXXXXX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[udzura@udzra.jp migtest]$ rake db:migrate:down VERSION=20990122041555
(in /var/web-devel/rails/migtest)
==  CreateViewBlogs: reverting ====================================================
-- drop_table(:view_blogs)
   -> 0.0277s
==  CreateViewBlogs: reverted (0.0280s) ===========================================
 
[udzura@udzra.jp migtest]$ rake db:migrate
(in /var/web-devel/rails/migtest)
==  CreateViewBlogs: migrating ====================================================
-- create_table(:view_blogs)
   -> 0.0472s
==  CreateViewBlogs: migrated (0.0475s) ===========================================
 
[udzura@udzra.jp migtest]$

RailsでCreate Viewしたい際は、普通に execute しないといけません。なので、そもそも「RailsでView表使うとかあほか」って方もいると思う。引き出しの一つとして。

 

開いたWindowの存在を検出

友達が困ってるので書いた。

ポップアップでウィンドウを開く時、二重でオープンしたり、開いてるウィンドウを間違えてリフレッシュしたりしないためにはどうするかということだと思う。

要するに普通にwindow.openで開けばいいのだが、このメソッドの返り値は開いた先のwindowなので、変数に閉じ込めればよい。そうすれば「closed」というプロパティで開いてるか閉じてるかが検出可能。

1
2
3
4
5
6
var myWin = window.open('http://blog.udzura.jp', 'mywindow1');
alert(myWin.closed);
//=> false
//開いたウィンドウを閉じれば
alert(myWin.closed);
//=> true

実際はグローバル変数とか微妙なので、せめてクロージャを使うとスマートだと思う。

やっつけなりに実装してみたソースは以下の如し。サンプルサイトもあるよ。

Read the rest of this entry »

 

僕の考えたカリー化

カリー化とは

複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること。

http://wiki.onakasuita.org/pukiwiki/?%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96

http://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96
など参考

最近は「関数が第一級のオブジェクト」、要は「手続きの固まり」を文字列や数字のようにモノ扱いできる、RubyやJavaScriptのような言語が普及しているが、そういう言語では「カリー化」の手法が使える。

例えばRubyで、x個の、yから始まる数の総和を求めるorigという関数を考える。

1
2
3
4
5
6
7
def orig(x, y)
  ret = 0
  for i in y..(x + y - 1) do
    ret += i
  end
  return ret
end

(※)

このorig(x, y)を元に、「3個」「4個」「5個」の、yから始まる数の総和を求める関数を作りたい。下の如し。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def curried_orig(x)
  lambda do |y|
    orig(x, y)
  end
end
 
sum3 = curried_orig(3)
#=> #<Proc:0x0000002a955cf098>
sum4 = curried_orig(4)
#=> #<Proc:0x0000002a955cf098>
sum5 = curried_orig(5)
#=> #<Proc:0x0000002a955cf098>
sum3[10]
#=> 10+11+12 = 33
sum4[10]
#=> 10+11+12+13 = 46
sum5[10]
#=> 10+11+12+13+14 = 60

sum3やsum4に束縛されるのは、実際にはメソッドというよりProcオブジェクトなので、()ではなく[]で呼び出す。

Ruby1.9.1からは「Proc#curry」が導入され、よりすっきり書ける。

1
2
3
4
5
6
7
8
9
10
sum_x = lambda {|x, y|
  ret = 0
  for i in x...(x + y) do
    ret += i
  end
  return ret
}
sum3 = sum_x.curry[3]
sum3[10]
#=> 10+11+12 = 33

数学的な概念は良く分かっていないけど。

http://www.nslabs.jp/closure.rhtml

「カリー化」とは (A, B) -> C という関数をA -> (B -> C) という関数に変換することです。

http://www.khelll.com/blog/ruby/ruby-currying/

Check this function f which takes 3 params x,y,z

f(x,y,z) = 4*x+3*y+2*z

Currying means that we can rewrite the function as a composition of 3 functions(a function for each param):

f(x)(y)(z) = 2*z+(3*y+(4*x))

http://www.ruby-forum.com/topic/142699#633354

It’s not difficult at all,

1
  proc {|x, y, z| x + y + z }.curry

returns the proc object equivalent to

1
  proc {|x| proc {|y| proc {|z| x + y + z } } }

実際利用する時に「関数の部分適用」と「カリー化」を区別する意味ってあまり無いかもしれないんだけど。「Proc#curry」はこういう意味ですよ、というのは認識したほうがいいのかも。

その他のカリー化の使い方例 参考:「Fun with Procs in Ruby 1.9」
http://pragdave.blogs.pragprog.com/pragdave/2008/09/fun-with-procs.html

* * *

※: 個人的に普段はこう書いてしまい俺気持ちが悪い。

1
2
3
def orig(x, y)
  (0...x).to_a.map{|v| y + v}.inject{|d, s| d += s}
end

 

Gitorious入れたメモ

日ごろgithubさんにお世話になっておりますが、Gitoriousというサービスは自分のコードを公開し、個人でインストールできるようなので入れてみたメモ。

概要

ホストするOSはUbuntu 9.10。

手順は、9.04の場合のものが公式Wikiにあったりする(http://gitorious.org/gitorious/pages/UbuntuInstallation)のでそれを大幅に参照した。基本的にコレの通りで問題ないのだが、Gitorious自体のバージョンが上がったこともあって微妙に違う点もある。

以下、日本語Remix CDを入れたてのまっさらな状態からの構築を想定。

Read the rest of this entry »