Railsのモデルの更新系メソッドをどう書くか

Railsでアプリを作っていて、モデルの更新系メソッドをどう定義するのが良いのだろうかと試行錯誤していたのだが、もうこれでいいんじゃないかな、というパターンが固まってきたのでメモっておく。

処理とエラーチェックの分離

メソッド定義内ではエラーチェックをせず、すべてがうまくいっているかのようなコードを書く。 状態を変更して保存し、自分自身を返す、というのがパターン。

エラーチェックはもっぱらバリデーションでおこなう。 定義したメソッドの実行時に必要なチェックが全て実行されるようにバリデーションを定義する。

# メインの処理
def do_something
  self.status = 'new_status'
  save
  self
end

# do_something実行時に実行されるようバリデーションの条件を指定する
validate :can_be_new_status, if: 'status_changed? && new_status?'

# バリデーションの定義
def can_be_new_status
  unless can_be_new_status?
    errors.add :status, :cannot_be_new_status
  end
end

バリデーションは必要に応じていくらでも増やせる。 チェック内容すべてを一つのバリデーションに詰め込んでもいいし、それらを個別のバリデーションに分けてもいい。 はじめはひとつのバリデーションに詰め込んでおいて、複数のメソッドで共通のチェック内容が出てきたら分離すればよいかと思う。

呼び出し側でのエラーチェック

メソッドの呼び出し側で処理の成否を確認するには、返されたオブジェクトのvalid?やerrors.any?、persisted?などのメソッドを使う。

何がうれしいのか

この方式だと、検出したいエラーの種類を増やしたいときにメソッド本体を修正する必要がなく、単にバリデーションを追加していけばよい。 戻り値として返されるオブジェクトにはバリデーションで引っかかった項目すべての情報が含まれているので、何がダメだったのかこれ以上ないほどに詳しい情報が含まれている。

これがメソッド本体でif文をならべて最初に引っかかったチェックでエラーを返す方式だと、たまたま最初に引っかかった問題点しかわからないので、その問題を修正して再度処理を実行しても、今度はまた別の問題で引っかかる可能性がある。

エラーを返す方法としてBool値や例外を使うことも可能だが、Bool値だとエラーの詳細がわからないし、例外を使う方法だと、そもそもどんな例外を定義するか考えたり、一貫性のある例外の使い方をするよう気をつけたりする必要があって、面倒くさい。

メソッド定義とエラーチェックが分離しているのはコードを追うときに若干わかりづらくなるかもしれないけど、コードはスッキリするし、エラーの検出も網羅的になるので、利点は大きい。

エラーを検出するのにいちいちバリデーションを追加するのは手間だと感じるかもしれないが、成功したか失敗したかぐらいの判別しかできない中途半端なエラー情報を返すメソッドを定義しても、後々自分が不便な思いをして修正するハメになりがちだ。 それだったら、決まりきったパターンとしてメソッドとバリデーションを定義して、バリデーション通過後の自分自身を返すメソッドを黙々と書いたほうが結局は時間の節約にもなるんじゃないかな。

ActiveRecordでカウンター

呼ばれるたびに前回返した値から+1したものを返すカウンターのクラスを書いたものの、出番がなくなったが、今後同じようなものを書きそうなのでメモ。 動作はMySQLで試した。

テーブルの定義

class CreateCounters < ActiveRecord::Migration
  def change
    create_table :counters do |t|
      t.string :key
      t.integer :count, :default => 0
 
      t.timestamps
    end
 
    add_index :counters, :key
  end
end

クラス定義

class Counter < ActiveRecord::Base
  class << self
    def next(key)
      find_or_create_by_key(key).next
    end
  end
 
  validates_presence_of :key, :count
  validates_uniqueness_of :key
 
  def next
    with_lock do
      increment! :count
      count
    end
  end
end

使い方

# クラスメソッドで使う
Counter.next('foo') # => 1
Counter.next('foo') # => 2
Counter.next('foo') # => 3
# 違うキーを使うと別のカウンター扱いになる
Counter.next('bar') # => 1
 
# インスタンス化して使う
counter = Counter.find_or_create_by_key 'foo'
counter.next # => 4
# 同じキーで別のインスタンスを作ると互いに影響する
counter2 = Counter.find_or_create_by_key 'foo'
counter2.next # => 5
counter.next  # => 6
counter2.next # => 7

Railsでモデルデータのクラスをサブクラスに変更する

