Permalink
Browse files

Merge branch 'nesting-transactions'

  • Loading branch information...
2 parents c2501e3 + c56db2f commit 12e643e585e5e4a02e50d9ac770c784ed075d2ee @cyx cyx committed Apr 3, 2012
Showing with 78 additions and 79 deletions.
  1. +25 −17 lib/ohm.rb
  2. +21 −17 lib/ohm/transaction.rb
  3. +32 −45 test/transactions.rb
View
@@ -1217,34 +1217,42 @@ def save(&block)
# Saves the model without checking for validity. Refer to
# `Model#save` for more details.
def save!
- transaction do |t|
+ t = __save__
+ yield t if block_given?
+ t.commit(db)
+
+ return self
+ end
+
+ def __save__
+ Transaction.new do |t|
t.watch(*_unique_keys)
t.watch(key) if not new?
t.before do
_initialize_id if new?
end
- t.read do |store|
+ existing = nil
+ uniques = nil
+ indices = nil
+
+ t.read do
_verify_uniques
- store.existing = key.hgetall
- store.uniques = _read_index_type(:uniques)
- store.indices = _read_index_type(:indices)
+ existing = key.hgetall
+ uniques = _read_index_type(:uniques)
+ indices = _read_index_type(:indices)
end
- t.write do |store|
+ t.write do
model.key[:all].sadd(id)
- _delete_uniques(store.existing)
- _delete_indices(store.existing)
+ _delete_uniques(existing)
+ _delete_indices(existing)
_save
- _save_indices(store.indices)
- _save_uniques(store.uniques)
+ _save_indices(indices)
+ _save_uniques(uniques)
end
-
- yield t if block_given?
end
-
- return self
end
# Delete the model, including all the following keys:
@@ -1258,12 +1266,12 @@ def save!
def delete
transaction do |t|
t.read do |store|
- store.existing = key.hgetall
+ store[:existing] = key.hgetall
end
t.write do |store|
- _delete_uniques(store.existing)
- _delete_indices(store.existing)
+ _delete_uniques(store[:existing])
+ _delete_indices(store[:existing])
model.collections.each { |e| key[e].del }
model.key[:all].srem(id)
key[:counters].del
View
@@ -1,5 +1,3 @@
-require "set"
-
module Ohm
# Transactions in Ohm are designed to be composable and atomic. They use
@@ -42,27 +40,27 @@ module Ohm
#
# @see http://redis.io/topic/transactions Transactions in Redis.
class Transaction
- class Store < BasicObject
- class EntryAlreadyExistsError < ::RuntimeError
+ class Store
+ class EntryAlreadyExistsError < RuntimeError
end
- def method_missing(writer, value = nil)
- super unless writer[-1] == "="
+ class NoEntryError < RuntimeError
+ end
- reader = writer[0..-2].to_sym
+ def initialize
+ @dict = Hash.new
+ end
- __metaclass__.send(:define_method, reader) do
- value
- end
+ def [](key)
+ raise NoEntryError unless @dict.member?(key)
- __metaclass__.send(:define_method, writer) do |*_|
- ::Kernel.raise EntryAlreadyExistsError
- end
+ @dict[key]
end
- private
- def __metaclass__
- class << self; self end
+ def []=(key, value)
+ raise EntryAlreadyExistsError if @dict.member?(key)
+
+ @dict[key] = value
end
end
@@ -83,7 +81,7 @@ def append(t)
end
def watch(*keys)
- phase[:watch] += keys
+ phase[:watch].concat(keys - phase[:watch])
end
def read(&block)
@@ -117,6 +115,8 @@ def commit(db)
break if db.multi do
run(phase[:write], store)
end
+
+ store = nil
end
phase[:after].each(&:call)
@@ -126,4 +126,8 @@ def run(procs, store)
procs.each { |p| p.call(store) }
end
end
+
+ def self.transaction(&block)
+ Transaction.new(&block)
+ end
end
View
@@ -45,11 +45,11 @@
test "transaction local storage" do |db|
t1 = Ohm::Transaction.new do |t|
t.read do |s|
- s.foo = db.type("foo")
+ s[:foo] = db.type("foo")
end
t.write do |s|
- db.set("foo", s.foo.reverse)
+ db.set("foo", s[:foo].reverse)
end
end
@@ -62,15 +62,15 @@
t1 = Ohm::Transaction.new do |t|
t.watch("foo")
- t.write do |s|
+ t.write do
db.set("foo", "bar")
end
end
t2 = Ohm::Transaction.new do |t|
t.watch("foo")
- t.write do |s|
+ t.write do
db.set("foo", "baz")
end
end
@@ -158,13 +158,13 @@
test "storage in composed transactions" do |db|
t1 = Ohm::Transaction.new do |t|
t.read do |s|
- s.foo = db.type("foo")
+ s[:foo] = db.type("foo")
end
end
t2 = Ohm::Transaction.new do |t|
t.write do |s|
- db.set("foo", s.foo.reverse)
+ db.set("foo", s[:foo].reverse)
end
end
@@ -176,25 +176,25 @@
test "reading an storage entries that doesn't exist raises" do |db|
t1 = Ohm::Transaction.new do |t|
t.read do |s|
- s.foo
+ s[:foo]
end
end
- assert_raise NoMethodError do
+ assert_raise Ohm::Transaction::Store::NoEntryError do
t1.commit(db)
end
end
test "storage entries can't be overriden" do |db|
t1 = Ohm::Transaction.new do |t|
t.read do |s|
- s.foo = db.type("foo")
+ s[:foo] = db.type("foo")
end
end
t2 = Ohm::Transaction.new do |t|
t.read do |s|
- s.foo = db.exists("foo")
+ s[:foo] = db.exists("foo")
end
end
@@ -203,51 +203,38 @@
end
end
-__END__
-# We leave this here to indicate what the past behavior was with
-# model transactions.
-
-class Post < Ohm::Model
- attribute :body
- attribute :state
- index :state
-
- def before_save
- self.body = body.to_s.strip
+test "banking transaction" do |db|
+ class A < Ohm::Model
+ attribute :amount
end
- def before_create
- self.state = "draft"
+ class B < Ohm::Model
+ attribute :amount
end
-end
-
-test "transactions in models" do |db|
- p = Post.new(body: " foo ")
- db.set "csv:foo", "A,B"
+ def transfer(amount, account1, account2)
+ Ohm.transaction do |t|
- t1 = Ohm::Transaction.define do |t|
- t.watch("csv:foo")
+ t.watch(account1.key, account2.key)
- t.read do |s|
- s.csv = db.get("csv:foo")
- end
+ t.read do |s|
+ s[:available] = account1.get(:amount).to_i
+ end
- t.write do |s|
- db.set("csv:foo", s.csv + "," + "C")
+ t.write do |s|
+ if s[:available] >= amount
+ account1.key.hincrby(:amount, - amount)
+ account2.key.hincrby(:amount, amount)
+ end
+ end
end
end
- main = Ohm::Transaction.new(p.transaction_for_create, t1)
- main.commit(db)
-
- # Verify the Post transaction proceeded without a hitch
- p = Post[p.id]
+ a = A.create amount: 100
+ b = B.create amount: 0
- assert_equal "draft", p.state
- assert_equal "foo", p.body
- assert Post.find(state: "draft").include?(p)
+ transfer(100, a, b).commit(db)
- # Verify that the second transaction happened
- assert_equal "A,B,C", db.get("csv:foo")
+ assert_equal a.get(:amount), "0"
+ assert_equal b.get(:amount), "100"
end

0 comments on commit 12e643e

Please sign in to comment.