忍者ブログ

ぢみへんプログラミング日誌

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

ActiveRecord::Base.transaction はネストしたトランザクションに対応しているのか確認した。

細かい説明は抜きにして、以下のirb (rails console -s で起動するコンソールモード)での実行内容を見てみよう。
まずはUserというモデルをsaveメソッド単体で保存してみる。

[root@localhost base]# rails console -s
Loading development environment in sandbox (Rails 4.0.0.beta1)
Any modifications you make will be rolled back on exit
irb(main):001:0> a = User.new
=> #
irb(main):002:0> a.user_no = "123"
=> "123"
irb(main):003:0> a.user_name = "aiueo"
=> "aiueo"
irb(main):004:0> a.save
(0.3ms) BEGIN
User Exists (1.6ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '123' LIMIT 1
SQL (13.1ms) INSERT INTO "users" ("updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [ ["created_at", Wed, 13 Mar 2013 15:10:12 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 15:10:12 UTC +00:00],["user_name", "aiueo"], ["user_no", "123"]]
(3.3ms) COMMIT
=> true

見ての通り、saveメソッドは単体でトランザクションも起動している。

では今度はトランザクションをネストさせてsaveを使ってみる。

irb(main):061:0> ActiveRecord::Base.transaction do
irb(main):062:1> a = User.new
irb(main):063:1> a.user_no = "234"
irb(main):064:1> a.user_name = "aiueo"
irb(main):065:1> a.user_category_id = 1
irb(main):066:1> a.user_qualification_id = 1
irb(main):067:1> a.card_registered_date = "20130313"
irb(main):068:1> a.library_id = 1
irb(main):069:1> a.save
irb(main):070:1> end
(0.3ms) BEGIN
User Exists (1.2ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '234' LIMIT 1
SQL (2.1ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 15:41:55 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 15:41:55 UTC +00:00], ["user_name", "aiueo"], ["user_no", "234"]]
(3.0ms) COMMIT
=> true

saveメソッドの内部にもトランザクション処理が起きて、いわゆるトランザクションのネストが発生するかと思ったが、SQLレベルでBeginが2回生成されていないこと、警告(筆者の環境ではDBがPostgreSQLなのでBEGINが2回続くと警告が出る)が出ないところから見ると、saveメソッド内でトランザクションを開始してはいないようだ。

では明示的にトランザクションをネストさせたらどうなるだろうか

irb(main):072:0> ActiveRecord::Base.transaction do
irb(main):073:1* a = User.new
irb(main):074:1> a.user_no = "345"
irb(main):075:1> a.user_name = "aiueo"
irb(main):080:1> a.save
irb(main):081:1>
irb(main):082:1* ActiveRecord::Base.transaction do
irb(main):083:2* b = User.new
irb(main):084:2> b.user_no = "456"
irb(main):085:2> b.user_name = "aiueo"
irb(main):090:2> b.save
irb(main):091:2> end
irb(main):092:1> end
(0.3ms) BEGIN
User Exists (0.9ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '345' LIMIT 1
SQL (2.9ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00],["updated_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00], ["user_name", "aiueo"], ["user_no", "345"]]
User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '456' LIMIT 1
SQL (1.3ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00],["user_name", "aiueo"], ["user_no", "456"]]
(3.5ms) COMMIT
=> true

ご覧のとおり、トランザクションはネストしない。

このことはメソッドを介しても全く同様。

irb(main):094:0> def test
irb(main):095:1> ActiveRecord::Base.transaction do
irb(main):096:2* a = User.new
irb(main):097:2> a.user_no = "789"
irb(main):098:2> a.user_name = "aiueo"
irb(main):103:2> a.save
irb(main):104:2> end
irb(main):105:1> end
=> nil
irb(main):106:0> ActiveRecord::Base.transaction do
irb(main):107:1* a = User.new
irb(main):108:1> a.user_no = "890"
irb(main):109:1> a.user_name = "aiueo"
irb(main):114:1> a.save
irb(main):115:1>
irb(main):116:1* test
irb(main):117:1> end
(0.4ms) BEGIN
User Exists (0.5ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '890' LIMIT 1
SQL (0.5ms) INSERT INTO "users" "created_at","updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00],["user_name", "aiueo"], ["user_no", "890"]]
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '789' LIMIT 1
SQL (0.3ms) INSERT INTO "users" "created_at", "updated_at","user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00],["user_name", "aiueo"], ["user_no", "789"]]
(1.1ms) COMMIT
=> true

このように、ActiveRecord::Base.transactionを明示して行うトランザクション処理ではネストが発生しない。

そのためロールバックも非常に明確となる。
ActiveRecord::Base.transaction のブロック中に、ActiveRecord::Base.transactionブロックをネストしても見た目とは反対に実際にはトランザクションがネストしないので、一番ネストの深いブロックで例外が発生しても、全体としてはBEGIN => Rollback の制御が1回起きるだけだ。
以下が論より証拠である。

irb(main):001:0> ActiveRecord::Base.transaction do
irb(main):002:1* a = User.new
irb(main):003:1> a.user_no = "901"
irb(main):004:1> a.user_name = "aiueo"
irb(main):009:1> a.save
irb(main):010:1>
irb(main):011:1* ActiveRecord::Base.transaction do
irb(main):012:2* b = User.new
irb(main):013:2> b.user_no = "012"
irb(main):014:2> b.user_name = "aiueo"
irb(main):019:2> b.save
irb(main):020:2> raise "Exception occured."
irb(main):021:2> end
irb(main):022:1> end
(0.1ms) BEGIN
User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '901' LIMIT 1
SQL (5.5ms) INSERT INTO "users" ("created_at", "updated_at","user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00],["user_name", "aiueo"], ["user_no", "901"]]
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '012' LIMIT 1
SQL (0.3ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00],["updated_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00],["user_name", "aiueo"], ["user_no", "012"]]
(0.8ms) ROLLBACK
RuntimeError: Exception occured.

上記通りにActiveRecord::Base.transactionをに使う限りにおいては、コード上でトランザクションブロックをいくらネストさせても、どこかで例外が発生した時点で全ての処理がロールバックされ、セーブポイント等は考慮されない。
セーブポイントが重要という場合は困るだろうが、トランザクションの一意性を考えるとロールバックと同時にトランザクション内全てのレコード処理が自動で無効になってくれたほうが親切だと言えるだろう。これで心置きなくトランザクションを安心して使えるというわけだ。
PR

Rakeで実行できるRails環境でのテストタスク

Railsでの単体テストの実行にはrakeコマンドを多用しなければならないが、このコマンドで実行できる作業(以下「タスク」と呼ぶ)は数多いため、いくつかポイントになりそうな情報をここに書き留めておく。

1) テスト用DBスキーマ構築時のrake オプション
rake db:test:clone
現在使用中環境のDBスキーマよりtest環境のDBを構築
rake db:test:clone_structure
development 環境のDBスキーマを使ってtest環境のDBを構築
rake db:test:load
schema.rb ファイルを基にtest環境のDBを構築

rake db:test:prepare
未反映のマイグレーションがないことを確認してから db:test:load実行
rake db:test:purge
test環境のDBを空にする
尚、テスト用データの作成は、test/fixture ディレクトリにてyamlファイルを編集することで行う。

2) テストの実行
rake test
定義済みの全Unit, Functional,Integrationテストを実施.
rake test:controllers
`test/controllers`に配置されたcontrollerの機能テストを実施
rake test:functionals
`test/controllers`, `test/mailers`および`test/functional`に配置されたテストユニットを全実施
rake test:helpers
`test/helpers`に配置されたヘルパークラスのテストを実施
rake test:integration
`test/integration`に配置されたインテグレーションテストを実施
rake test:mailers
`test/mailers`に配置されたメール機能テストを実施
rake test:models
`test/models`に配置されたモデルの機能テストを実施
rake test:recent
最近変更のみテスト?(詳細未確認)
rake test:uncommitted
SubversionやGitでコミットされていないテストを実施?(詳細未確認)
rake test:units
`test/models`, `test/helpers`,`test/unit`に配置されたテストユニットを全実施

これを見て分かるように、test/unit という独自ディレクトリに、MVCモジュールと関係のないテストユニットを配置できる。


詳細は以下のファイルを参照(rails4 -- 2012.2.24時点)

(Rubyのインストールされたディレクトリ)/ruby/gems/x.x.x/gems/rails-x.x.x/guides/source/testing.md

基本的にこの内容を理解できればRails上でのRake操作はほぼ把握できると思われる。

Ruby gemパッケージには(no-docインストールしなければ)それぞれドキュメントが添付されており、それを参照することでパッケージの利用方法を得ることができるので、そちらを参照することで知りたい情報を知ることができる可能性もある。
(gem server とコマンド入力することでブラウザでドキュメント参照できる)==>便利!

Enumerator がなんで必要なのかようやく分かった

RubyにはEnumeratorというEnumerableモジュールのラッパークラスと呼ばれるクラスがあるのだが、これはeachメソッドを実装してないクラスにもEnumerableモジュールの機能を与えるためのインターフェイスクラスとして存在しているということを今日初めて理解した。


class Abc
def initialize
@arr = []
end

def set_num(num)
@arr << num
end

def my_each
@arr.each do |num|
yield num
end
end
end

abc = Abc.new

abc.set_num 100
abc.set_num 200
abc.set_num 300
abc.set_num 1000
abc.set_num 10000

abc.my_each { |i| p i}

enum = Enumerator.new(abc, :my_each)

m = enum.map{|i| i*i}

p m


このコードの実行結果は以下の通りとなる。

> ruby enumerator_experiment.rb
100
200
300
1000
10000
[10000, 40000, 90000, 1000000, 100000000]


見ての通り、クラスAbcはEnumerableをincludeしていないのでEnumerableで定義されているmapやsellect等のメソッドをAbcは使えない。また仮にEnumerableをincludeしていたとしても、Abcにはeachメソッドがないから機能しない。こういうクラスにEnumerableのインターフェイスを与えてやるにはどうしたらいいのか。

そこでEnumeratorの出番というわけだ。クラス独自のイテレータメソッドを持っていれば、それをeachの代わりとみなしてEnumerableの各メソッドを実行させてしまおうという意図で使うことができる。EnumeratorクラスのメソッドインターフェイスはEnumerable互換であると考えてよい。
上記コード例で行くと、Abc#my_eachは自作のeachメソッドで内部実装自体にはArray#eachを使っている。Enumerator::new でこのメソッドを指定すると生成されたEnumeratorオブジェクトの各メソッドは、Enumerableモジュールの各メソッドがinclude元クラスのeachメソッドを基盤に動作するのと同じように、Abc#my_eachの実装を基にmapやsellectメソッドを構築する。


さて、Ruby1.9からはeachメソッドにブロックが続かなかったときは、eachメソッドの戻り値がEnumeratorとなる。そのため次のようなコードが書ける。


irb(main):003:0> [1,2,5,100,10].each
=> #
irb(main):004:0> [1,2,5,100,10].each.zip([2,4,6,8,10])
=> [[1, 2], [2, 4], [5, 6], [100, 8], [10, 10]]


これと同じ挙動を先のmy_eachに加えられるだろうか?
block_given? メソッドを使ってブロック判定を行い、ブロックがなければEnumeratorば可能だ。


class Abc
def initialize
@arr = []
end

def set_num(num)
@arr << num
end

def my_each
if block_given?
@arr.each do |num|
yield num
end
else
Enumerator.new(self, :my_each)
end
end
end

abc = Abc.new

abc.set_num 100
abc.set_num 200
abc.set_num 300
abc.set_num 1000
abc.set_num 10000

abc.my_each { |i| p i}

enum = Enumerator.new(abc, :my_each)

m = enum.map{|i| i*i}

p m

m = abc.my_each.map{|i| i*2}

p m


実行結果:

> ruby enumerator_experiment.rb
100
200
300
1000
10000
[10000, 40000, 90000, 1000000, 100000000]
[200, 400, 600, 2000, 20000]

Rubyにおける多重代入について

Rubyの多重代入とは基本的にこういうものを指す。

a, b = *[1, 2] # a = 1, b = 2
*c = 1, 2, 3 # c = [1, 2, 3]

このように * という記号を付けることで、代入式の右辺の配列を分解して左辺に渡したり、右辺の任意数の要素を配列としてまとめて左辺に代入できる。(こういう働きをするときの * をsplat演算子とも呼ぶこともあるそうだが、実際には本物の演算子ではないと「プログラミングRuby」には書いてある。)この性質を生かして、メソッドの定義で可変引数を実現可能だ。

irb(main):001:0> def func( *s )
irb(main):002:1> s
irb(main):003:1> end
=> nil
irb(main):005:0> func(1,2,3)
=> [1, 2, 3]

見ての通り、可変引数は配列として引数に渡される。

ところが話はここで終わらない。多重代入による可変引数を使うことには意外な(ある意味便利な)副作用があるからだ。以下は上記コードのfuncメソッドを引数なしで実行した例である。

irb(main):004:0> func
=> []

なんと! 引数がないから例外発生かと思いきや、空の配列として引数を受け取っているではないか。ということは多重代入引数は同時に空配列のデフォルト引数でもあるわけだ。

多重代入に関する注意点がもう一つ。単なる代入式での多重代入とメソッドの引数で扱う多重代入では挙動が異なっていることに注意しなければならない。

メソッドの引数ではなく、左辺に * をつけての単なる多重代入の場合、右辺要素が1つしかないときは、左辺の変数に配列が代入されるのではなく、単に右辺のオブジェクトがそのまま代入される。

irb(main):021:0> *a = 1
=> 1
irb(main):022:0> *a = 1, 2
=> [1, 2]

しかしメソッド引数としての多重代入の動作では、呼び出し側で用意した引数が1個のみだとしても、メソッド側では必ず配列として扱われる。

irb(main):001:0> def func( *s )
irb(main):002:1> s
irb(main):003:1> end
=> nil
irb(main):023:0> func
=> []
irb(main):024:0> func 1
=> [1]
irb(main):025:0> func 1, 2
=> [1, 2]


この挙動の違いが将来なくなるかどうかは分からないが、現状では注意するべきだろう。

ノートパソコン(Win7)でタッチパッドを無効にする方法

いやー、結構腹立たしかったんだよね、キーボード入力していると勝手にマウスカーソルがおかしなところに瞬間移動していたりするときって。

特に外付けマウスをつなげて操作すると、マウス操作でキーボードから手が離れる回数が自然と多くなるんだが、離れた指をキーボード上のホームポジション(FとJのキーに人差し指が置かれた状態のこと)に戻そうとするときに手の平の一部がタッチパッドに触れるか近づくかすると、あっという間にマウスカーソルが関係ない場所に移動してしまうんだこれが。

そんなことがあまりにも何度も連続したので嫌気がさして調べてみたら、やっぱり同じ悩みの人はたくさんいたのねぇ~……。先人に解決方法を教わっちゃいました。

参考URL http://okwave.jp/qa/q5875661.html

コントロールパネルから「ハードウェアとサウンド」を選択し、それから「デバイスとプリンタ」と分類されたカテゴリ内に「マウス」と書かれたメニューがあるはずなので、それを選択すると「マウスのプロパティ」というウィンドウが現れる。
このウィンドウ内の画面表示はPCメーカー各社で異なっているので一般的なことは分からないが、この画面からタッチパッドの無効化ができるようになっているはず。

ちなみにDELLのPC(VOSTRO 3550)では、「マウスのプロパティ」ウィンドウの「Dell Touchpad」タブに「クリックしてDellタッチパッドの設定を変更します」と書かれたリンクが表示されており、それをクリックすると「Dell TouchPad」というタイトルの別ウィンドウが新たに立ち上がる。そこの「デバイスの選択」と書かれたメニューを選択すると、「外付けのUSBマウスが接続されている場合、タッチパッドおよびポインティングスティックを無効にします」と書かれたチェックボックスがある。これにチェックを入れて「OK」ボタンを押せば設定完了だ。

実際、タッチパッドを無効化できたおかげで余計な苛立ちを感じることもなくなり、いい感じでPCを操作できるようになった。
  

カレンダー

04 2024/05 06
S M T W T F S
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
26 27 28 29 30 31

フリーエリア

最新CM

バーコード

ブログ内検索

Copyright ©  -- ぢみへんプログラミング日誌 --  All Rights Reserved

Design by CriCri / Material by petit sozai emi / powered by NINJA TOOLS / 忍者ブログ / [PR]