elastics-client 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +28 -0
  3. data/VERSION +1 -0
  4. data/elastics-client.gemspec +33 -0
  5. data/lib/elastics.rb +108 -0
  6. data/lib/elastics/api_stubs.rb +1268 -0
  7. data/lib/elastics/api_templates/cluster_api.yml +94 -0
  8. data/lib/elastics/api_templates/core_api.yml +202 -0
  9. data/lib/elastics/api_templates/indices_api.yml +304 -0
  10. data/lib/elastics/class_proxy/base.rb +29 -0
  11. data/lib/elastics/class_proxy/templates.rb +97 -0
  12. data/lib/elastics/class_proxy/templates/doc.rb +91 -0
  13. data/lib/elastics/class_proxy/templates/search.rb +72 -0
  14. data/lib/elastics/configuration.rb +25 -0
  15. data/lib/elastics/deprecation.rb +153 -0
  16. data/lib/elastics/errors.rb +43 -0
  17. data/lib/elastics/http_clients/base.rb +15 -0
  18. data/lib/elastics/http_clients/loader.rb +51 -0
  19. data/lib/elastics/http_clients/patron.rb +29 -0
  20. data/lib/elastics/http_clients/rest_client.rb +36 -0
  21. data/lib/elastics/logger.rb +37 -0
  22. data/lib/elastics/prog_bar.rb +39 -0
  23. data/lib/elastics/rails.rb +1 -0
  24. data/lib/elastics/result.rb +24 -0
  25. data/lib/elastics/result/bulk.rb +20 -0
  26. data/lib/elastics/result/document.rb +67 -0
  27. data/lib/elastics/result/multi_get.rb +24 -0
  28. data/lib/elastics/result/search.rb +28 -0
  29. data/lib/elastics/struct/array.rb +25 -0
  30. data/lib/elastics/struct/hash.rb +105 -0
  31. data/lib/elastics/struct/paginable.rb +58 -0
  32. data/lib/elastics/struct/prunable.rb +60 -0
  33. data/lib/elastics/struct/symbolize.rb +27 -0
  34. data/lib/elastics/tasks.rb +62 -0
  35. data/lib/elastics/template.rb +124 -0
  36. data/lib/elastics/template/common.rb +42 -0
  37. data/lib/elastics/template/logger.rb +66 -0
  38. data/lib/elastics/template/partial.rb +28 -0
  39. data/lib/elastics/template/search.rb +30 -0
  40. data/lib/elastics/template/slim_search.rb +13 -0
  41. data/lib/elastics/template/tags.rb +56 -0
  42. data/lib/elastics/templates.rb +20 -0
  43. data/lib/elastics/utility_methods.rb +131 -0
  44. data/lib/elastics/utils.rb +103 -0
  45. data/lib/elastics/variables.rb +62 -0
  46. data/lib/tasks.rake +28 -0
  47. 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