chassis_repo 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/.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
|