rrrmatey 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +16 -0
  3. data/README.md +249 -0
  4. data/RELEASE_NOTES.md +26 -0
  5. data/Rakefile +39 -0
  6. data/lib/rrrmatey.rb +8 -0
  7. data/lib/rrrmatey/crud_controller.rb +97 -0
  8. data/lib/rrrmatey/discrete_result.rb +31 -0
  9. data/lib/rrrmatey/errors.rb +11 -0
  10. data/lib/rrrmatey/retryable.rb +28 -0
  11. data/lib/rrrmatey/string_model/connection_methods.rb +47 -0
  12. data/lib/rrrmatey/string_model/consumer_adapter_methods.rb +9 -0
  13. data/lib/rrrmatey/string_model/delete_methods.rb +12 -0
  14. data/lib/rrrmatey/string_model/field_definition_methods.rb +101 -0
  15. data/lib/rrrmatey/string_model/find_methods.rb +85 -0
  16. data/lib/rrrmatey/string_model/index_methods.rb +90 -0
  17. data/lib/rrrmatey/string_model/namespaced_key_methods.rb +21 -0
  18. data/lib/rrrmatey/string_model/string_model.rb +92 -0
  19. data/lib/rrrmatey/type_coercion.rb +61 -0
  20. data/lib/rrrmatey/version.rb +3 -0
  21. data/spec/rrrmatey/crud_controller/crud_controller_spec.rb +258 -0
  22. data/spec/rrrmatey/crud_controller/model_methods_spec.rb +26 -0
  23. data/spec/rrrmatey/discrete_result_spec.rb +104 -0
  24. data/spec/rrrmatey/retryable_spec.rb +95 -0
  25. data/spec/rrrmatey/string_model/connection_methods_spec.rb +64 -0
  26. data/spec/rrrmatey/string_model/consumer_adapter_methods_spec.rb +43 -0
  27. data/spec/rrrmatey/string_model/delete_methods_spec.rb +36 -0
  28. data/spec/rrrmatey/string_model/field_definition_methods_spec.rb +51 -0
  29. data/spec/rrrmatey/string_model/find_methods_spec.rb +147 -0
  30. data/spec/rrrmatey/string_model/index_methods_spec.rb +231 -0
  31. data/spec/rrrmatey/string_model/namespaced_key_methods_spec.rb +34 -0
  32. data/spec/rrrmatey/string_model/string_model_spec.rb +208 -0
  33. data/spec/spec_helper.rb +16 -0
  34. metadata +148 -0
