curly_mustache 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|