chassis_repo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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