Single Table Inheritanceで親子関係にある二つのクラスHoge, Fugaがあるとする。

class Hoge < ActiveRecord::Base
end

class Fuga < Hoge
end

このときHogeクラスのオブジェクトaがあったとしてそれをFugaクラスに変換したいとする。 そもそもそういう状況がオブジェクト指向的にどうなのかというのは置いておく。

このとき、

a = Hoge.find(1)
b = a.becomes(Fuga)
b.save!

とすればいいのかと思って試してみるも、ロードしなおしたオブジェクトはHogeクラスのままだった。

オブジェクトを保存するときのクエリーを見ると次のようになっていた。

UPDATE `hoges` SET `type` = 'Fuga', `updated_at` = '2013-04-08 14:04:33' 
 WHERE `hoges`.`type` IN ('Fuga') AND `hoges`.`id` = 1

type In ('Fuga')の制約のために、更新したいレコードが引っかからなかったのだ。 親クラスから子クラスに変更するには、次のように元のオブジェクトのtypeカラムを直接変更するのが無難か。

a = Hoge.find(1)
a.update_attribute(:type, Fuga.name)

becomesというメソッドの存在を知って、これを使えばクラスの変換もナチュラルな感じに書けるのかと期待したけど、結局typeカラムを修正するという、STIの実装に依存した書き方になった。

ちなみに子クラスから親クラスへの変換はbecomesとsave!を使ったやり方でもうまくいく。

Working with Unix Processes メモ

プロセスが持っているもの

ID

Process.pid

用途

  • シグナルを送る
  • ログファイル内でプロセスを識別

Process.ppid

ファイルディスクリプタ

passwd = File.open('/etc/passwd')
puts passwd.fileno

扱えるリソースの限度

Process.getrlimit(:NOFILE) # => [256, 9223372036854775807]
Process.setrlimit(:NOFILE, 10000)
ri Process.getrlimit

引数

ARGV[0]

環境

ENV["PATH"]

名前

$PROGRAM_NAME

終了コード

exit
exit 10
at_exit{puts "bye!"}
exit
abort        # 終了コード1
abort "hoge" # 標準エラーに表示される

プロセスができること

fork

puts "親プロセスID: #{Process.id}"
if fork
  puts "親プロセスID: #{Process.id}"
else
  puts "子プロセスID: #{Process.id}"
end
fork do
  # 子プロセス
end

wait

Process.wait  # => pid
Process.wait2 # => [pid, status]
child_pid = fork do
  # 子プロセス
  exit 10
end

pid, status = Process.waitpid2(child_pid)
puts status.exitstatus

親なしになる

fork do
  while true
    sleep 1
    puts "hello"
  end
end
abort "Bye!"

ゾンビになる

child_pid = fork do
  # 子プロセス
end

puts child_pid
sleep 20
ps -ho pid,state -p {プロセスID}
child_pid = fork do
  # 子プロセス
end

Process.detach(child_pid)
sleep 20

シグナルを受け取る

child_pid = fork do
  # 子プロセス
  sleep 3
end

trap(:CHLD) do
  puts "Hey!"
  exit
end

loop do
  sleep 1
end

他のプロセスと通信する

パイプ

r, w = IO.pipe
w.write("Hello")
w.close
puts r.read
r, w = IO.pipe
fork do
  r.close
  5.times do |i|
    sleep 1
    w.puts i
  end
end
w.close
while s = r.gets
  puts s
end

パイプは一方向

ソケット

require 'socket'
child_socket, parent_socket = Socket.pair(:UNIX, :DGRAM, 0)
fork do
  parent_socket.close

  5.times do |i|
    s = child_socket.recv(1000)
    puts s
    child_socket.send("CHILD: You said '#{s}'?", 0)
  end  
end
child_socket.close
5.times do |i|
  parent_socket.send("Hello #{i}!", 0)
end
5.times do
  puts parent_socket.recv(1000)
end

ソケットは双方向

デーモンになる

Process.daemon
10.times do |i|
  system "say #{10 - i}"
  sleep 1
end

Working With Unix Processes

Working With Unix Processes

経済的価値がある理由を考える切り口

The Personal MBAの中で、おなじみマズローの欲求段階のお話などとともに、Core Human Drivesというのを紹介している。

  • The Drive to Acquire
  • The Drive to Bond
  • The Drive to Learn
  • The Drive to Defend
  • The Drive to Feel

