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,29 @@
|
|
1
|
+
module Chassis
|
2
|
+
DelegationError = Chassis.error do |method, delegate|
|
3
|
+
"Cannot delegate #{method} without #{delegate}"
|
4
|
+
end
|
5
|
+
|
6
|
+
class Delegation < Module
|
7
|
+
def initialize(*methods)
|
8
|
+
options = methods.last.is_a?(Hash) ? methods.pop : { }
|
9
|
+
|
10
|
+
delegate = options.fetch :to do
|
11
|
+
fail ArgumentError, ":to not given"
|
12
|
+
end
|
13
|
+
|
14
|
+
methods.each do |method|
|
15
|
+
define_method method do |*args, &block|
|
16
|
+
object = send delegate
|
17
|
+
fail DelegationError.new method, delegate unless object
|
18
|
+
object.send(method, *args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def delegate(*methods)
|
26
|
+
Delegation.new *methods
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Chassis
|
2
|
+
module HashUtils
|
3
|
+
def symbolize(hash)
|
4
|
+
hash.inject({}) do |memo, pair|
|
5
|
+
key, value = pair
|
6
|
+
|
7
|
+
if value.is_a? Hash
|
8
|
+
memo.merge! key.to_sym => symbolize(value)
|
9
|
+
else
|
10
|
+
memo.merge! key.to_sym => value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
module_function :symbolize
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Chassis
|
2
|
+
module Persistence
|
3
|
+
module ClassMethods
|
4
|
+
def create(*args, &block)
|
5
|
+
record = new(*args, &block)
|
6
|
+
record.save
|
7
|
+
record
|
8
|
+
end
|
9
|
+
|
10
|
+
def repo
|
11
|
+
begin
|
12
|
+
@repo ||= StringUtils.constantize "#{name}Repo"
|
13
|
+
rescue NameError
|
14
|
+
fail "#{name}Repo not defined! Define this method to specify a different repo"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def included(base)
|
21
|
+
base.class_eval do
|
22
|
+
include Initializable
|
23
|
+
#include Equalizer.new(:id)
|
24
|
+
|
25
|
+
# Compare the object with other object for equality
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# object.eql?(other) # => true or false
|
29
|
+
#
|
30
|
+
# @param [Object] other
|
31
|
+
# the other object to compare with
|
32
|
+
#
|
33
|
+
# @return [Boolean]
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def eql?(other)
|
37
|
+
instance_of?(other.class) && id.eql?(other.id)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Compare the object with other object for equivalency
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# object == other # => true or false
|
44
|
+
#
|
45
|
+
# @param [Object] other
|
46
|
+
# the other object to compare with
|
47
|
+
#
|
48
|
+
# @return [Boolean]
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def ==(other)
|
52
|
+
other = coerce(other).first if respond_to?(:coerce, true)
|
53
|
+
other.kind_of?(self.class) && id == other.id
|
54
|
+
end
|
55
|
+
|
56
|
+
def hash
|
57
|
+
id.hash
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_accessor :id
|
61
|
+
end
|
62
|
+
|
63
|
+
base.extend ClassMethods
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def save
|
68
|
+
yield self if block_given?
|
69
|
+
repo.save self
|
70
|
+
end
|
71
|
+
|
72
|
+
def delete
|
73
|
+
repo.delete self
|
74
|
+
end
|
75
|
+
|
76
|
+
def new_record?
|
77
|
+
id.nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
def repo
|
81
|
+
self.class.repo
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/chassis/repo.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
module Chassis
|
2
|
+
RecordNotFoundError = Chassis.error do |klass, id|
|
3
|
+
"Could not locate #{klass} with id #{id}"
|
4
|
+
end
|
5
|
+
|
6
|
+
QueryNotImplementedError = Chassis.error do |selector|
|
7
|
+
"Adapter does not support #{selector.class}!"
|
8
|
+
end
|
9
|
+
|
10
|
+
GraphQueryNotImplementedError = Chassis.error do |selector|
|
11
|
+
"Adapter does not know how to graph #{selector.class}!"
|
12
|
+
end
|
13
|
+
|
14
|
+
NoQueryResultError = Chassis.error do |selector|
|
15
|
+
"Query #{selector.class} must return results!"
|
16
|
+
end
|
17
|
+
|
18
|
+
class Repo
|
19
|
+
include Interchange.new(*[
|
20
|
+
:all, :find, :create, :update, :delete,
|
21
|
+
:first, :last, :query, :graph_query,
|
22
|
+
:sample, :empty?, :count, :clear,
|
23
|
+
:initialize_storage
|
24
|
+
])
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def default
|
28
|
+
@default ||= new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def find(klass, id)
|
33
|
+
raise ArgumentError, "id cannot be nil!" if id.nil?
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
def save(record)
|
38
|
+
if record.id
|
39
|
+
update record
|
40
|
+
else
|
41
|
+
create record
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def query!(klass, selector)
|
46
|
+
result = query klass, selector
|
47
|
+
no_results = result.respond_to?(:empty?) ? result.empty? : result.nil?
|
48
|
+
|
49
|
+
if no_results && block_given?
|
50
|
+
yield klass, selector
|
51
|
+
elsif no_results
|
52
|
+
fail NoQueryResultError, selector
|
53
|
+
end
|
54
|
+
|
55
|
+
result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
def repo
|
61
|
+
Repo.default
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
require_relative 'repo/delegation'
|
67
|
+
require_relative 'repo/record_map'
|
68
|
+
require_relative 'repo/base_repo'
|
69
|
+
require_relative 'repo/null_repo'
|
70
|
+
require_relative 'repo/memory_repo'
|
71
|
+
require_relative 'repo/maglev_repo'
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Chassis
|
2
|
+
class BaseRepo
|
3
|
+
def create(record)
|
4
|
+
record.id ||= next_id
|
5
|
+
map.set record
|
6
|
+
end
|
7
|
+
|
8
|
+
def update(record)
|
9
|
+
map.set record
|
10
|
+
end
|
11
|
+
|
12
|
+
def delete(record)
|
13
|
+
map.delete record
|
14
|
+
end
|
15
|
+
|
16
|
+
def clear
|
17
|
+
map.clear
|
18
|
+
end
|
19
|
+
|
20
|
+
def all(klass)
|
21
|
+
map.all klass
|
22
|
+
end
|
23
|
+
|
24
|
+
def find(klass, id)
|
25
|
+
map.get klass, id
|
26
|
+
end
|
27
|
+
|
28
|
+
def first(klass)
|
29
|
+
all(klass).first
|
30
|
+
end
|
31
|
+
|
32
|
+
def last(klass)
|
33
|
+
all(klass).last
|
34
|
+
end
|
35
|
+
|
36
|
+
def sample(klass)
|
37
|
+
all(klass).sample
|
38
|
+
end
|
39
|
+
|
40
|
+
def empty?(klass)
|
41
|
+
all(klass).empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
def count(klass)
|
45
|
+
all(klass).count
|
46
|
+
end
|
47
|
+
|
48
|
+
def query(klass, selector)
|
49
|
+
if query_implemented? klass, selector
|
50
|
+
send query_method(klass, selector), klass, selector
|
51
|
+
else
|
52
|
+
raise QueryNotImplementedError, selector
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def graph_query(klass, selector)
|
57
|
+
if graph_query_implemented? klass, selector
|
58
|
+
send graph_query_method(klass, selector), klass, selector
|
59
|
+
else
|
60
|
+
raise GraphQueryNotImplementedError, selector
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize_storage
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def map
|
70
|
+
@map
|
71
|
+
end
|
72
|
+
|
73
|
+
def next_id
|
74
|
+
@counter ||= 0
|
75
|
+
@counter = @counter + 1
|
76
|
+
@counter
|
77
|
+
end
|
78
|
+
|
79
|
+
def query_method(klass, selector)
|
80
|
+
"query_#{selector_key(selector)}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def query_implemented?(klass, selector)
|
84
|
+
respond_to? query_method(klass, selector)
|
85
|
+
end
|
86
|
+
|
87
|
+
def graph_query_method(klass, selector)
|
88
|
+
"graph_query_#{selector_key(selector)}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def graph_query_implemented?(klass, selector)
|
92
|
+
respond_to? graph_query_method(klass, selector)
|
93
|
+
end
|
94
|
+
|
95
|
+
def selector_key(selector)
|
96
|
+
StringUtils.underscore(StringUtils.demodulize(selector.class.name))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Chassis
|
2
|
+
UnknownObjectClassError = Chassis.error do
|
3
|
+
"Rename class to end in Repo or define object_class"
|
4
|
+
end
|
5
|
+
|
6
|
+
class Repo
|
7
|
+
module Delegation
|
8
|
+
def all
|
9
|
+
backend.all object_class
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
backend.each object_class, &block
|
14
|
+
end
|
15
|
+
|
16
|
+
def count
|
17
|
+
backend.count object_class
|
18
|
+
end
|
19
|
+
|
20
|
+
def find(id)
|
21
|
+
backend.find object_class, id
|
22
|
+
end
|
23
|
+
|
24
|
+
def save(record)
|
25
|
+
backend.save(record)
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(record)
|
29
|
+
backend.delete record
|
30
|
+
end
|
31
|
+
|
32
|
+
def first
|
33
|
+
backend.first object_class
|
34
|
+
end
|
35
|
+
|
36
|
+
def last
|
37
|
+
backend.last object_class
|
38
|
+
end
|
39
|
+
|
40
|
+
def query(selector)
|
41
|
+
backend.query object_class, selector
|
42
|
+
end
|
43
|
+
|
44
|
+
def query!(selector, &block)
|
45
|
+
backend.query! object_class, selector, &block
|
46
|
+
end
|
47
|
+
|
48
|
+
def sample
|
49
|
+
backend.sample object_class
|
50
|
+
end
|
51
|
+
|
52
|
+
def empty?
|
53
|
+
backend.empty? object_class
|
54
|
+
end
|
55
|
+
|
56
|
+
def graph(id)
|
57
|
+
backend.graph object_class, id
|
58
|
+
end
|
59
|
+
|
60
|
+
def graph_query(selector)
|
61
|
+
backend.graph_query object_class, selector
|
62
|
+
end
|
63
|
+
|
64
|
+
def lazy(id)
|
65
|
+
LazyAssociation.new self, id
|
66
|
+
end
|
67
|
+
|
68
|
+
def object_class
|
69
|
+
@object_class ||= begin
|
70
|
+
fail UnknownObjectClassError unless name
|
71
|
+
match = name.match(/^(.+)Repo$/)
|
72
|
+
fail UnknownObjectClassError unless match
|
73
|
+
StringUtils.constantize match[1]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def backend
|
78
|
+
Repo.default
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|