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