@@ -0,0 +1,31 @@
1
+ module RRRMatey
2
+ class DiscreteResult
3
+ attr_reader :offset, :discrete_length, :length, :results
4
+
5
+ def initialize(opts = {})
6
+ @results = opts[:results].to_a
7
+ @length = opts[:length]
8
+ @offset = opts[:offset]
9
+ @discrete_length = opts[:discrete_length]
10
+ end
11
+
12
+ def to_json(opts = {})
13
+ to_consumer_hash.to_json
14
+ end
15
+
16
+ def to_xml(opts = {})
17
+ to_consumer_hash.to_xml
18
+ end
19
+
20
+ private
21
+
22
+ def to_consumer_hash
23
+ {
24
+ :length => length,
25
+ :offset => offset,
26
+ :limit => discrete_length,
27
+ :results => results.map { |it| it.send(:to_consumer_hash) }
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ module RRRMatey
2
+ class PlankError < StandardError; end
3
+ class UnknownContentTypeError < PlankError; end
4
+ class UnparseableContentError < PlankError; end
5
+ class InvalidModelError < PlankError; end
6
+ class UnsupportedTypeError < PlankError
7
+ def initialize(type)
8
+ super("Unsupported type: #{type}")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ module RRRMatey
2
+ class Retryable
3
+ def initialize(conn, opts = {})
4
+ @retries = opts[:retries] || 3
5
+ @retry_delay = opts[:retry_delay] || 0.1
6
+ @conn = conn
7
+ @conn_respond_to_with = conn.respond_to?(:with)
8
+ end
9
+
10
+ def with(&block)
11
+ return unless block_given?
12
+ ex = nil
13
+ 1.upto(@retries) do
14
+ begin
15
+ if @conn_respond_to_with
16
+ return @conn.with { |conn| block.call(conn) }
17
+ else
18
+ return block.call(@conn)
19
+ end
20
+ rescue StandardError => e
21
+ ex = e
22
+ sleep(@retry_delay)
23
+ end
24
+ end
25
+ raise ex
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ module RRRMatey
2
+ module StringModel
3
+ module ConnectionMethods
4
+ def cache_proxy()
5
+ if @cache_proxy.nil? && self.name != RRRMatey::StringModel.name
6
+ return StringModel.cache_proxy
7
+ end
8
+ @cache_proxy
9
+ end
10
+
11
+ def cache_proxy=(v)
12
+ @cache_proxy = v
13
+ end
14
+
15
+ def riak()
16
+ if @riak.nil? && self.name != RRRMatey::StringModel.name
17
+ return StringModel.riak
18
+ end
19
+ @riak
20
+ end
21
+
22
+ def riak=(v)
23
+ @riak = v
24
+ unless @riak.nil?
25
+ ensure_search_index()
26
+ end
27
+ @riak
28
+ end
29
+
30
+ private
31
+
32
+ def ensure_search_index()
33
+ return unless respond_to_index_name?
34
+ bucket = riak.with { |r|
35
+ r.create_search_index(index_name, '_yz_default')
36
+ r.bucket(namespace)
37
+ }
38
+ bucket.properties = { 'search_index' => index_name }
39
+ end
40
+
41
+ def respond_to_index_name?()
42
+ @respond_to_index_name ||= respond_to?(:index_name)
43
+ end
44
+ end
45
+ self.extend ConnectionMethods
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ module RRRMatey
2
+ module StringModel
3
+ module ConsumerAdapterMethods
4
+ def from_params(params, id = nil, content_type = nil)
5
+ send(:object_from_hash, params, id || params[:id], content_type || :json)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module RRRMatey
2
+ module StringModel
3
+ module DeleteMethods
4
+ def delete(id)
5
+ return 0 if cache_proxy.nil?
6
+ cache_proxy.with { |r|
7
+ r.del(self.namespaced_key(id))
8
+ }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,101 @@
1
+ module RRRMatey
2
+ module StringModel
3
+ module FieldDefinitionMethods
4
+ def fields(*field_syms)
5
+ if field_syms.blank?
6
+ @fields
7
+ else
8
+ @fields =
9
+ field_syms.each { |field_sym| attr_accessor field_sym }
10
+
11
+ define_method('initialize') do |opts={}|
12
+ opts.each do |k,v|
13
+ send("#{k}=", v)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ def consumer_fields
20
+ @consumer_fields || []
21
+ end
22
+
23
+ def field(field_sym, opts = {})
24
+ fs = @fields || []
25
+ type = opts[:type] || :string
26
+ default = opts[:default]
27
+ f = append_type(field_sym, type)
28
+
29
+ cfs = self.consumer_fields
30
+ cfs << field_sym
31
+ @consumer_fields = cfs
32
+
33
+ fs << f[:name]
34
+ self.fields(*fs)
35
+
36
+ define_method(field_sym) do
37
+ v = send(f[:name]) || default
38
+ v.send(f[:from_underlying])
39
+ end
40
+ define_method("#{field_sym}=") do |v|
41
+ v = v.send(f[:to_underlying] || :to_s)
42
+ send("#{f[:name]}=", v)
43
+ end
44
+ nil
45
+ end
46
+
47
+ private
48
+
49
+ def append_type(field_sym, type)
50
+ tms = type_mappings()
51
+ unless tms.has_key?(type)
52
+ raise UnsupportedTypeError.new(field_sym)
53
+ end
54
+ tm = tms[type]
55
+ tm[:name] = "#{field_sym}#{tm[:suffix]}".to_sym
56
+ tm
57
+ end
58
+
59
+ def type_mappings()
60
+ {
61
+ :string =>
62
+ {
63
+ :suffix => '_s',
64
+ :from_underlying => :to_s
65
+ },
66
+ :boolean =>
67
+ {
68
+ :suffix => '_b',
69
+ :from_underlying => :to_b
70
+ },
71
+ :date =>
72
+ {
73
+ :suffix => '_ti',
74
+ :from_underlying => :to_fixnum_to_date,
75
+ :to_underlying => :seconds_since_epoch
76
+ },
77
+ :integer =>
78
+ {
79
+ :suffix => '_i',
80
+ :from_underlying => :to_i
81
+ },
82
+ :long =>
83
+ {
84
+ :suffix => '_l',
85
+ :from_underlying => :to_i
86
+ },
87
+ :double =>
88
+ {
89
+ :suffix => '_d',
90
+ :from_underlying => :to_f
91
+ },
92
+ :float =>
93
+ {
94
+ :suffix => '_f',
95
+ :from_underlying => :to_f
96
+ }
97
+ }
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,85 @@
1
+ module RRRMatey
2
+ module StringModel
3
+ module FindMethods
4
+ def get(id)
5
+ return if id.blank? || self.cache_proxy.nil?
6
+ s = nil
7
+ self.cache_proxy.with { |r|
8
+ s = r.get(namespaced_key(id))
9
+ }
10
+
11
+ type = interpolate_type(s)
12
+ h = typed_string_to_hash(s, type)
13
+ o = object_from_hash(h, id, type)
14
+ if o.content_type == 'application/unknown'
15
+ o.content_type = 'application/json'
16
+ end
17
+ o
18
+ end
19
+
20
+ private
21
+
22
+ def interpolate_type(s)
23
+ if s.blank? || s.start_with?('{') && s.end_with?('}')
24
+ :json
25
+ elsif s.start_with?('<') && s.end_with?('>')
26
+ :xml
27
+ else
28
+ :string
29
+ end
30
+ end
31
+
32
+ def content_type_from_type(type)
33
+ case type
34
+ when :json
35
+ 'application/json'
36
+ when :xml
37
+ 'application/xml'
38
+ # NOTE: unreachable since typed_string_to_hash throws for unknown
39
+ # else
40
+ # 'application/unknown'
41
+ end
42
+ end
43
+
44
+ def typed_string_to_hash(s, type)
45
+ return {} if s.blank?
46
+ case type
47
+ when :json
48
+ JSON.parse(s)
49
+ when :xml
50
+ Hash.from_xml(s)
51
+ else
52
+ raise UnknownContentTypeError
53
+ end
54
+ rescue
55
+ raise UnparseableContentError
56
+ end
57
+
58
+ def object_from_hash(h, id, type)
59
+ o = new
60
+ o.id = id
61
+ o.content_type = content_type_from_type(type)
62
+
63
+ h = h[self.item_name]
64
+ if h.nil?
65
+ # ie, search result with extractor missing
66
+ o.content_type = 'application/unknown'
67
+ else
68
+ consumer_fields.each do |f|
69
+ fs = f.to_s
70
+ if h.has_key?(fs)
71
+ o.send("#{f}=", h[fs])
72
+ end
73
+ end
74
+ fields.each do |f|
75
+ fs = f.to_s
76
+ if h.has_key?(fs)
77
+ o.send("#{f}=", h[fs])
78
+ end
79
+ end
80
+ end
81
+ o
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,90 @@
1
+ module RRRMatey
2
+ module StringModel
3
+ module IndexMethods
4
+ def index_name()
5
+ @index_name ||= self.namespace
6
+ end
7
+
8
+ def index_name=(v)
9
+ @index_name = v
10
+ end
11
+
12
+ def list(offset = 0, limit = 20)
13
+ index_by_index_term('*:*', offset, limit)
14
+ end
15
+
16
+ def list_by(offset = 0, limit = 20, field_qs = {})
17
+ index_term = field_qs_to_index_term(field_qs)
18
+ index_by_index_term(index_term, offset, limit)
19
+ end
20
+
21
+ private
22
+
23
+ def index_by_index_term(index_term, offset, limit)
24
+ search_result = nil
25
+ unless self.riak.nil?
26
+ self.riak.with { |r|
27
+ # for consistent result, sort by key
28
+ search_result = r.search(self.index_name,
29
+ index_term,
30
+ {:start => offset,
31
+ :rows => limit,
32
+ :sort => '_yz_rk asc'})
33
+ }
34
+ end
35
+ search_result_to_discrete_result(search_result, offset, limit)
36
+ end
37
+
38
+ def search_result_to_discrete_result(search_result, offset, limit)
39
+ search_result = {} if search_result.nil?
40
+
41
+ if search_result['docs'].blank?
42
+ results = []
43
+ else
44
+ results = search_result['docs'].map { |h|
45
+ if h.nil?
46
+ nil
47
+ else
48
+ id = h['_yz_rk']
49
+ h = normalize_hash(h)
50
+ object_from_hash(h, id, :json)
51
+ end
52
+ }
53
+ end
54
+ DiscreteResult.new(:length => search_result['num_found'] || 0,
55
+ :offset => offset,
56
+ :discrete_length => limit,
57
+ :results => results)
58
+ end
59
+
60
+ def item_name_prefix()
61
+ @item_name_prefix ||= "#{self.item_name}."
62
+ end
63
+
64
+ def normalize_hash(h)
65
+ h_norm = {}
66
+ item_name_prefix_len = item_name_prefix.length
67
+ h.each do |k,v|
68
+ if k.start_with?(item_name_prefix)
69
+ normalized_key = k[item_name_prefix_len..-1]
70
+ h_norm[normalized_key] = v
71
+ end
72
+ end
73
+ return {} if h_norm.length <= 0
74
+ { self.item_name => h_norm }
75
+ end
76
+
77
+ def field_qs_to_index_term(field_qs)
78
+ return "*:*" if field_qs.blank?
79
+ field_qs.reduce("") do |q, (k, v)|
80
+ q_part = "#{item_name_prefix}#{k}:#{v}"
81
+ if q.blank?
82
+ q += q_part
83
+ else
84
+ q += " OR #{q_part}"
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,21 @@
1
+ module RRRMatey
2
+ module StringModel
3
+ module NamespacedKeyMethods
4
+ def item_name()
5
+ @item_name ||= name.underscore
6
+ end
7
+
8
+ def item_name=(v)
9
+ @item_name = v
10
+ end
11
+
12
+ def namespace()
13
+ @namespace ||= item_name.pluralize
14
+ end
15
+
16
+ def namespaced_key(id)
17
+ "#{namespace}:#{id}"
18
+ end
19
+ end
20
+ end
21
+ end