chassis_repo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +47 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +118 -0
- data/Rakefile +34 -0
- data/chassis_repo.gemspec +33 -0
- data/examples/maglev_repo.rb +56 -0
- data/examples/repo.rb +40 -0
- data/lib/chassis.rb +34 -0
- data/lib/chassis/array_utils.rb +8 -0
- data/lib/chassis/core_ext/array.rb +5 -0
- data/lib/chassis/core_ext/hash.rb +5 -0
- data/lib/chassis/core_ext/string.rb +13 -0
- data/lib/chassis/delegate.rb +29 -0
- data/lib/chassis/error.rb +7 -0
- data/lib/chassis/hash_utils.rb +16 -0
- data/lib/chassis/initializable.rb +3 -0
- data/lib/chassis/logger.rb +8 -0
- data/lib/chassis/persistence.rb +84 -0
- data/lib/chassis/repo.rb +71 -0
- data/lib/chassis/repo/base_repo.rb +99 -0
- data/lib/chassis/repo/delegation.rb +82 -0
- data/lib/chassis/repo/maglev_repo.rb +51 -0
- data/lib/chassis/repo/memory_repo.rb +7 -0
- data/lib/chassis/repo/null_repo.rb +64 -0
- data/lib/chassis/repo/record_map.rb +44 -0
- data/lib/chassis/string_utils.rb +50 -0
- data/lib/chassis/version.rb +3 -0
- data/test/array_utils_test.rb +23 -0
- data/test/chassis_test.rb +7 -0
- data/test/core_ext/array_test.rb +8 -0
- data/test/core_ext/hash_test.rb +8 -0
- data/test/core_ext/string_test.rb +16 -0
- data/test/delegate_test.rb +41 -0
- data/test/error_test.rb +12 -0
- data/test/hash_utils_test.rb +17 -0
- data/test/initializable_test.rb +7 -0
- data/test/logger_test.rb +43 -0
- data/test/persistence_test.rb +112 -0
- data/test/repo/delegation_test.rb +100 -0
- data/test/repo/maglev_repo_test.rb +52 -0
- data/test/repo/memory_repo_test.rb +25 -0
- data/test/repo/null_repo_test.rb +56 -0
- data/test/repo/repo_tests.rb +120 -0
- data/test/repo_test.rb +76 -0
- data/test/string_utils_test.rb +21 -0
- data/test/test_helper.rb +13 -0
- metadata +274 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
module Chassis
|
2
|
+
class MaglevRepo < BaseRepo
|
3
|
+
class MaglevMap
|
4
|
+
def initialize store
|
5
|
+
@store = store
|
6
|
+
end
|
7
|
+
|
8
|
+
def set(record)
|
9
|
+
record_map(record)[record.id] = record
|
10
|
+
Maglev.commit
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(klass, id)
|
14
|
+
class_map(klass).fetch id do
|
15
|
+
fail RecordNotFoundError.new(klass, id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(record)
|
20
|
+
record_map(record).delete record.id
|
21
|
+
Maglev.commit
|
22
|
+
end
|
23
|
+
|
24
|
+
def all(klass)
|
25
|
+
class_map(klass).values
|
26
|
+
end
|
27
|
+
|
28
|
+
def clear
|
29
|
+
store.clear
|
30
|
+
Maglev.commit
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def store
|
35
|
+
@store
|
36
|
+
end
|
37
|
+
|
38
|
+
def class_map(klass)
|
39
|
+
store[klass] ||= { }
|
40
|
+
end
|
41
|
+
|
42
|
+
def record_map(record)
|
43
|
+
class_map record.class
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize store
|
48
|
+
@map = MaglevMap.new store
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Chassis
|
2
|
+
class NullRepo
|
3
|
+
def initialize
|
4
|
+
@counter = 0
|
5
|
+
end
|
6
|
+
|
7
|
+
def create(record)
|
8
|
+
@counter = @counter + 1
|
9
|
+
record.id ||= @counter
|
10
|
+
end
|
11
|
+
|
12
|
+
def update(*)
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(*)
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def find(*)
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def first(*)
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def last(*)
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def sample(*)
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def count(*)
|
41
|
+
0
|
42
|
+
end
|
43
|
+
|
44
|
+
def first(*)
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def all(klass)
|
49
|
+
[ ]
|
50
|
+
end
|
51
|
+
|
52
|
+
def query(*)
|
53
|
+
[ ]
|
54
|
+
end
|
55
|
+
|
56
|
+
def graph_query(*)
|
57
|
+
[ ]
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize_storage
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Chassis
|
2
|
+
class Repo
|
3
|
+
class RecordMap
|
4
|
+
def initialize
|
5
|
+
@hash = { }
|
6
|
+
end
|
7
|
+
|
8
|
+
def set(record)
|
9
|
+
record_map(record)[record.id] = record
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(klass, id)
|
13
|
+
class_map(klass).fetch id do
|
14
|
+
fail RecordNotFoundError.new(klass, id)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(record)
|
19
|
+
record_map(record).delete record.id
|
20
|
+
end
|
21
|
+
|
22
|
+
def all(klass)
|
23
|
+
class_map(klass).values
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear
|
27
|
+
hash.clear
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def hash
|
32
|
+
@hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def class_map(klass)
|
36
|
+
hash[klass] ||= { }
|
37
|
+
end
|
38
|
+
|
39
|
+
def record_map(record)
|
40
|
+
class_map record.class
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Chassis
|
2
|
+
module StringUtils
|
3
|
+
def demodulize(path)
|
4
|
+
path = path.to_s
|
5
|
+
if i = path.rindex('::')
|
6
|
+
path[(i+2)..-1]
|
7
|
+
else
|
8
|
+
path
|
9
|
+
end
|
10
|
+
end
|
11
|
+
module_function :demodulize
|
12
|
+
|
13
|
+
def underscore(camel_cased_word)
|
14
|
+
word = camel_cased_word.to_s.gsub('::', '/')
|
15
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
16
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
17
|
+
word.tr!("-", "_")
|
18
|
+
word.downcase!
|
19
|
+
word
|
20
|
+
end
|
21
|
+
module_function :underscore
|
22
|
+
|
23
|
+
def constantize(camel_cased_word)
|
24
|
+
names = camel_cased_word.split('::')
|
25
|
+
names.shift if names.empty? || names.first.empty?
|
26
|
+
|
27
|
+
names.inject(Object) do |constant, name|
|
28
|
+
if constant == Object
|
29
|
+
constant.const_get(name)
|
30
|
+
else
|
31
|
+
candidate = constant.const_get(name)
|
32
|
+
next candidate if constant.const_defined?(name, false)
|
33
|
+
next candidate unless Object.const_defined?(name)
|
34
|
+
|
35
|
+
# Go down the ancestors to check it it's owned
|
36
|
+
# directly before we reach Object or the end of ancestors.
|
37
|
+
constant = constant.ancestors.inject do |const, ancestor|
|
38
|
+
break const if ancestor == Object
|
39
|
+
break ancestor if ancestor.const_defined?(name, false)
|
40
|
+
const
|
41
|
+
end
|
42
|
+
|
43
|
+
# owner is in Object, so raise
|
44
|
+
constant.const_get(name, false)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
module_function :constantize
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class ArrayUtilsTest < Minitest::Test
|
4
|
+
def utils
|
5
|
+
Chassis::ArrayUtils
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_extract_options_removes_options_hash_if_present
|
9
|
+
args = ['foo', { bar: 'baz' }]
|
10
|
+
options = utils.extract_options! args
|
11
|
+
|
12
|
+
assert_equal({ bar: 'baz' }, options)
|
13
|
+
assert_equal(%w(foo), args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_extract_options_does_nothing_if_no_options
|
17
|
+
args = %w(foo)
|
18
|
+
options = utils.extract_options! args
|
19
|
+
|
20
|
+
assert_equal({}, options)
|
21
|
+
assert_equal(%w(foo), args)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'chassis/core_ext/string'
|
3
|
+
|
4
|
+
class StringCoreExtTest < Minitest::Test
|
5
|
+
def test_underscore
|
6
|
+
assert_equal('foo_bar', 'FooBar'.underscore)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_demodulize
|
10
|
+
assert_equal('Bar', 'Foo::Bar'.demodulize)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_constantize
|
14
|
+
assert_equal self.class, self.class.name.constantize
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class DelegateTest < Minitest::Test
|
4
|
+
class Delegator
|
5
|
+
include Chassis.delegate(:add, to: :object)
|
6
|
+
|
7
|
+
attr_reader :object
|
8
|
+
|
9
|
+
def initialize(object)
|
10
|
+
@object = object
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_methods_are_delegated
|
15
|
+
delegate = Class.new do
|
16
|
+
def add(number)
|
17
|
+
number + 5
|
18
|
+
end
|
19
|
+
end.new
|
20
|
+
|
21
|
+
delegator = Delegator.new delegate
|
22
|
+
|
23
|
+
assert_equal 10, delegator.add(5)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_raises_an_error_if_no_object_to_delegate_to
|
27
|
+
delegator = Delegator.new nil
|
28
|
+
|
29
|
+
assert_raises Chassis::DelegationError do
|
30
|
+
delegator.add 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_fails_with_an_error_if_nothing_to_delegate_to
|
35
|
+
assert_raises ArgumentError do
|
36
|
+
Class.new do
|
37
|
+
include Chassis.delegate(:foo, :bar)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/test/error_test.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class HashUtilsTest < Minitest::Test
|
4
|
+
def utils
|
5
|
+
Chassis::HashUtils
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_symbolize_keys_converts_keys_to_symbols
|
9
|
+
result = utils.symbolize({ 'foo' => 'bar' })
|
10
|
+
assert_equal({ foo: 'bar'}, result)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_symbolize_keys_recursises_into_objects
|
14
|
+
result = utils.symbolize({ 'foo' => { 'bar' => 'baz' }})
|
15
|
+
assert_equal({ foo: { bar: 'baz' }}, result)
|
16
|
+
end
|
17
|
+
end
|
data/test/logger_test.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class LoggerTest < Minitest::Test
|
4
|
+
#def test_log_level_defaults_to_env_variable
|
5
|
+
# with_env :warn do
|
6
|
+
# logger = Chassis::Logger.new $stdout
|
7
|
+
# assert_equal Logger::WARN, logger.level
|
8
|
+
# end
|
9
|
+
#end
|
10
|
+
|
11
|
+
def test_log_deafults_debug_without_env_variable
|
12
|
+
refute ENV['LOG_LEVEL'], "Precondition: LOG_LEVEL must be blank"
|
13
|
+
|
14
|
+
logger = Chassis::Logger.new $stdout
|
15
|
+
assert_equal Logger::DEBUG, logger.level
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_log_dev_defaults_to_chassis_stream
|
19
|
+
Chassis.stream = StringIO.new
|
20
|
+
|
21
|
+
logger = Chassis::Logger.new
|
22
|
+
logger.debug 'test'
|
23
|
+
|
24
|
+
Chassis.stream.rewind
|
25
|
+
content = Chassis.stream.read
|
26
|
+
|
27
|
+
assert_includes content, 'test'
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def with_env(name)
|
32
|
+
original_env = ENV['LOG_LEVEL']
|
33
|
+
ENV['LOG_LEVEL'] = name.to_s
|
34
|
+
|
35
|
+
yield
|
36
|
+
ensure
|
37
|
+
if original_env
|
38
|
+
ENV['LOG_LEVEL'] = original_env
|
39
|
+
else
|
40
|
+
ENV.delete 'LOG_LEVEL'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class PersistenceTest < Minitest::Test
|
4
|
+
class FakeRepo
|
5
|
+
|
6
|
+
end
|
7
|
+
|
8
|
+
class Model
|
9
|
+
include Chassis::Persistence
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def repo
|
13
|
+
FakeRepo
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_models_can_be_initialize_with_a_hash
|
19
|
+
assert_includes Model.ancestors, Chassis::Initializable
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_class_has_a_create_factory
|
23
|
+
FakeRepo.expects(:save)
|
24
|
+
Model.create id: 5
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_delegates_save_to_the_repo
|
28
|
+
model = Model.new id: 5
|
29
|
+
FakeRepo.expects(:save).with(model)
|
30
|
+
model.save
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_delegates_delete_to_the_repo
|
34
|
+
model = Model.new id: 5
|
35
|
+
FakeRepo.expects(:delete).with(model)
|
36
|
+
model.delete
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_is_new_record_when_id_is_nil
|
40
|
+
model = Model.new id: 5
|
41
|
+
refute model.new_record?
|
42
|
+
|
43
|
+
model.id = nil
|
44
|
+
assert model.new_record?
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_implements_double_equals
|
48
|
+
m1 = Model.new(id: 1)
|
49
|
+
m2 = Model.new(id: 2)
|
50
|
+
m3 = Model.new(id: 1)
|
51
|
+
|
52
|
+
assert (m1 == m1)
|
53
|
+
assert (m1 == m3)
|
54
|
+
refute (m1 == m2)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_implements_triple_equals
|
58
|
+
m1 = Model.new(id: 1)
|
59
|
+
m2 = Model.new(id: 2)
|
60
|
+
m3 = Model.new(id: 1)
|
61
|
+
|
62
|
+
assert (m1 === m1)
|
63
|
+
assert (m1 === m3)
|
64
|
+
refute (m1 === m2)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_implements_eql?
|
68
|
+
m1 = Model.new(id: 1)
|
69
|
+
m2 = Model.new(id: 2)
|
70
|
+
m3 = Model.new(id: 1)
|
71
|
+
|
72
|
+
assert (m1 == m1)
|
73
|
+
assert (m1 == m3)
|
74
|
+
refute (m1 == m2)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_acts_has_a_hash_key
|
78
|
+
m1 = Model.new(id: 1)
|
79
|
+
m2 = Model.new(id: 2)
|
80
|
+
m3 = Model.new(id: 1)
|
81
|
+
|
82
|
+
hash = { }
|
83
|
+
hash[m1] = 'm1'
|
84
|
+
|
85
|
+
assert_includes hash, m1
|
86
|
+
assert_includes hash, m3
|
87
|
+
refute_includes hash, m2
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_acts_well_in_arrays
|
91
|
+
m1 = Model.new(id: 1)
|
92
|
+
m2 = Model.new(id: 2)
|
93
|
+
m3 = Model.new(id: 1)
|
94
|
+
|
95
|
+
array = [ m1 ]
|
96
|
+
|
97
|
+
assert_includes array, m1
|
98
|
+
assert_includes array, m3
|
99
|
+
refute_includes array, m2
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_save_takes_a_block_of_changes
|
103
|
+
FakeRepo.expects(:save)
|
104
|
+
|
105
|
+
model = Model.new id: 5
|
106
|
+
model.save do |m|
|
107
|
+
m.id = 6
|
108
|
+
end
|
109
|
+
|
110
|
+
assert_equal 6, model.id
|
111
|
+
end
|
112
|
+
end
|