第16回Rails勉強会
http://wiki.fdiary.net/rails/?RailsMeetingTokyo-0016
時間をおくと先月のように書かなくなってしまいそうなのでとりあえず書く。
今回はやや人が少なめだった。
RSpecについて
RSpecを使うとTest/Unitを使うことよりどんなことがうれしいかということの再確認をした。
個人的には
- とりあえずテストに入るまでの書く量が少なくて済む(専用コマンドがあるため)
- 何をテストするのかのリストを出力できる。
- 標準で色がつく
- expect, actualの順に書かなくても良い。(エラーメッセージを気にしなければ別に逆でも問題ないよねと舞波氏
- テストの切り分けがクラス単位意外でもしやすい
- context_setupとcontext_teardownがある。
- Mock機能がついている(test/unitでもFlexMockとかMochaを併用すればOK
これぐらいかなと思う。
今までは,
@stack.should_empty
のように書いてきたのだが,最新のバージョンだとobsolatedになるそうな。
@stack.should be(:empty)
と書くようになるらしい。個人的にはエディタの動的略称展開が使えるので前の方が良かった。
その代わり,trunkだとspecifyの引数の文字列を省略しておくと,ブロック内のコードからメッセージを自動生成してくれる機能がついているらしい。これは便利かなと思った。
ActiveRecordを雰囲気Drivenから脱出する
ActiveRecordのよく分からないところを実際に試して検証してみようというセッション。
主に関連づけられたモデルはいつDBに保存されるのかということについて検証してみた。
会場では./script/runnerを使って検証した。何度もデータが同じ状況で実験するためにはテストとして書いてしまった方が良いとのことなので,このログはテストコードでまとめる。*1
まずサンプルのモデルの構造から
class CreateShops < ActiveRecord::Migration def self.up create_table :shops do |t| t.column :name, :string, :null => false end end def self.down drop_table :shops end end class CreateServings < ActiveRecord::Migration def self.up create_table :servings do |t| t.column :shop_id, :interger, :null => false t.column :menu_id, :interger, :null => false t.column :created_at, :datetime, :null => false t.column :updated_at, :datetime, :null => false end end def self.down drop_table :servings end end class CreateMenus < ActiveRecord::Migration def self.up create_table :menus do |t| t.column :name, :string, :null => false t.column :price, :integer, :null => false end end def self.down drop_table :menus end end
次にモデルで関連付けの設定をする
class Menu < ActiveRecord::Base has_many :shops, :through => :servings has_many :servings end class Serving < ActiveRecord::Base belongs_to :shop belongs_to :menu end class Shop < ActiveRecord::Base has_many :menus, :through => :servings has_many :servings end
適当にfixtureを書く
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html katsudon: id: 1 name: カツ丼 price: 500 tendon: id: 2 name: 天丼 price: 600 chukadon: id: 3 name: 中華丼 price: 450 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html one: id: 1 shop_id: 1 menu_id: 1 two: id: 2 shop_id: 1 menu_id: 1 three: id: 3 shop_id: 1 menu_id: 1 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html washoku: id: 1 name: 和食屋 youshoku: id: 2 name: 洋食屋
ShopにMenuを追加してみる
require File.dirname(__FILE__) + '/../test_helper' class ShopTest < Test::Unit::TestCase fixtures :shops # Replace this with your real tests. def setup @shop = Shop.find(2) end def test_add_menu_failed assert_raise ActiveRecord::HasManyThroughCantAssociateNewRecords do menu = Menu.new :name => "シチュー", :price => 500 @shop.menus << menu # servingsが存在していないため例外発生 end end def test_add_menu_success menu = Menu.new :name => "シチュー", :price => 500 menu.save! @shop.menus << menu # @shop.updateをかけなくてもservingsは保存される assert(Menu.find_by_name("シチュー")) assert(Serving.find_by_menu_id(Menu.find_by_name("シチュー").id)) end end
Belongs_to側の場合
require File.dirname(__FILE__) + '/../test_helper' class ServingTest < Test::Unit::TestCase fixtures :menus fixtures :servings # Replace this with your real tests. def setup @servings = Serving.find(1) end # セーブする前にupdateをかけてしまうとどうなるか def test_add_add_and_update_before_save_menu assert_not_nil(@servings.menu) @servings.shop = Shop.find_by_name "洋食屋" @servings.menu = Menu.new :name => "シチュー", :price => 500 assert_nil(Menu.find_by_name("シチュー")) @servings.update # 例外は起こらない? assert_nil(Menu.find_by_name("シチュー")) assert_equal("シチュー", @servings.menu.name) # 新しいデータも残ったまま assert_equal("洋食屋", @servings.shop.name) # 新しいデータも残ったまま invalid_serving = Serving.find(1) assert_equal("カツ丼", invalid_serving.menu.name) # まだ更新されてない assert_equal("洋食屋", invalid_serving.shop.name) # Shopは更新される!! @servings.menu.save! @servings.update valid_serving = Serving.find(1) assert(Menu.find_by_name("シチュー")) assert_equal("カツ丼", valid_serving.menu.name) # 一度updateをかけてしまうとダメ end end
あとはcreateとnewの違いとか,そこら辺を確認した。
ちなみにTransactionが有効かどうかのテストを書くときはテストDBをMySQLのInnoDBなどにしなければならない模様。
あとActiveRecordからSQLiteでBlobを保存すると画像が壊れると聞いたのでそれの実験もした。それに関しては,つかもとさんがパッチを作ってくれた模様。GJ!
それにしても今実験するのにRSpecやっぱり使わなかった。。。やっぱり標準でサポートされてることは大きい。
*1:記憶を便りに書いているので会場でやったときとは例題も違う点に注意
Ruby版WSGI: Rack
まだ触ってないですが,これは良さそう。
標準の日付の表示形式を設定する
色々落ち着いてるので久々にRailsを触る
標準の表示だと英語圏に沿った表示になってるので変更したいなと探してみたら案の定あった。
# environments.rb ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!({ :default => "%Y年%m月%d日 %H:%M", })
あと日付選択も月名が英語になっているのを毎回設定するのはめんどいのでこうした。application_helper.rbでやるよりもconfig/environments.rbでやる方がふつうなのかな。
# app/helpers/application_helper.rb module ApplicationHelper include ::ActionView::Helpers::DateHelper alias default_date_select date_select def date_select object_name, method, options = {} options[:use_month_numbers] ||= true default_date_select(object_name, method, options) end alias default_datetime_select datetime_select def datetime_select object_name, method, options = {} options[:use_month_numbers] ||= true default_datetime_select(object_name, method, options) end end
Ruby忘年会
一次会
50名参加ということで1テーブルあたり約10人とやや狭かった。自分たちのテーブルは一番奥ということもあってかやや広めだった。
一次会は自分のテーブルはささださんとコーディングスタイルの話で少し盛り上がった。
前にささださんに会ったときはキモいといって否定していたメソッド定義のときの括弧の省略は最近自分も使うようになった。
呼び出しのときの括弧の省略は結構ややこしくて後から変更すると括弧をつけなきゃいけなくなったりして最近はあきらめて最初から書くことも多いのだけど、メソッド定義時であればだいたいのケースで大丈夫なので括弧は省略してしまう。
また、レシーバが省略できるメソッドで引数のない呼び出しの場合()をつけるようにしている。これはローカル変数とメソッドかぱっと見たときに判断がつかないからなのだけど、selfをつけるのが一番見栄えが良いのだけどprivateなメソッドの場合selfをつけることができなくなったので()をつけるということにしている。
def hoge a, b, c=nil # 括弧を付けなくても問題なし # ... end hoge "foo", "bar" # pでデバッグするときなどでどうせ括弧を付けなければいけなくなる def foo # ... end foo = "fuga" foo() # レシーバがなくて引数のないメソッドはローカル変数と見分けがつけにくいときがある
それから、ささださんは捨て変数としてよく_を使うのだそうだ。さすがにこれには同感できなかった。
[1, 2, 3].each {|_| p _ }
二次会
二次会は20名ちょいくらいだったと思う。
高橋さんによればPHPUnitはメソッド名に日本語が使えるのが良いのだそうだ。高橋さん的にはRSpecもshould_beとかではなくて「は」などのメソッドにしてほしいらしい。
$KCODEをUTF-8にすれば日本語が使えるとかなんとかを角谷さんがテストしていたが結果どうなったかは少し覚えていない。リテラルの扱いを変更する際はコードで$KCODEを変更するのではなく、オプションで変更しなきゃいけないというのが少しめんどくさい。
最後の方でid:ogijunさんが駆けつけた。[あとで払う]にみんな爆笑していた。
来年はもうちょっとRubyでライブラリ作ったりアプリ書いたりして参加しないとなぁと思った。
Rails勉強会@東京第13回
忘年会で最近更新があまりないと言われてしまったのでちゃんとログぐらいは書くことにします。
Rails勉強会@東京第13回に参加してきました。
AWDwR 2ndを読む
前半はかわむらさんのAgile Web Development with Rails 2nd Editionをみんなで読みながら具体的にはどの辺りが変わったのかを比べたりしました。記憶を便りに比較したので必ずしも正しくない点はご了承ください。
- まだ出ていないRails v1.2に準拠
- Depotの作成ではRJSテンプレートを利用したAjaxによるカートの作成が加わっているようでした。
- ActiveRecoardのRelationのところがだいぶ書き変わっている
- habtmは一応載っている
- has_many :throughとhabtmの違いは外部キー以外にカラムを持つかどうかで分けているのではないか
- ActionPackがActiveControllerとActionViewの二つの章に分けられた
- ActiveController
- RESTの説明にやたらとページが割かれている
- ActiveController
とかが気になった点でした。
RSpec
前回参加してなかったし、セッションがあまり埋まってなかったのでid:moroさんに頼んでRSpecをもう一度やってもらいました。
「RSpecとTest::Unitの何が違うのか?順番が入れ替わっただけじゃないか」という人もいるのだけれど、自分的にはそれだけで全然読みやすさも書きやすさも違うのでRSpecを使う理由になると思う。それ以外のメリットとしては、ContextごとにSpecificationをリストできるというところ。でもこれは自分ののところだと何故か出力できてなかったりする。Test::Unitだとリテラルであるところが文字列なので日本語を書けるという点もメソッド名に悩まなくなったりしてメリットなのだそうだ。
ちなみにRSpecは0.7から大幅に仕様が変わってそれ以前のものは0.7以降では動かなくなっていたりする。ということで個人的にはRSpecのファイルには
require 'rubygems' require_gem 'rspec', '> 0.7'
を他の人も入れてほしいのだけど、角谷さんによるとtrunkはまだまだ仕様変更しそうな感じであるしRSpecを使う利点は専用のコマンドがあることでテストに対するいらない障壁がなくなることにもあるので余計なものは書かないという方針らしい。
RSpecを使う難点と言えば、ライブラリを使っていた際にTest::Unitであれば色々な拡張されたAssersionがライブラリで提供されている場合もあるのだけど、RSpecのメソッドまで用意されていることは少ないというところがある。RSpecの知名度が上がっていけば解決される問題なのかなと思ったり。
gettext-scaffoldのパッチ
初期状態ではshow、confirm_create、confirm_update、confirm_deleteのページのカラムがローカル化するようにされてないのでそれをローカル化するパッチを書きました。(もしかするとローカル化するようにされていないのは何か罠があるからかもしれませんが。
# === gettext_scaffold_generator.rb # ================================================================== # --- gettext_scaffold_generator.rb (revision 109) # +++ gettext_scaffold_generator.rb (local) # @@ -23,7 +23,7 @@ elsif column_type == :date or column_type == :datetime column_modifier = '.to_s(:long)' end - "<dt><%=h '#{column.human_name}'%></dt><dd><%=h(@#{record}.#{column.name}#{column_modifier})#{column_escaped_modifier} %></dd>" } + "<dt><%=h _('#{record.classify}|#{column.human_name}') %></dt><dd><%=h(@#{record}.#{column.name}#{column_modifier})#{column_escaped_modifier} %></dd>" } end end # === templates/view_list.rhtml # ================================================================== # --- templates/view_list.rhtml (revision 109) # +++ templates/view_list.rhtml (local) # @@ -23,4 +23,4 @@ <%%= link_to _('Next page'), { :page => @<%= singular_name %>_pages.current.next } if @<%= singular_name %>_pages.current.next %> <br /> -<%%= link_to _('New %{name}') % {:name => '<%= singular_name%>'}, :action => 'new<%= suffix %>' %> +<%%= link_to _('New %{name}') % {:name => _('<%= singular_name%>') }, :action => 'new<%= suffix %>' %>
rdefsをRails仕様に拡張してみる
最近はRubygemsのRSSを眺めて面白そうだなと思ったらインストールして軽く使い方とソースを眺めるというのがマイブームだったりするのですが、ソースを眺めるときに青木さんのrdefsは便利だなぁと思いました。
ただRailsとかだとcattr_readerとか色々拡張されているのでそれっぽいのをActiveSupportから調べてみました。(既に誰かやってそうな気もするけど。
--- bin/rdefs_orig 2006-09-12 23:29:24.000000000 +0900 +++ bin/rdefs 2006-09-10 16:55:17.000000000 +0900 @@ -7,10 +7,25 @@ DEF_RE = /\A\s*(?: def\s | class\s | module\s | include[\s\(] | alias(?:_\w+)? + | alias_method(?:_\w+)? | attr_reader[\s\(] | attr_writer[\s\(] | attr_accessor[\s\(] | attr[\s\(] + | cattr_reader[\s\(] + | cattr_writer[\s\(] + | cattr_accessor[\s\(] + | mattr_reader[\s\(] + | mattr_writer[\s\(] + | mattr_accessor[\s\(] + | class_inheritable_reader[\s\(] + | class_inheritable_writer[\s\(] + | class_inheritable_array_writer[\s\(] + | class_inheritable_hash_writer[\s\(] + | class_inheritable_accessor[\s\(] + | class_inheritable_array[\s\(] + | class_inheritable_hash[\s\(] + | reset_inheritable_attributes | public[\s\(] | private[\s\(] | protected[\s\(]
何か抜けとか他にもこういうのも加えとくと良さそうとかあればコメントとかください。
cattr_readerなどは同様のことをしたいときのデフォルトになり得る気もするので、ActiveSupportの中身を再解体してCPANっぽくclass-attribute_accessorsとかclass-inheritable_attributesとかに分けて欲しいなと思いました。
さすがに
require 'rubygems' require 'active_support/core_ext/class/attribute_accessors'
はめんどいと思ってしまう。(と思いつつ今打ってみたら案外めんどくさくなかった
まぁRuby使ってる人はたいていActiveSuportをインストールしてるのでActiveSupportに依存した方が可搬性は高いと思いますが。