chassis 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 (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +362 -0
  6. data/Rakefile +33 -0
  7. data/chassis.gemspec +41 -0
  8. data/examples/repo.rb +40 -0
  9. data/lib/chassis.rb +81 -0
  10. data/lib/chassis/array_utils.rb +8 -0
  11. data/lib/chassis/circuit_panel.rb +22 -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/dirty_session.rb +105 -0
  17. data/lib/chassis/error.rb +7 -0
  18. data/lib/chassis/faraday.rb +226 -0
  19. data/lib/chassis/form.rb +56 -0
  20. data/lib/chassis/hash_utils.rb +16 -0
  21. data/lib/chassis/heroku.rb +5 -0
  22. data/lib/chassis/initializable.rb +11 -0
  23. data/lib/chassis/logger.rb +8 -0
  24. data/lib/chassis/observable.rb +19 -0
  25. data/lib/chassis/persistence.rb +49 -0
  26. data/lib/chassis/rack/bouncer.rb +33 -0
  27. data/lib/chassis/rack/builder_shim_patch.rb +7 -0
  28. data/lib/chassis/rack/health_check.rb +45 -0
  29. data/lib/chassis/rack/instrumentation.rb +20 -0
  30. data/lib/chassis/rack/json_body_parser.rb +20 -0
  31. data/lib/chassis/rack/no_robots.rb +24 -0
  32. data/lib/chassis/registry.rb +30 -0
  33. data/lib/chassis/repo.rb +73 -0
  34. data/lib/chassis/repo/base_repo.rb +99 -0
  35. data/lib/chassis/repo/delegation.rb +78 -0
  36. data/lib/chassis/repo/lazy_association.rb +57 -0
  37. data/lib/chassis/repo/memory_repo.rb +7 -0
  38. data/lib/chassis/repo/null_repo.rb +64 -0
  39. data/lib/chassis/repo/pstore_repo.rb +54 -0
  40. data/lib/chassis/repo/record_map.rb +44 -0
  41. data/lib/chassis/repo/redis_repo.rb +55 -0
  42. data/lib/chassis/serializable.rb +52 -0
  43. data/lib/chassis/string_utils.rb +50 -0
  44. data/lib/chassis/version.rb +3 -0
  45. data/lib/chassis/web_service.rb +61 -0
  46. data/test/array_utils_test.rb +23 -0
  47. data/test/chassis_test.rb +7 -0
  48. data/test/circuit_panel_test.rb +22 -0
  49. data/test/core_ext/array_test.rb +8 -0
  50. data/test/core_ext/hash_test.rb +8 -0
  51. data/test/core_ext/string_test.rb +16 -0
  52. data/test/delegate_test.rb +41 -0
  53. data/test/dirty_session_test.rb +138 -0
  54. data/test/error_test.rb +12 -0
  55. data/test/faraday_test.rb +749 -0
  56. data/test/form_test.rb +29 -0
  57. data/test/hash_utils_test.rb +17 -0
  58. data/test/initializable_test.rb +22 -0
  59. data/test/logger_test.rb +43 -0
  60. data/test/observable_test.rb +27 -0
  61. data/test/persistence_test.rb +112 -0
  62. data/test/prox_test.rb +7 -0
  63. data/test/rack/bouncer_test.rb +42 -0
  64. data/test/rack/builder_patch_test.rb +36 -0
  65. data/test/rack/health_check_test.rb +35 -0
  66. data/test/rack/instrumentation_test.rb +38 -0
  67. data/test/rack/json_body_parser_test.rb +38 -0
  68. data/test/rack/no_robots_test.rb +34 -0
  69. data/test/registry_test.rb +26 -0
  70. data/test/repo/delegation_test.rb +101 -0
  71. data/test/repo/lazy_association_test.rb +115 -0
  72. data/test/repo/memory_repo_test.rb +25 -0
  73. data/test/repo/null_repo_test.rb +48 -0
  74. data/test/repo/pstore_repo_test.rb +28 -0
  75. data/test/repo/redis_repo_test.rb +26 -0
  76. data/test/repo/repo_tests.rb +120 -0
  77. data/test/repo_test.rb +76 -0
  78. data/test/serializable_test.rb +77 -0
  79. data/test/string_utils_test.rb +21 -0
  80. data/test/test_helper.rb +10 -0
  81. data/test/web_service_test.rb +107 -0
  82. metadata +426 -0
@@ -0,0 +1,78 @@
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 count
13
+ backend.count object_class
14
+ end
15
+
16
+ def find(id)
17
+ backend.find object_class, id
18
+ end
19
+
20
+ def save(record)
21
+ backend.save(record)
22
+ end
23
+
24
+ def delete(record)
25
+ backend.delete record
26
+ end
27
+
28
+ def first
29
+ backend.first object_class
30
+ end
31
+
32
+ def last
33
+ backend.last object_class
34
+ end
35
+
36
+ def query(selector)
37
+ backend.query object_class, selector
38
+ end
39
+
40
+ def query!(selector, &block)
41
+ backend.query! object_class, selector, &block
42
+ end
43
+
44
+ def sample
45
+ backend.sample object_class
46
+ end
47
+
48
+ def empty?
49
+ backend.empty? object_class
50
+ end
51
+
52
+ def graph(id)
53
+ backend.graph object_class, id
54
+ end
55
+
56
+ def graph_query(selector)
57
+ backend.graph_query object_class, selector
58
+ end
59
+
60
+ def lazy(id)
61
+ LazyAssociation.new self, id
62
+ end
63
+
64
+ def object_class
65
+ @object_class ||= begin
66
+ fail UnknownObjectClassError unless name
67
+ match = name.match(/^(.+)Repo$/)
68
+ fail UnknownObjectClassError unless match
69
+ StringUtils.constantize match[1]
70
+ end
71
+ end
72
+
73
+ def backend
74
+ Repo.default
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,57 @@
1
+ module Chassis
2
+ class Repo
3
+ class LazyAssociation < Proxy
4
+ def initialize(repo, id)
5
+ @repo, @id = repo, id
6
+ end
7
+
8
+ def id
9
+ @id
10
+ end
11
+
12
+ def class
13
+ @repo.object_class
14
+ end
15
+
16
+ def repo
17
+ @repo
18
+ end
19
+
20
+ def instance_of?(klass)
21
+ self.class == klass
22
+ end
23
+
24
+ def is_a?(klass)
25
+ instance_of?(klass) || self.class > klass
26
+ end
27
+
28
+ def kind_of?(klass)
29
+ is_a? klass
30
+ end
31
+
32
+ def ==(other)
33
+ if other.instance_of? self.class
34
+ other.id == id
35
+ else
36
+ other.repo == repo && other.id == id
37
+ end
38
+ end
39
+
40
+ def eql?(other)
41
+ self == other
42
+ end
43
+
44
+ def materialize
45
+ @object ||= repo.find id
46
+ end
47
+
48
+ def __getobj__
49
+ materialize
50
+ end
51
+
52
+ def inspect
53
+ "#<LazyAssociation:#{object_id} @repo=#{repo} @id=#{id}>"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,7 @@
1
+ module Chassis
2
+ class MemoryRepo < BaseRepo
3
+ def initialize
4
+ @map = Repo::RecordMap.new
5
+ end
6
+ end
7
+ 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,54 @@
1
+ module Chassis
2
+ class PStoreRepo < BaseRepo
3
+ class AccessProxy < Chassis::Proxy
4
+ def map
5
+ __getobj__[:map] ||= Chassis::Repo::RecordMap.new
6
+ end
7
+ end
8
+
9
+ class PStoreMap
10
+ def initialize(pstore)
11
+ @pstore = AccessProxy.new pstore
12
+ end
13
+
14
+ def clear
15
+ pstore.transaction do
16
+ pstore.map.clear
17
+ end
18
+ end
19
+
20
+ def all(klass)
21
+ pstore.transaction do
22
+ pstore.map.all klass
23
+ end
24
+ end
25
+
26
+ def get(klass, id)
27
+ pstore.transaction do
28
+ pstore.map.get klass, id
29
+ end
30
+ end
31
+
32
+ def set(record)
33
+ pstore.transaction do
34
+ pstore.map.set record
35
+ end
36
+ end
37
+
38
+ def delete(record)
39
+ pstore.transaction do
40
+ pstore.map.delete record
41
+ end
42
+ end
43
+
44
+ private
45
+ def pstore
46
+ @pstore
47
+ end
48
+ end
49
+
50
+ def initialize(pstore)
51
+ @map = PStoreMap.new pstore
52
+ end
53
+ end
54
+ 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,55 @@
1
+ module Chassis
2
+ class RedisRepo < Chassis::BaseRepo
3
+ class RedisMap
4
+ def initialize(redis)
5
+ @redis = redis
6
+ end
7
+
8
+ def clear
9
+ redis.del key
10
+ end
11
+
12
+ def all(klass)
13
+ read.all klass
14
+ end
15
+
16
+ def get(klass, id)
17
+ read.get klass, id
18
+ end
19
+
20
+ def set(record)
21
+ map = read
22
+ map.set record
23
+ write map
24
+ end
25
+
26
+ def delete(record)
27
+ map = read
28
+ map.delete record
29
+ write map
30
+ end
31
+
32
+ private
33
+ def key
34
+ 'repo'
35
+ end
36
+
37
+ def redis
38
+ @redis
39
+ end
40
+
41
+ def read
42
+ value = redis.get key
43
+ value ? Marshal.load(value) : Repo::RecordMap.new
44
+ end
45
+
46
+ def write(map)
47
+ redis.set key, Marshal.dump(map)
48
+ end
49
+ end
50
+
51
+ def initialize(redis)
52
+ @map = RedisMap.new redis
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,52 @@
1
+ require 'json'
2
+ require 'yaml'
3
+
4
+ module Chassis
5
+ module Serializable
6
+ module ClassMethods
7
+ def from_hash(hash)
8
+ new.from_hash(hash)
9
+ end
10
+
11
+ def from_json(json)
12
+ from_hash JSON.load(json)
13
+ end
14
+
15
+ def from_yaml(yaml)
16
+ from_hash YAML.load(yaml)
17
+ end
18
+ end
19
+
20
+ class << self
21
+ def included(base)
22
+ base.extend ClassMethods
23
+ end
24
+ end
25
+
26
+ def marshal_dump
27
+ fail NotImplementedError, 'subclass must implement marshal_dump'
28
+ end
29
+
30
+ def marshal_load(hash)
31
+ fail NotImplementedError, 'subclass must implement marshal_load'
32
+ end
33
+
34
+ def to_hash
35
+ marshal_dump
36
+ end
37
+ alias_method :to_h, :to_hash
38
+
39
+ def to_json
40
+ JSON.dump marshal_dump
41
+ end
42
+
43
+ def to_yaml
44
+ YAML.dump marshal_dump
45
+ end
46
+
47
+ def from_hash(hash)
48
+ marshal_load HashUtils.symbolize(hash)
49
+ self
50
+ end
51
+ end
52
+ 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