elastics-client 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +28 -0
- data/VERSION +1 -0
- data/elastics-client.gemspec +33 -0
- data/lib/elastics.rb +108 -0
- data/lib/elastics/api_stubs.rb +1268 -0
- data/lib/elastics/api_templates/cluster_api.yml +94 -0
- data/lib/elastics/api_templates/core_api.yml +202 -0
- data/lib/elastics/api_templates/indices_api.yml +304 -0
- data/lib/elastics/class_proxy/base.rb +29 -0
- data/lib/elastics/class_proxy/templates.rb +97 -0
- data/lib/elastics/class_proxy/templates/doc.rb +91 -0
- data/lib/elastics/class_proxy/templates/search.rb +72 -0
- data/lib/elastics/configuration.rb +25 -0
- data/lib/elastics/deprecation.rb +153 -0
- data/lib/elastics/errors.rb +43 -0
- data/lib/elastics/http_clients/base.rb +15 -0
- data/lib/elastics/http_clients/loader.rb +51 -0
- data/lib/elastics/http_clients/patron.rb +29 -0
- data/lib/elastics/http_clients/rest_client.rb +36 -0
- data/lib/elastics/logger.rb +37 -0
- data/lib/elastics/prog_bar.rb +39 -0
- data/lib/elastics/rails.rb +1 -0
- data/lib/elastics/result.rb +24 -0
- data/lib/elastics/result/bulk.rb +20 -0
- data/lib/elastics/result/document.rb +67 -0
- data/lib/elastics/result/multi_get.rb +24 -0
- data/lib/elastics/result/search.rb +28 -0
- data/lib/elastics/struct/array.rb +25 -0
- data/lib/elastics/struct/hash.rb +105 -0
- data/lib/elastics/struct/paginable.rb +58 -0
- data/lib/elastics/struct/prunable.rb +60 -0
- data/lib/elastics/struct/symbolize.rb +27 -0
- data/lib/elastics/tasks.rb +62 -0
- data/lib/elastics/template.rb +124 -0
- data/lib/elastics/template/common.rb +42 -0
- data/lib/elastics/template/logger.rb +66 -0
- data/lib/elastics/template/partial.rb +28 -0
- data/lib/elastics/template/search.rb +30 -0
- data/lib/elastics/template/slim_search.rb +13 -0
- data/lib/elastics/template/tags.rb +56 -0
- data/lib/elastics/templates.rb +20 -0
- data/lib/elastics/utility_methods.rb +131 -0
- data/lib/elastics/utils.rb +103 -0
- data/lib/elastics/variables.rb +62 -0
- data/lib/tasks.rake +28 -0
- metadata +148 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
module Elastics
|
2
|
+
class Template
|
3
|
+
module Logger
|
4
|
+
|
5
|
+
def caller_line
|
6
|
+
caller.find{|l| l !~ /(#{LIB_PATHS.join('|')})/}
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def template_name
|
12
|
+
@host_elastics && @name && "#{@host_elastics.context}.#@name" || 'template'
|
13
|
+
end
|
14
|
+
|
15
|
+
def log_render(int, path, encoded_data, result)
|
16
|
+
logger = Conf.logger
|
17
|
+
return unless logger.is_a?(Elastics::Logger)
|
18
|
+
logger.info Dye.dye("Rendered #{template_name} from: #{caller_line}", :blue, :bold)
|
19
|
+
return unless logger.level == ::Logger::DEBUG
|
20
|
+
|
21
|
+
h = {}
|
22
|
+
if logger.debug_variables
|
23
|
+
h[:variables] = int[:vars] if int
|
24
|
+
end
|
25
|
+
if logger.debug_request
|
26
|
+
h[:request] = {}
|
27
|
+
h[:request][:method] = method
|
28
|
+
h[:request][:path] = path
|
29
|
+
h[:request][:data] = begin
|
30
|
+
MultiJson.decode(encoded_data) unless encoded_data.nil?
|
31
|
+
rescue MultiJson::DecodeError
|
32
|
+
encoded_data
|
33
|
+
end
|
34
|
+
h[:request].delete(:data) if h[:request][:data].nil?
|
35
|
+
end
|
36
|
+
if logger.debug_result
|
37
|
+
h[:result] = result if result
|
38
|
+
end
|
39
|
+
logger.debug logger.curl_format ? curl_format(h[:request]) : yaml_format(h)
|
40
|
+
end
|
41
|
+
|
42
|
+
def curl_format(h)
|
43
|
+
pretty = h[:path] =~ /\?/ ? '&pretty=1' : '?pretty=1'
|
44
|
+
curl = %(curl -X#{method} "#{Conf.base_uri}#{h[:path]}#{pretty}")
|
45
|
+
if h[:data]
|
46
|
+
data = h[:data].is_a?(String) ? h[:data] : MultiJson.encode(h[:data], :pretty => true)
|
47
|
+
curl << %( -d '\n#{data}\n')
|
48
|
+
end
|
49
|
+
curl
|
50
|
+
end
|
51
|
+
|
52
|
+
def yaml_format(hash)
|
53
|
+
hash.to_yaml.split("\n").map do |l|
|
54
|
+
case l
|
55
|
+
when /^---$/
|
56
|
+
when /^( |-)/
|
57
|
+
Dye.dye(l, :blue)
|
58
|
+
when /^:(variables|request|result)/
|
59
|
+
Dye.dye(l, :magenta, :bold) + (Dye.color ? Dye.sgr(:blue) : '')
|
60
|
+
end
|
61
|
+
end.compact.join("\n") + "\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Elastics
|
2
|
+
class Template
|
3
|
+
class Partial
|
4
|
+
|
5
|
+
include Common
|
6
|
+
|
7
|
+
def initialize(data)
|
8
|
+
@data = data
|
9
|
+
tags = Tags.new
|
10
|
+
stringified = tags.stringify(data)
|
11
|
+
@partials, @tags = tags.partial_and_tag_names
|
12
|
+
@tags_variables = tags.variables
|
13
|
+
instance_eval <<-ruby, __FILE__, __LINE__
|
14
|
+
def interpolate(vars={}, partial_assigned_vars={})
|
15
|
+
vars = Vars.new(vars, @tags_variables, partial_assigned_vars)
|
16
|
+
vars = interpolate_partials(vars)
|
17
|
+
#{stringified}
|
18
|
+
end
|
19
|
+
ruby
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_source
|
23
|
+
{@name.to_s => @data}.to_yaml
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Elastics
|
2
|
+
class Template
|
3
|
+
class Search < Template
|
4
|
+
|
5
|
+
def initialize(data, vars=nil)
|
6
|
+
super('GET', '/<<index>>/<<type>>/_search', data, vars)
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_a(*vars)
|
10
|
+
a = super
|
11
|
+
2.times{ a.delete_at 0 }
|
12
|
+
a
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_msearch(*vars)
|
16
|
+
vars = Vars.new(*vars)
|
17
|
+
int = interpolate(vars, strict=true)
|
18
|
+
header = {}
|
19
|
+
header[:index] = int[:vars][:index] if int[:vars][:index]
|
20
|
+
header[:type] = int[:vars][:type] if int[:vars][:type]
|
21
|
+
[:search_type, :preferences, :routing].each do |k|
|
22
|
+
header[k] = int[:vars][k] if int[:vars][k] || int[:vars][:params] && int[:vars][:params][k]
|
23
|
+
end
|
24
|
+
data, encoded = build_data(int, vars)
|
25
|
+
"#{MultiJson.encode(header)}\n#{encoded}\n"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Elastics
|
2
|
+
class Template
|
3
|
+
class SlimSearch < Search
|
4
|
+
|
5
|
+
# removes the fields param (no _source returned)
|
6
|
+
# the result.loaded_collection, will load the records from the db
|
7
|
+
def self.variables
|
8
|
+
super.deep_merge!(:params => {:fields => ''})
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Elastics
|
2
|
+
class Template
|
3
|
+
class Tags < Array
|
4
|
+
|
5
|
+
TAG_REGEXP = /<<\s*([\w\.]+)\s*(?:=([^>]*))*>>/
|
6
|
+
|
7
|
+
# tag variables are the defaults defined with the tag
|
8
|
+
# a variable could be optional, and the default could be nil
|
9
|
+
def variables
|
10
|
+
tag_variables = Vars.new
|
11
|
+
each do |t|
|
12
|
+
if t.default || t.optional
|
13
|
+
if t.name =~ /\./ # set default for nested var
|
14
|
+
tag_variables.store_nested(t.name, t.default)
|
15
|
+
else
|
16
|
+
tag_variables[t.name] = t.default
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
tag_variables
|
21
|
+
end
|
22
|
+
|
23
|
+
def stringify(structure)
|
24
|
+
structure.inspect.gsub(/(?:"#{TAG_REGEXP}"|#{TAG_REGEXP})/) do
|
25
|
+
match = $&
|
26
|
+
match =~ TAG_REGEXP
|
27
|
+
t = Tag.new($1, $2)
|
28
|
+
push t unless find{|i| i.name == t.name}
|
29
|
+
(match !~ /^"/) ? "\#{vars.get_prunable(:'#{t.name}')}" : "vars.get_prunable(:'#{t.name}')"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def partial_and_tag_names
|
34
|
+
map(&:name).partition{|n| n.to_s =~ /^_/}
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class Tag
|
40
|
+
|
41
|
+
RESERVED = [:context, :path, :data, :params, :no_pruning, :raw_result, :raise]
|
42
|
+
|
43
|
+
attr_reader :optional, :name, :default
|
44
|
+
|
45
|
+
def initialize(name, default)
|
46
|
+
raise SourceError, ":#{name} is a reserved symbol and cannot be used as a tag name" \
|
47
|
+
if RESERVED.include?(name)
|
48
|
+
@name = name.to_sym
|
49
|
+
@optional = !!default
|
50
|
+
@default = YAML.load(default) unless default.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Elastics
|
2
|
+
module Templates
|
3
|
+
|
4
|
+
extend self
|
5
|
+
attr_accessor :contexts
|
6
|
+
@contexts = []
|
7
|
+
|
8
|
+
def self.included(context)
|
9
|
+
context.class_eval do
|
10
|
+
Elastics::Templates.contexts |= [context]
|
11
|
+
@elastics ||= ClassProxy::Base.new(context)
|
12
|
+
@elastics.extend(ClassProxy::Templates).init
|
13
|
+
def self.elastics; @elastics end
|
14
|
+
def self.template_methods; elastics.templates.keys end
|
15
|
+
eval "extend module #{context}::ElasticsTemplateMethods; self end"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Elastics
|
2
|
+
module UtilityMethods
|
3
|
+
|
4
|
+
def search(data, vars={})
|
5
|
+
Template::Search.new(data).setup(Elastics.elastics).render(vars)
|
6
|
+
end
|
7
|
+
|
8
|
+
# like Elastics.search, but it will use the Elastics::Template::SlimSearch instead
|
9
|
+
def slim_search(data, vars={})
|
10
|
+
Template::SlimSearch.new(data).setup(Elastics.elastics).render(vars)
|
11
|
+
end
|
12
|
+
|
13
|
+
%w[HEAD GET PUT POST DELETE].each do |m|
|
14
|
+
class_eval <<-ruby, __FILE__, __LINE__
|
15
|
+
def #{m}(*args)
|
16
|
+
perform '#{m}', *args
|
17
|
+
end
|
18
|
+
ruby
|
19
|
+
end
|
20
|
+
|
21
|
+
def json2yaml(json)
|
22
|
+
YAML.dump(MultiJson.decode(json))
|
23
|
+
end
|
24
|
+
|
25
|
+
def yaml2json(yaml)
|
26
|
+
MultiJson.encode(YAML.load(yaml))
|
27
|
+
end
|
28
|
+
|
29
|
+
def reload!
|
30
|
+
elastics.variables.deep_merge! Conf.variables
|
31
|
+
Templates.contexts.each {|c| c.elastics.reload!}
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def doc(*args)
|
36
|
+
elastics.doc(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def scan_search(*args, &block)
|
40
|
+
elastics.scan_search(*args, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def scan_all(*vars, &block)
|
44
|
+
elastics.scan_search(:match_all, *vars) do |raw_result|
|
45
|
+
batch = raw_result['hits']['hits']
|
46
|
+
block.call(batch)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def dump_all(*vars, &block)
|
51
|
+
refresh_index(*vars)
|
52
|
+
scan_all({:params => {:fields => '*,_source'}}, *vars) do |batch|
|
53
|
+
batch.map!{|document| document.delete('_score'); document}
|
54
|
+
block.call(batch)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# refresh and pull the full document from the index
|
59
|
+
def dump_one(*vars)
|
60
|
+
refresh_index(*vars)
|
61
|
+
document = search_by_id({:params => {:fields => '*,_source'}}, *vars)
|
62
|
+
document.delete('_score')
|
63
|
+
document
|
64
|
+
end
|
65
|
+
|
66
|
+
# You should use Elastics.post_bulk_string if you have an already formatted bulk data-string
|
67
|
+
def post_bulk_collection(collection, options={})
|
68
|
+
raise ArgumentError, "Array expected as :collection, got #{collection.inspect}" \
|
69
|
+
unless collection.is_a?(Array)
|
70
|
+
bulk_string = ''
|
71
|
+
collection.each do |d|
|
72
|
+
bulk_string << build_bulk_string(d, options)
|
73
|
+
end
|
74
|
+
post_bulk_string(:bulk_string => bulk_string) unless bulk_string.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_bulk_string(document, options={})
|
78
|
+
case document
|
79
|
+
when Hash
|
80
|
+
bulk_string_from_hash(document, options)
|
81
|
+
when Elastics::ModelIndexer, Elastics::ActiveModel
|
82
|
+
bulk_string_from_elastics(document, options)
|
83
|
+
else
|
84
|
+
raise NotImplementedError, "Unable to convert the document #{document.inspect} to a bulk string."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def perform(*args)
|
91
|
+
Template.new(*args).setup(Elastics.elastics).render
|
92
|
+
end
|
93
|
+
|
94
|
+
def bulk_string_from_hash(document, options)
|
95
|
+
meta = Utils.slice_hash(document, '_index', '_type', '_id')
|
96
|
+
if document.has_key?('fields')
|
97
|
+
document['fields'].each do |k, v|
|
98
|
+
meta[k] = v if k[0] == '_'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
source = document['_source'] unless options[:action] == 'delete'
|
102
|
+
to_bulk_string(meta, source, options)
|
103
|
+
end
|
104
|
+
|
105
|
+
def bulk_string_from_elastics(document, options)
|
106
|
+
elastics = document.elastics
|
107
|
+
return '' unless document.elastics_indexable?
|
108
|
+
meta = { '_index' => elastics.index,
|
109
|
+
'_type' => elastics.type,
|
110
|
+
'_id' => elastics.id }
|
111
|
+
meta['_parent'] = elastics.parent if elastics.parent
|
112
|
+
meta['_routing'] = elastics.routing if elastics.routing
|
113
|
+
source = document.elastics_source unless options[:action] == 'delete'
|
114
|
+
to_bulk_string(meta, source, options)
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_bulk_string(meta, source, options)
|
118
|
+
action = options[:action] || 'index'
|
119
|
+
return '' if source.nil? || source.empty? &&! (action == 'delete')
|
120
|
+
meta['_index'] = LiveReindex.prefix_index(meta['_index']) if LiveReindex.should_prefix_index?
|
121
|
+
bulk_string = MultiJson.encode(action => meta) + "\n"
|
122
|
+
unless action == 'delete'
|
123
|
+
source_line = source.is_a?(String) ? source : MultiJson.encode(source)
|
124
|
+
return '' if source.nil? || source.empty?
|
125
|
+
bulk_string << source_line + "\n"
|
126
|
+
end
|
127
|
+
bulk_string
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Elastics
|
2
|
+
module Utils
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def parse_source(source)
|
6
|
+
return unless source
|
7
|
+
parsed = case source
|
8
|
+
when Hash then keyfy(:to_s, source)
|
9
|
+
when /^\s*\{.+\}\s*$/m then source
|
10
|
+
when String then YAML.load(source)
|
11
|
+
else raise ArgumentError, "expected a String or Hash instance, got #{source.inspect}"
|
12
|
+
end
|
13
|
+
raise ArgumentError, "the source does not decode to an Array, Hash or String, got #{parsed.inspect}" \
|
14
|
+
unless parsed.is_a?(Hash) || parsed.is_a?(Array) || parsed.is_a?(String)
|
15
|
+
parsed
|
16
|
+
end
|
17
|
+
|
18
|
+
def erb_process(source)
|
19
|
+
varname = "_elastics_#{source.hash.to_s.tr('-', '_')}"
|
20
|
+
ERB.new(File.read(source), nil, nil, varname).result
|
21
|
+
end
|
22
|
+
|
23
|
+
def group_array_by(ary)
|
24
|
+
h = {}
|
25
|
+
ary.each do |i|
|
26
|
+
k = yield i
|
27
|
+
if h.has_key?(k)
|
28
|
+
h[k] << i
|
29
|
+
else
|
30
|
+
h[k] = [i]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
h
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete_allcaps_keys(hash)
|
37
|
+
hash.keys.each { |k| hash.delete(k) if k =~ /^[A-Z_]+$/ }
|
38
|
+
hash
|
39
|
+
end
|
40
|
+
|
41
|
+
def keyfy(to_what, obj)
|
42
|
+
case obj
|
43
|
+
when Hash
|
44
|
+
h = {}
|
45
|
+
obj.each do |k,v|
|
46
|
+
h[k.send(to_what)] = keyfy(to_what, v)
|
47
|
+
end
|
48
|
+
h
|
49
|
+
when Array
|
50
|
+
obj.map{|i| keyfy(to_what, i)}
|
51
|
+
else
|
52
|
+
obj
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def slice_hash(hash, *keys)
|
57
|
+
h = {}
|
58
|
+
keys.each{|k| h[k] = hash[k] if hash.has_key?(k)}
|
59
|
+
h
|
60
|
+
end
|
61
|
+
|
62
|
+
def env2options(*keys)
|
63
|
+
options = {}
|
64
|
+
ENV.keys.map do |k|
|
65
|
+
key = k.downcase.to_sym
|
66
|
+
options[key] = ENV[k] if keys.include?(key)
|
67
|
+
end
|
68
|
+
options
|
69
|
+
end
|
70
|
+
|
71
|
+
def define_delegation(opts)
|
72
|
+
file, line = caller.first.split(':', 2)
|
73
|
+
line = line.to_i
|
74
|
+
|
75
|
+
obj, meth, methods, to = opts[:in], opts[:by], opts[:for], opts[:to]
|
76
|
+
|
77
|
+
methods.each do |method|
|
78
|
+
obj.send meth, <<-method, file, line - 1
|
79
|
+
def #{method}(*args, &block) # def method_name(*args, &block)
|
80
|
+
if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
|
81
|
+
#{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
|
82
|
+
end # end
|
83
|
+
end # end
|
84
|
+
method
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
def class_name_to_type(class_name)
|
90
|
+
type = class_name.tr(':', '_')
|
91
|
+
type.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
92
|
+
type.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
93
|
+
type.downcase!
|
94
|
+
type
|
95
|
+
end
|
96
|
+
|
97
|
+
def type_to_class_name(type)
|
98
|
+
type.gsub(/__(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|