物が欲しい、愛されたい、知りたい、安全で居たい、刺激が欲しい、という欲求が人にはあり、これらの欲求が満たされない状態にある人は、それを求めるものだ、という話。

ビジネスで売りものを決めるときは、ちゃんと人の欲求を満たすものを作りましょうね、そして人の欲求ってのは大雑把にこれぐらいなものなので、自分が考えたものが人のどの欲求を満たすものなのかちゃんと押さえておきましょうね、どの欲求にも答えないものはどう頑張っても売れっこないですよ、ってこと。

この認識がどの程度妥当なのかはわからないけど、商品が売れる理由を考える際のチェックリストとして有用そうではある。

The Personal MBA: Master the Art of Business

The Personal MBA: Master the Art of Business

ビジネスが提供する価値の形

引き続きThe Personal MBAから。

この本の中で、ビジネスが提供する価値の典型的な形として12個挙げられている。

1. Product

車とかコンピュータなどの「モノ」を提供する。

複製(大量生産)可能な価値であることが良いところ。

2. Service

お金をとって助力を提供する。

医者、美容師、コンサルタントとか。

3. Shared Resources

各自では所有できない設備を提供する。

ジム、フィットネスクラブ、博物館、遊園地。

資金的に所有できない場合もあれば、スペース的に所有できないものもありそう。

4. Subscription

継続して価値を提供する。

ケーブルテレビ。

「継続して届ける」「いつでも利用できる状態に保っておく」ことが価値、か。

5. Resale

元の生産者が個別の消費者に販売する手間を引き受ける。

雑貨屋とかスーパーとか。

6. Lease

商品として買うと高価なものを、安く使えるようにする。

レンタカー、賃貸、貸しDVD。

7. Agency

元の価値提供者が個別の買い手を見つける手間を引き受ける。

不動産業。

Resaleと似ているが、元の価値提供者との間の取引が発生するタイミングが違うのか。

8. Audience Aggregation

人を集め、その人達へのアクセスを提供する。

雑誌と広告、広告で運営しているWeb。

9. Loan

自分で賄えない額のお金を提供する。

住宅ローン。

10. Option

特定の行動をとる権利を提供する。

映画のチケット。

11. Insurance

リスクを引き受ける。

生命保険。

12. Capital

ビジネスに資金を提供する。

こうして整理されると、「どう売るのか」を考えるときに網羅的に検討できそうでよい。

The Personal MBA: Master the Art of Business

The Personal MBA: Master the Art of Business

ビジネスについて学びはじめる

ちょっと前からThe Personal MBAという本をちょびちょびと読んでいる。

著者は大学のMBAコースを、大金払って現場では役に立たない知識を教えるところと批判し、自分で勉強したほうがいいという意見を持っている。

本の構成は、大きく「ビジネス」「人」「システム」に分けられ、それぞれいくつかの章が割り当てられている。 システムというのはITシステムとか生産システムなどではなく、それらを含む、ビジネスを回すのに伴う活動全般を指してるっぽい。

ビジネスについては、まずビジネスを構成するプロセスとして5つを挙げ、それぞれについて1つの章を割り当てている。 各章のタイトルは

  • Value Creation
  • Marketing
  • Sales
  • Value Delivery
  • Finance

となっている。

このように、活動全体をビジネス、人、システムに分け、さらにビジネスであればそれを5つのプロセスに分類した上で、各分類に関連する言葉と考え方を1つずつ紹介する、というスタイルになっている。 ひとつの節がひとつコンセプトの説明になっていて、本全体として用語集のように見えなくもない。

ちなみに「人」についての章は

  • Human Mind
  • Working with Yourself
  • Working with Others

「システム」に関する章のタイトルは

  • Understanding Systems
  • Analyzing Systems
  • Improving Systems

となっている。

人はそもそも言葉を知らなければうまく考えることもできない。 この本はビジネスという分野に関して、そもそも何を考えればいいのかすらわからないという人が、考慮しなければならなくなるであろう範囲の全体と、知っておくべき、あるいは知っていると便利かもしれない概念、それらを使った考え方の最初の取っ掛かりを得るのに良い内容かと思う。

著者のそもそもの活動が、有益なビジネス書のリストをブログで紹介することから始まったということもあり、この本の中でも、トピックについてもっとくわしく知りたい人向けに別の書籍を紹介しているのも便利。

The Personal MBA: Master the Art of Business

The Personal MBA: Master the Art of Business