rrrmatey 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.
- checksums.yaml +7 -0
- data/LICENSE.md +16 -0
- data/README.md +249 -0
- data/RELEASE_NOTES.md +26 -0
- data/Rakefile +39 -0
- data/lib/rrrmatey.rb +8 -0
- data/lib/rrrmatey/crud_controller.rb +97 -0
- data/lib/rrrmatey/discrete_result.rb +31 -0
- data/lib/rrrmatey/errors.rb +11 -0
- data/lib/rrrmatey/retryable.rb +28 -0
- data/lib/rrrmatey/string_model/connection_methods.rb +47 -0
- data/lib/rrrmatey/string_model/consumer_adapter_methods.rb +9 -0
- data/lib/rrrmatey/string_model/delete_methods.rb +12 -0
- data/lib/rrrmatey/string_model/field_definition_methods.rb +101 -0
- data/lib/rrrmatey/string_model/find_methods.rb +85 -0
- data/lib/rrrmatey/string_model/index_methods.rb +90 -0
- data/lib/rrrmatey/string_model/namespaced_key_methods.rb +21 -0
- data/lib/rrrmatey/string_model/string_model.rb +92 -0
- data/lib/rrrmatey/type_coercion.rb +61 -0
- data/lib/rrrmatey/version.rb +3 -0
- data/spec/rrrmatey/crud_controller/crud_controller_spec.rb +258 -0
- data/spec/rrrmatey/crud_controller/model_methods_spec.rb +26 -0
- data/spec/rrrmatey/discrete_result_spec.rb +104 -0
- data/spec/rrrmatey/retryable_spec.rb +95 -0
- data/spec/rrrmatey/string_model/connection_methods_spec.rb +64 -0
- data/spec/rrrmatey/string_model/consumer_adapter_methods_spec.rb +43 -0
- data/spec/rrrmatey/string_model/delete_methods_spec.rb +36 -0
- data/spec/rrrmatey/string_model/field_definition_methods_spec.rb +51 -0
- data/spec/rrrmatey/string_model/find_methods_spec.rb +147 -0
- data/spec/rrrmatey/string_model/index_methods_spec.rb +231 -0
- data/spec/rrrmatey/string_model/namespaced_key_methods_spec.rb +34 -0
- data/spec/rrrmatey/string_model/string_model_spec.rb +208 -0
- data/spec/spec_helper.rb +16 -0
- 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,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
|