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.
Files changed (49) hide show
  1. data/.gitignore +47 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +42 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +118 -0
  6. data/Rakefile +34 -0
  7. data/chassis_repo.gemspec +33 -0
  8. data/examples/maglev_repo.rb +56 -0
  9. data/examples/repo.rb +40 -0
  10. data/lib/chassis.rb +34 -0
  11. data/lib/chassis/array_utils.rb +8 -0
  12. data/lib/chassis/core_ext/array.rb +5 -0
  13. data/lib/chassis/core_ext/hash.rb +5 -0
  14. data/lib/chassis/core_ext/string.rb +13 -0
  15. data/lib/chassis/delegate.rb +29 -0
  16. data/lib/chassis/error.rb +7 -0
  17. data/lib/chassis/hash_utils.rb +16 -0
  18. data/lib/chassis/initializable.rb +3 -0
  19. data/lib/chassis/logger.rb +8 -0
  20. data/lib/chassis/persistence.rb +84 -0
  21. data/lib/chassis/repo.rb +71 -0
  22. data/lib/chassis/repo/base_repo.rb +99 -0
  23. data/lib/chassis/repo/delegation.rb +82 -0
  24. data/lib/chassis/repo/maglev_repo.rb +51 -0
  25. data/lib/chassis/repo/memory_repo.rb +7 -0
  26. data/lib/chassis/repo/null_repo.rb +64 -0
  27. data/lib/chassis/repo/record_map.rb +44 -0
  28. data/lib/chassis/string_utils.rb +50 -0
  29. data/lib/chassis/version.rb +3 -0
  30. data/test/array_utils_test.rb +23 -0
  31. data/test/chassis_test.rb +7 -0
  32. data/test/core_ext/array_test.rb +8 -0
  33. data/test/core_ext/hash_test.rb +8 -0
  34. data/test/core_ext/string_test.rb +16 -0
  35. data/test/delegate_test.rb +41 -0
  36. data/test/error_test.rb +12 -0
  37. data/test/hash_utils_test.rb +17 -0
  38. data/test/initializable_test.rb +7 -0
  39. data/test/logger_test.rb +43 -0
  40. data/test/persistence_test.rb +112 -0
  41. data/test/repo/delegation_test.rb +100 -0
  42. data/test/repo/maglev_repo_test.rb +52 -0
  43. data/test/repo/memory_repo_test.rb +25 -0
  44. data/test/repo/null_repo_test.rb +56 -0
  45. data/test/repo/repo_tests.rb +120 -0
  46. data/test/repo_test.rb +76 -0
  47. data/test/string_utils_test.rb +21 -0
  48. data/test/test_helper.rb +13 -0
  49. metadata +274 -0
@@ -0,0 +1,8 @@
1
+ module Chassis
2
+ module ArrayUtils
3
+ def extract_options!(array)
4
+ array.last.is_a?(Hash) ? array.pop : { }
5
+ end
6
+ module_function :extract_options!
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def extract_options!
3
+ Chassis::ArrayUtils.extract_options! self
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def symbolize
3
+ Chassis::HashUtils.symbolize self
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ class String
2
+ def constantize
3
+ Chassis::StringUtils.constantize self
4
+ end
5
+
6
+ def demodulize
7
+ Chassis::StringUtils.demodulize self
8
+ end
9
+
10
+ def underscore
11
+ Chassis::StringUtils.underscore self
12
+ end
13
+ end
@@ -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,7 @@
1
+ module Chassis
2
+ class << self
3
+ def error(*args, &block)
4
+ Tnt.boom *args, &block
5
+ end
6
+ end
7
+ 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,3 @@
1
+ module Chassis
2
+ Initializable = ::Lift
3
+ end
@@ -0,0 +1,8 @@
1
+ module Chassis
2
+ class Logger < ::Logger
3
+ def initialize(logdev = Chassis.stream, shift_age = 0, shift_size = 1048576)
4
+ super
5
+ self.level = ENV['LOG_LEVEL'].to_sym if ENV['LOG_LEVEL']
6
+ end
7
+ end
8
+ 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
@@ -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