curly_mustache 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +106 -0
- data/Rakefile +56 -0
- data/TODO +0 -0
- data/VERSION.yml +4 -0
- data/curly_mustache.gemspec +89 -0
- data/lib/curly_mustache.rb +23 -0
- data/lib/curly_mustache/adapters.rb +61 -0
- data/lib/curly_mustache/adapters/abstract.rb +86 -0
- data/lib/curly_mustache/adapters/cassandra.rb +35 -0
- data/lib/curly_mustache/adapters/memcached.rb +56 -0
- data/lib/curly_mustache/attributes.rb +85 -0
- data/lib/curly_mustache/attributes/definition.rb +24 -0
- data/lib/curly_mustache/attributes/manager.rb +41 -0
- data/lib/curly_mustache/attributes/types.rb +49 -0
- data/lib/curly_mustache/base.rb +36 -0
- data/lib/curly_mustache/connection.rb +65 -0
- data/lib/curly_mustache/crud.rb +244 -0
- data/lib/curly_mustache/default_types.rb +18 -0
- data/lib/curly_mustache/errors.rb +14 -0
- data/lib/curly_mustache/locking.rb +83 -0
- data/test/abstract_adapter_test.rb +22 -0
- data/test/adapters.yml +6 -0
- data/test/attributes_test.rb +96 -0
- data/test/callbacks_test.rb +22 -0
- data/test/crud_test.rb +169 -0
- data/test/locking_test.rb +57 -0
- data/test/models/account.rb +31 -0
- data/test/models/feed.rb +13 -0
- data/test/models/page.rb +4 -0
- data/test/models/user.rb +10 -0
- data/test/serialization_test.rb +22 -0
- data/test/test_helper.rb +35 -0
- data/test/types_test.rb +13 -0
- data/test/validations_test.rb +65 -0
- metadata +112 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
require "curly_mustache/attributes/types"
|
2
|
+
|
3
|
+
types = CurlyMustache::Attributes::Types
|
4
|
+
types.define(:integer){ |value| Integer(value) rescue 0 }
|
5
|
+
types.define(:string){ |value| String(value) rescue "" }
|
6
|
+
types.define(:float){ |value| Float(value) rescue 0.0 }
|
7
|
+
types.define(:boolean){ |value| !!value }
|
8
|
+
types.define(:time) do |value|
|
9
|
+
if value.kind_of?(Time)
|
10
|
+
value
|
11
|
+
elsif value.kind_of?(String)
|
12
|
+
Time.parse(value)
|
13
|
+
elsif value.kind_of?(Integer) or value.kind_of?(Float)
|
14
|
+
Time.at(value)
|
15
|
+
else
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module CurlyMustache
|
2
|
+
class NoKeyError < RuntimeError; end
|
3
|
+
class AttributeNotDefinedError < RuntimeError; end
|
4
|
+
class IdNotSettableError < RuntimeError; end
|
5
|
+
class InvaildAttributeType < ArgumentError; end
|
6
|
+
class InvalidAssociation < RuntimeError; end
|
7
|
+
class NotImplementedError < RuntimeError
|
8
|
+
def initialize
|
9
|
+
super("Not implemented!")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
class RecordNotFound < RuntimeError; end
|
13
|
+
class RecordInvalid < RuntimeError; end
|
14
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "timeout"
|
2
|
+
|
3
|
+
module CurlyMustache
|
4
|
+
module Locking
|
5
|
+
|
6
|
+
def self.included(mod)
|
7
|
+
mod.send(:extend, ClassMethods)
|
8
|
+
mod.send(:include, InstanceMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def lock(id, timeout = nil)
|
14
|
+
if timeout.nil?
|
15
|
+
connection.lock(lock_key(id))
|
16
|
+
else
|
17
|
+
lock_with_timeout(id, timeout)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def lock_with_timeout(id, timeout)
|
22
|
+
start_time = Time.now
|
23
|
+
while (Time.now.to_f - start_time.to_f) < timeout
|
24
|
+
connection.lock(lock_key(id)) and return true
|
25
|
+
sleep(0.1)
|
26
|
+
end
|
27
|
+
raise Timeout::Error, "aquiring lock for #{id_to_key(id)}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def unlock(id)
|
31
|
+
connection.unlock(lock_key(id))
|
32
|
+
end
|
33
|
+
|
34
|
+
def lock_and_find(id, timeout = nil, &block)
|
35
|
+
lock(id, timeout) or return false
|
36
|
+
unlock(id) and return if (record = find(id)).blank?
|
37
|
+
if block_given?
|
38
|
+
do_locked_block(id, record, &block)
|
39
|
+
else
|
40
|
+
record
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def do_locked_block(id, record, &block)
|
45
|
+
yield(record)
|
46
|
+
rescue Exception
|
47
|
+
raise
|
48
|
+
ensure
|
49
|
+
unlock(id)
|
50
|
+
end
|
51
|
+
|
52
|
+
def lock_key(id)
|
53
|
+
"#{lock_prefix}:#{id_to_key(id)}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def lock_prefix
|
57
|
+
"lock"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
module InstanceMethods
|
63
|
+
|
64
|
+
def lock(timeout = nil, &block)
|
65
|
+
self.class.lock(self.id, timeout) or return false
|
66
|
+
if block_given?
|
67
|
+
self.class.do_locked_block(self.id, self.reload, &block)
|
68
|
+
else
|
69
|
+
true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def unlock
|
74
|
+
self.class.unlock(self.id)
|
75
|
+
end
|
76
|
+
|
77
|
+
def locked?
|
78
|
+
connection.locked?(self.class.lock_key(id))
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class BadAdapter < CurlyMustache::Adapters::Abstract; end
|
4
|
+
|
5
|
+
class AbstractAdapterTest < ActiveSupport::TestCase
|
6
|
+
|
7
|
+
def test_not_implemented
|
8
|
+
assert_raise(CurlyMustache::NotImplementedError){ BadAdapter.new(nil) }
|
9
|
+
BadAdapter.class_eval do
|
10
|
+
def initialize(config); nil; end
|
11
|
+
end
|
12
|
+
adapter = BadAdapter.new(nil)
|
13
|
+
assert_raise(CurlyMustache::NotImplementedError){ adapter.get(1) }
|
14
|
+
assert_raise(CurlyMustache::NotImplementedError){ adapter.mget([1, 2]) }
|
15
|
+
assert_raise(CurlyMustache::NotImplementedError){ adapter.put(1, "one") }
|
16
|
+
assert_raise(CurlyMustache::NotImplementedError){ adapter.delete(1) }
|
17
|
+
assert_raise(CurlyMustache::NotImplementedError){ adapter.flush_db }
|
18
|
+
assert_raise(CurlyMustache::NotImplementedError){ adapter.lock(1) }
|
19
|
+
assert_raise(CurlyMustache::NotImplementedError){ adapter.unlock(1) }
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/test/adapters.yml
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class AttributesTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def teardown
|
6
|
+
Account.allow_settable_id!(false) # Leave pristine.
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_attribute_type
|
10
|
+
assert_equal :string, Account.attribute_type(:name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_allow_settable_id
|
14
|
+
assert_equal false, Account.allow_settable_id
|
15
|
+
assert_raise(CurlyMustache::IdNotSettableError){ Account.create(:id => "123abc") }
|
16
|
+
Account.allow_settable_id!
|
17
|
+
assert_equal "123abc", Account.create(:id => "123abc").id
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_attributes
|
21
|
+
time = Time.now
|
22
|
+
attributes = { :name => "test", :created_at => time, :updated_at => time }.stringify_keys
|
23
|
+
account = Account.new
|
24
|
+
account.attributes = attributes
|
25
|
+
assert_equal attributes, account.attributes
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_class_attributes
|
29
|
+
assert_equal %w[id name phone_number balance is_admin created_at updated_at].sort, User.attributes.keys.sort
|
30
|
+
assert_equal %w[id name created_at updated_at].sort, Account.attributes.keys.sort
|
31
|
+
assert_equal :integer, User.attribute_type(:id)
|
32
|
+
assert_equal :string, Account.attribute_type(:id)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_set_attributes
|
36
|
+
assert_equal false, Account.allow_settable_id
|
37
|
+
account = Account.new
|
38
|
+
account.send(:set_attributes, :id => "123abc")
|
39
|
+
assert_equal "123abc", account.id
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_time_type
|
43
|
+
time = Time.now
|
44
|
+
account = Account.new
|
45
|
+
|
46
|
+
account.created_at = time.to_s(:number)
|
47
|
+
assert_equal Time.parse(time.to_s(:number)), account.created_at
|
48
|
+
|
49
|
+
account.created_at = time.to_i
|
50
|
+
assert_equal Time.at(time.to_i), account.created_at
|
51
|
+
|
52
|
+
account.created_at = Object.new
|
53
|
+
assert_equal nil, account.created_at
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_set_to_nil
|
57
|
+
user = User.new(:phone_number => 5123334444)
|
58
|
+
assert_equal 5123334444, user.phone_number
|
59
|
+
user.phone_number = nil
|
60
|
+
assert_nil user.phone_number
|
61
|
+
|
62
|
+
account = Account.new(:name => "blah")
|
63
|
+
assert_equal "blah", account.name
|
64
|
+
account.name = nil
|
65
|
+
assert_equal "", account.name
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_dirty
|
69
|
+
user = User.create(:name => "chris")
|
70
|
+
assert_equal false, user.changed?
|
71
|
+
assert_equal false, user.name_changed?
|
72
|
+
|
73
|
+
user.name = "callie"
|
74
|
+
assert_equal true, user.changed?
|
75
|
+
assert_equal true, user.name_changed?
|
76
|
+
assert_equal({ "name" => ["chris", "callie"] }, user.changes)
|
77
|
+
|
78
|
+
user.reset_name!
|
79
|
+
assert_equal false, user.changed?
|
80
|
+
assert_equal false, user.name_changed?
|
81
|
+
assert_equal({}, user.changes)
|
82
|
+
|
83
|
+
user.name = "callie"
|
84
|
+
user.phone_number = 5123334444
|
85
|
+
assert_equal true, user.changed?
|
86
|
+
assert_equal true, user.name_changed?
|
87
|
+
assert_equal true, user.phone_number_changed?
|
88
|
+
assert_equal({ "name" => ["chris", "callie"], "phone_number" => [nil, 5123334444] }, user.changes)
|
89
|
+
user.reset_phone_number!
|
90
|
+
assert_equal true, user.changed?
|
91
|
+
assert_equal true, user.name_changed?
|
92
|
+
assert_equal false, user.phone_number_changed?
|
93
|
+
assert_equal({ "name" => ["chris", "callie"] }, user.changes)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class CallbacksTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def test_callbacks
|
6
|
+
account = Account.create(:name => "blah")
|
7
|
+
assert_equal [:before_validation_on_create, :before_validation, :after_validation, :after_validation_on_create, :before_create, :before_save, :after_save, :after_create], account.callbacks_made
|
8
|
+
|
9
|
+
account.callbacks_made = []
|
10
|
+
account.save
|
11
|
+
assert_equal [:before_validation_on_update, :before_validation, :after_validation, :after_validation_on_update, :before_update, :before_save, :after_save, :after_update], account.callbacks_made
|
12
|
+
|
13
|
+
account.callbacks_made = []
|
14
|
+
account = Account.find(account.id)
|
15
|
+
assert_equal [:after_find], account.callbacks_made
|
16
|
+
|
17
|
+
account.callbacks_made = []
|
18
|
+
account.destroy
|
19
|
+
assert_equal [:before_destroy, :after_destroy], account.callbacks_made
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/test/crud_test.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class CrudTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
CurlyMustache::Base.connection.flush_db
|
7
|
+
end
|
8
|
+
|
9
|
+
def attributes_hash
|
10
|
+
{ :id => 123,
|
11
|
+
:name => "chris",
|
12
|
+
:phone_number => 5128258325,
|
13
|
+
:balance => 1.01,
|
14
|
+
:is_admin => true,
|
15
|
+
:created_at => Time.parse("2009-04-23 22:09:50 -05:00"),
|
16
|
+
:updated_at => Time.parse("2009-04-23 22:09:50 -05:00") }.dup
|
17
|
+
end
|
18
|
+
|
19
|
+
def assert_attributes(record, attributes = nil)
|
20
|
+
(attributes or attributes_hash).each do |k, v|
|
21
|
+
assert_equal v, record.send(k), "for attribute '#{k}'"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_create
|
26
|
+
user = User.create(attributes_hash)
|
27
|
+
assert(user)
|
28
|
+
assert(!user.new_record?)
|
29
|
+
user = User.find(user.id)
|
30
|
+
assert_attributes(user)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_create_with_autogenerated_id
|
34
|
+
user = User.create(attributes_hash.reject{ |k, v| k == :id })
|
35
|
+
assert user.id != attributes_hash[:id]
|
36
|
+
assert_attributes(user, attributes_hash.merge(:id => user.id))
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_create_autogenerated_id_by_expectation
|
40
|
+
id = "1341adb122d8190872c2928dbcc08b9d"
|
41
|
+
User.any_instance.expects(:generate_id).returns(id)
|
42
|
+
user = User.create :name => "christopher"
|
43
|
+
assert_equal id, user.id
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_find
|
47
|
+
attributes1 = attributes_hash
|
48
|
+
attributes1[:id] = 1
|
49
|
+
User.create(attributes1)
|
50
|
+
|
51
|
+
attributes2 = attributes_hash
|
52
|
+
attributes2[:id] = 2
|
53
|
+
User.create(attributes2)
|
54
|
+
|
55
|
+
assert_equal User.find(1), User.find(1)
|
56
|
+
|
57
|
+
user1 = User.find(1)
|
58
|
+
assert_attributes(user1, attributes1)
|
59
|
+
|
60
|
+
user2 = User.find(2)
|
61
|
+
assert_attributes(user2, attributes2)
|
62
|
+
|
63
|
+
assert_equal [user1, user2], User.find(1, 2)
|
64
|
+
assert_raise(CurlyMustache::RecordNotFound){ User.find(3) }
|
65
|
+
assert_raise(CurlyMustache::RecordNotFound){ User.find(1, 2, 3) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_find_by_id
|
69
|
+
assert_nil User.find_by_id(attributes_hash[:id])
|
70
|
+
User.create(attributes_hash)
|
71
|
+
assert_attributes(User.find_by_id(attributes_hash[:id]))
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_find_all_by_id
|
75
|
+
1.upto(3) do |i|
|
76
|
+
attributes = attributes_hash
|
77
|
+
attributes[:id] = i
|
78
|
+
User.create(attributes)
|
79
|
+
end
|
80
|
+
user1, user2, user3 = User.find_all_by_id(1, 2, 3)
|
81
|
+
assert_attributes(user1, attributes_hash.merge(:id => 1))
|
82
|
+
assert_attributes(user2, attributes_hash.merge(:id => 2))
|
83
|
+
assert_attributes(user3, attributes_hash.merge(:id => 3))
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_delete_all
|
87
|
+
1.upto(3) do |i|
|
88
|
+
attributes = attributes_hash
|
89
|
+
attributes[:id] = i
|
90
|
+
User.create(attributes)
|
91
|
+
end
|
92
|
+
|
93
|
+
User.delete_all(1, 2)
|
94
|
+
assert_raise(CurlyMustache::RecordNotFound){ User.find(1) }
|
95
|
+
assert_raise(CurlyMustache::RecordNotFound){ User.find(2) }
|
96
|
+
assert(User.find(3))
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_destroy_all
|
100
|
+
1.upto(3) do |i|
|
101
|
+
attributes = attributes_hash
|
102
|
+
attributes[:id] = i
|
103
|
+
User.create(attributes)
|
104
|
+
end
|
105
|
+
|
106
|
+
User.destroy_all(1, 2)
|
107
|
+
assert_raise(CurlyMustache::RecordNotFound){ User.find(1) }
|
108
|
+
assert_raise(CurlyMustache::RecordNotFound){ User.find(2) }
|
109
|
+
assert(User.find(3))
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_new
|
113
|
+
user = User.new(attributes_hash)
|
114
|
+
assert_attributes(user)
|
115
|
+
assert(user.new_record?)
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_reload
|
119
|
+
user = User.new(attributes_hash)
|
120
|
+
assert_raise(CurlyMustache::RecordNotFound){ user.reload }
|
121
|
+
|
122
|
+
assert(user = User.create(attributes_hash))
|
123
|
+
User.delete_all(user.id)
|
124
|
+
assert_raise(CurlyMustache::RecordNotFound){ user.reload }
|
125
|
+
|
126
|
+
assert(user = User.create(attributes_hash))
|
127
|
+
user.name = "callie"
|
128
|
+
assert_equal(user.read_attribute(:name), "callie")
|
129
|
+
user.reload
|
130
|
+
assert_equal(user.read_attribute(:name), attributes_hash[:name])
|
131
|
+
|
132
|
+
# Strange reload bug where it sets the id to 0.
|
133
|
+
# Ahh, this was happening because attributes_manager wasn't inheriting properly.
|
134
|
+
account = Account.create(:name => "blah")
|
135
|
+
assert_equal 32, (account_id = account.id).length
|
136
|
+
account.reload
|
137
|
+
assert_equal account_id, account.id
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_save_by_expectations
|
141
|
+
user = User.new(attributes_hash)
|
142
|
+
user.expects(:create).once
|
143
|
+
user.save
|
144
|
+
|
145
|
+
user = User.create(attributes_hash)
|
146
|
+
user.expects(:update).once
|
147
|
+
user.save
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_save
|
151
|
+
user = User.new(attributes_hash)
|
152
|
+
user.save
|
153
|
+
assert_attributes(user.reload)
|
154
|
+
|
155
|
+
user = User.create(attributes_hash)
|
156
|
+
user.name = "callie"
|
157
|
+
user.save
|
158
|
+
user.reload
|
159
|
+
assert_equal("callie", user.name)
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_destroy
|
163
|
+
user = User.create(attributes_hash)
|
164
|
+
user.destroy
|
165
|
+
assert(user.frozen?)
|
166
|
+
assert_raise(CurlyMustache::RecordNotFound){ user.reload }
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
unless [:cassandra].include?(CurlyMustache::Base.connection.adapter_name)
|
4
|
+
require "curly_mustache/locking"
|
5
|
+
CurlyMustache::Base.send(:include, CurlyMustache::Locking)
|
6
|
+
end
|
7
|
+
|
8
|
+
class LockingTest < ActiveSupport::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
if CurlyMustache::Base.method_defined?(:lock)
|
12
|
+
CurlyMustache::Base.connection.flush_db
|
13
|
+
@account = Account.create :name => "chris"
|
14
|
+
else
|
15
|
+
disable_tests
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_lock_unlock
|
20
|
+
assert !@account.unlock
|
21
|
+
assert @account.lock
|
22
|
+
assert !@account.lock
|
23
|
+
assert @account.unlock
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_lock_with_block
|
27
|
+
assert_equal "chris", @account.lock{ |user| user.name }
|
28
|
+
assert_equal false, @account.locked?
|
29
|
+
assert_equal true, @account.lock
|
30
|
+
assert_equal false, @account.lock{ |user| user.name }
|
31
|
+
assert_equal true, @account.locked?
|
32
|
+
assert_equal true, @account.unlock
|
33
|
+
assert_equal false, @account.locked?
|
34
|
+
@account.lock{ raise "blah" } rescue nil
|
35
|
+
assert_equal false, @account.locked?
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_lock_with_timeout
|
39
|
+
assert_equal true, @account.lock
|
40
|
+
assert_raise(Timeout::Error){ @account.lock(0.1) }
|
41
|
+
assert_equal true, @account.unlock
|
42
|
+
assert_equal "chris", @account.lock(0.1){ |user| user.name }
|
43
|
+
assert_equal false, @account.unlock
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_lock_and_find
|
47
|
+
assert_equal true, @account.lock
|
48
|
+
assert_equal false, Account.lock_and_find(@account.id)
|
49
|
+
assert_raise(Timeout::Error){ Account.lock_and_find(@account.id, 0.1) }
|
50
|
+
assert_equal true, @account.unlock
|
51
|
+
assert_equal "chris", Account.lock_and_find(@account.id).name
|
52
|
+
assert_equal true, @account.unlock
|
53
|
+
assert_equal "chris", Account.lock_and_find(@account.id){ |user| user.name }
|
54
|
+
assert_equal false, @account.unlock
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|