flex 0.4.2 → 1.0.1

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.
Files changed (78) hide show
  1. data/LICENSE +1 -1
  2. data/README.md +46 -7
  3. data/VERSION +1 -1
  4. data/flex.gemspec +13 -19
  5. data/lib/flex.rb +66 -392
  6. data/lib/flex/api_stubs.rb +1268 -0
  7. data/lib/flex/api_templates/cluster_api.yml +94 -0
  8. data/lib/flex/api_templates/core_api.yml +202 -0
  9. data/lib/flex/api_templates/indices_api.yml +304 -0
  10. data/lib/flex/class_proxy/base.rb +20 -6
  11. data/lib/flex/class_proxy/templates.rb +97 -0
  12. data/lib/flex/class_proxy/templates/doc.rb +91 -0
  13. data/lib/flex/class_proxy/templates/search.rb +72 -0
  14. data/lib/flex/configuration.rb +17 -49
  15. data/lib/flex/deprecation.rb +153 -0
  16. data/lib/flex/errors.rb +2 -1
  17. data/lib/flex/http_clients/base.rb +15 -0
  18. data/lib/flex/http_clients/loader.rb +51 -0
  19. data/lib/flex/http_clients/patron.rb +9 -7
  20. data/lib/flex/http_clients/rest_client.rb +6 -8
  21. data/lib/flex/logger.rb +24 -3
  22. data/lib/flex/prog_bar.rb +11 -6
  23. data/lib/flex/rails.rb +1 -13
  24. data/lib/flex/result.rb +8 -2
  25. data/lib/flex/result/document.rb +42 -13
  26. data/lib/flex/result/multi_get.rb +24 -0
  27. data/lib/flex/result/search.rb +1 -24
  28. data/lib/flex/struct/array.rb +25 -0
  29. data/lib/flex/struct/hash.rb +105 -0
  30. data/lib/flex/{result/collection.rb → struct/paginable.rb} +16 -9
  31. data/lib/flex/struct/prunable.rb +60 -0
  32. data/lib/flex/struct/symbolize.rb +27 -0
  33. data/lib/flex/tasks.rb +26 -86
  34. data/lib/flex/template.rb +60 -120
  35. data/lib/flex/template/common.rb +42 -0
  36. data/lib/flex/template/logger.rb +66 -0
  37. data/lib/flex/template/partial.rb +12 -15
  38. data/lib/flex/template/search.rb +6 -6
  39. data/lib/flex/template/slim_search.rb +1 -1
  40. data/lib/flex/template/tags.rb +19 -9
  41. data/lib/flex/templates.rb +20 -0
  42. data/lib/flex/utility_methods.rb +80 -89
  43. data/lib/flex/utils.rb +68 -25
  44. data/lib/flex/variables.rb +55 -4
  45. data/lib/tasks.rake +28 -0
  46. metadata +61 -85
  47. data/bin/flexes +0 -174
  48. data/lib/flex/api_methods.yml +0 -108
  49. data/lib/flex/class_proxy/loader.rb +0 -102
  50. data/lib/flex/class_proxy/model.rb +0 -45
  51. data/lib/flex/class_proxy/model_sync.rb +0 -23
  52. data/lib/flex/class_proxy/related_model.rb +0 -23
  53. data/lib/flex/instance_proxy/base.rb +0 -29
  54. data/lib/flex/instance_proxy/model.rb +0 -102
  55. data/lib/flex/instance_proxy/related_model.rb +0 -7
  56. data/lib/flex/loader.rb +0 -18
  57. data/lib/flex/manager.rb +0 -61
  58. data/lib/flex/model.rb +0 -24
  59. data/lib/flex/rails/engine.rb +0 -23
  60. data/lib/flex/rails/helper.rb +0 -16
  61. data/lib/flex/related_model.rb +0 -16
  62. data/lib/flex/result/indifferent_access.rb +0 -11
  63. data/lib/flex/result/source_document.rb +0 -63
  64. data/lib/flex/result/source_search.rb +0 -32
  65. data/lib/flex/structure/indifferent_access.rb +0 -44
  66. data/lib/flex/structure/mergeable.rb +0 -21
  67. data/lib/flex/template/base.rb +0 -41
  68. data/lib/flex/template/info.rb +0 -68
  69. data/lib/generators/flex/setup/setup_generator.rb +0 -48
  70. data/lib/generators/flex/setup/templates/flex_config.yml +0 -16
  71. data/lib/generators/flex/setup/templates/flex_dir/es.rb.erb +0 -18
  72. data/lib/generators/flex/setup/templates/flex_dir/es.yml.erb +0 -19
  73. data/lib/generators/flex/setup/templates/flex_dir/es_extender.rb.erb +0 -17
  74. data/lib/generators/flex/setup/templates/flex_initializer.rb.erb +0 -44
  75. data/lib/tasks/index.rake +0 -17
  76. data/test/flex.irt +0 -143
  77. data/test/flex/configuration.irt +0 -53
  78. data/test/irt_helper.rb +0 -12
@@ -1,16 +1,18 @@
1
1
  module Flex
2
- class Result
3
- module Collection
2
+ module Struct
3
+ module Paginable
4
4
 
5
- attr_reader :total_entries, :variables
5
+ attr_accessor :total_entries, :variables
6
6
 
7
7
  def setup(total_entries, variables)
8
8
  @total_entries = total_entries
9
9
  @variables = variables
10
+ self
10
11
  end
11
12
 
12
13
  def per_page
13
- (@variables[:per_page] || @variables[:params] && @variables[:params][:size] || @variables[:size] || 10).to_i
14
+ (@variables[:per_page] || @variables[:limit_value] ||
15
+ @variables[:params] && @variables[:params][:size] || 10).to_i
14
16
  end
15
17
 
16
18
  def total_pages
@@ -18,11 +20,7 @@ module Flex
18
20
  end
19
21
 
20
22
  def current_page
21
- if @variables[:page]
22
- @variables[:page].to_i
23
- else
24
- (per_page + (@variables[:from]||0).to_i) / per_page
25
- end
23
+ (@variables[:page] || @variables[:current_page] || 1).to_i
26
24
  end
27
25
 
28
26
  def previous_page
@@ -33,6 +31,14 @@ module Flex
33
31
  current_page < total_pages ? (current_page + 1) : nil
34
32
  end
35
33
 
34
+ def last_page?
35
+ total_pages == current_page
36
+ end
37
+
38
+ def first_page?
39
+ current_page == 1
40
+ end
41
+
36
42
  def offset
37
43
  per_page * (current_page - 1)
38
44
  end
@@ -48,4 +54,5 @@ module Flex
48
54
 
49
55
  end
50
56
  end
57
+
51
58
  end
@@ -0,0 +1,60 @@
1
+ module Flex
2
+ module Struct
3
+ module Prunable
4
+
5
+ extend self
6
+
7
+ VALUES = [ nil, '', {}, [], false ]
8
+
9
+ class Value
10
+ class << self
11
+ def to_s; '' end
12
+ alias_method :===, :==
13
+ end
14
+ end
15
+
16
+ def prune_blanks(obj)
17
+ prune(obj, *VALUES) || {}
18
+ end
19
+
20
+ # prunes the branch when the leaf is Prunable
21
+ # and compact.flatten the Array values
22
+ # values are the prunable values, like VALUES or Prunable::Value,
23
+ # or any arbitrary value
24
+ def prune(obj, *values)
25
+ case
26
+ when values.include?(obj)
27
+ obj
28
+ when obj.is_a?(::Array)
29
+ return obj if obj.empty?
30
+ ar = []
31
+ obj.each do |i|
32
+ pruned = prune(i, *values)
33
+ next if values.include?(pruned)
34
+ ar << pruned
35
+ end
36
+ a = ar.compact.flatten
37
+ a.empty? ? values.first : a
38
+ when obj.is_a?(::Hash)
39
+ return obj if obj.empty?
40
+ h = {}
41
+ obj.each do |k, v|
42
+ pruned = prune(v, *values)
43
+ next if values.include?(pruned)
44
+ # when a key is prunable merges the value if it is a hash (allows merging of partials)
45
+ if VALUES.include?(k)
46
+ h.merge!(pruned) if pruned.is_a?(::Hash)
47
+ else
48
+ h[k] = pruned
49
+ end
50
+ end
51
+ h.empty? ? values.first : h
52
+ else
53
+ obj
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ Prunable = Struct::Prunable
60
+ end
@@ -0,0 +1,27 @@
1
+ module Flex
2
+ module Struct
3
+ module AsIs end
4
+ module Symbolize
5
+
6
+ def symbolize(obj)
7
+ case obj
8
+ when Flex::Struct::Hash, Flex::Struct::Array, Flex::Struct::AsIs
9
+ obj
10
+ when ::Hash
11
+ h = Struct::Hash.new
12
+ obj.each do |k,v|
13
+ h[k.to_sym] = symbolize(v)
14
+ end
15
+ h
16
+ when ::Array
17
+ a = Struct::Array.new
18
+ obj.each{|i| a << i}
19
+ a
20
+ else
21
+ obj
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -1,10 +1,23 @@
1
1
  module Flex
2
- module Tasks
3
- extend self
2
+ class Tasks
3
+
4
+ attr_reader :options
5
+
6
+ def initialize(overrides={})
7
+ options = Flex::Utils.env2options *default_options.keys
8
+ options[:index] = options[:index].split(',') if options[:index]
9
+ @options = default_options.merge(options).merge(overrides)
10
+ end
11
+
12
+ def default_options
13
+ @default_options ||= { :force => false,
14
+ :index => Conf.variables[:index],
15
+ :config_file => Conf.config_file }
16
+ end
4
17
 
5
18
  def create_indices
6
19
  indices.each do |index|
7
- delete_index(index) if ENV['FORCE']
20
+ delete_index(index) if options[:force]
8
21
  raise ExistingIndexError, "#{index.inspect} already exists. Please use FORCE=1 if you want to delete it first." \
9
22
  if exist?(index)
10
23
  create(index)
@@ -15,96 +28,24 @@ module Flex
15
28
  indices.each { |index| delete_index(index) }
16
29
  end
17
30
 
18
- def import_models
19
- Configuration.http_client_options[:timeout] = ENV['TIMEOUT'].to_i if ENV['TIMEOUT']
20
- Configuration.http_client_options[:timeout] ||= 20
21
- Configuration.debug = !!ENV['FLEX_DEBUG']
22
- batch_size = ENV['BATCH_SIZE'] && ENV['BATCH_SIZE'].to_i || 1000
23
- options = {}
24
- if ENV['IMPORT_OPTIONS']
25
- ENV['IMPORT_OPTIONS'].split('&').each do |pair|
26
- k, v = pair.split('=')
27
- options[k.to_sym] = v
28
- end
29
- end
30
- deleted = []
31
-
32
- models.each do |klass|
33
- index = klass.flex.index
34
-
35
- if ENV['FORCE']
36
- unless deleted.include?(index)
37
- delete_index(index)
38
- deleted << index
39
- puts "#{index} index deleted"
40
- end
41
- end
42
-
43
- unless exist?(index)
44
- create(index)
45
- puts "#{index} index created"
46
- end
47
-
48
- if defined?(Mongoid::Document) && klass.ancestors.include?(Mongoid::Document)
49
- def klass.find_in_batches(options={})
50
- 0.step(count, options[:batch_size]) do |offset|
51
- yield limit(options[:batch_size]).skip(offset).to_a
52
- end
53
- end
54
- end
55
-
56
- unless klass.respond_to?(:find_in_batches)
57
- STDERR.puts "[ERROR] Class #{klass} does not respond to :find_in_batches. Skipped."
58
- next
59
- end
60
-
61
- pbar = ProgBar.new(klass.count, batch_size, "Class #{klass}: ")
62
-
63
- klass.find_in_batches(:batch_size => batch_size) do |array|
64
- opts = {:index => index}.merge(options)
65
- result = Flex.import_collection(array, opts) || next
66
- pbar.process_result(result, array.size)
67
- end
68
-
69
- pbar.finish
70
- end
31
+ def config_hash
32
+ @config_hash ||= ( hash = YAML.load(Utils.erb_process(config_path))
33
+ Utils.delete_allcaps_keys(hash) )
71
34
  end
72
35
 
73
- private
36
+ private
74
37
 
75
38
  def indices
76
- indices = ENV['INDICES'] || struct.keys
77
- indices = eval(indices) if indices.is_a?(String)
78
- indices = [indices] unless indices.is_a?(Array)
79
- indices
39
+ i = options[:index] || config_hash.keys
40
+ i.is_a?(Array) ? i : [i]
80
41
  end
81
42
 
82
43
  def exist?(index)
83
44
  Flex.exist?(:index => index)
84
45
  end
85
46
 
86
- def struct
87
- @struct ||= begin
88
- @indices_yaml = ENV['CONFIG_FILE'] || Flex::Configuration.config_file
89
- raise Errno::ENOENT, "no such file or directory #{@indices_yaml.inspect}. " +
90
- 'Please, use CONFIG_FILE=/path/to/index.yml ' +
91
- 'or set the Flex::Configuration.config_file properly' \
92
- unless File.exist?(@indices_yaml)
93
- Manager.indices(@indices_yaml)
94
- end
95
- end
96
-
97
-
98
- def models
99
- @models ||= begin
100
- mods = ENV['MODELS'] || Flex::Configuration.flex_models
101
- raise AgrumentError, 'no class defined. Please use MODELS=[ClassA,ClassB]' +
102
- 'or set the Flex::Configuration.flex_models properly' \
103
- if mods.nil? || mods.empty?
104
- mods = eval(mods) if mods.is_a?(String)
105
- mods = [mods] unless mods.is_a?(Array)
106
- mods.map{|c| c.is_a?(String) ? eval("::#{c}") : c}
107
- end
47
+ def config_path
48
+ @config_path ||= options[:config_file] || Conf.config_file
108
49
  end
109
50
 
110
51
  def delete_index(index)
@@ -112,9 +53,8 @@ module Flex
112
53
  end
113
54
 
114
55
  def create(index)
115
- raise MissingIndexEntryError, "no #{index.inspect} entry defined in #@indices_yaml" \
116
- unless struct.has_key?(index)
117
- Flex.POST "/#{index}", struct[index]
56
+ config_hash[index] = {} unless config_hash.has_key?(index)
57
+ Flex.POST "/#{index}", config_hash[index]
118
58
  end
119
59
 
120
60
  end
@@ -7,84 +7,85 @@ module Flex
7
7
  # For more details about templates, see the documentation.
8
8
  class Template
9
9
 
10
- include Base
10
+ include Logger
11
+ include Common
11
12
 
12
13
  def self.variables
13
- Variables.new
14
+ Vars.new
14
15
  end
15
16
 
16
- attr_reader :method, :path, :data, :variables, :tags, :partials
17
+ attr_reader :method, :path
17
18
 
18
- def initialize(method, path, data=nil, vars=nil)
19
+ def initialize(method, path, data=nil, *vars)
19
20
  @method = method.to_s.upcase
20
21
  raise ArgumentError, "#@method method not supported" \
21
22
  unless %w[HEAD GET PUT POST DELETE].include?(@method)
22
23
  @path = path =~ /^\// ? path : "/#{path}"
23
- @data = Utils.data_from_source(data)
24
- @instance_vars = vars
24
+ @data = Utils.parse_source(data)
25
+ @instance_vars = Vars.new(*vars)
25
26
  end
26
27
 
27
- def setup(host_flex, name=nil, source_vars=nil)
28
- @host_flex = host_flex
29
- @name = name
30
- @source_vars = source_vars
31
- self
32
- end
33
-
34
- def render(vars={})
35
- do_render(vars) do |response, int|
36
- Result.new(self, int[:vars], response)
28
+ def render(*vars)
29
+ do_render(*vars) do |response, int|
30
+ Result.new(self, int[:vars], response).to_flex_result
37
31
  end
38
32
  end
39
33
 
40
- def to_a(vars={})
41
- int = interpolate(vars)
42
- a = [method, int[:path], int[:data], @instance_vars]
43
- 2.times { a.pop if a.last.nil? }
34
+ def to_a(*vars)
35
+ vars = Vars.new(*vars)
36
+ int = interpolate(vars)
37
+ a = [method, int[:path], Utils.keyfy(:to_s, int[:data]), Utils.keyfy(:to_s, @instance_vars)]
38
+ 2.times { a.pop if a.last.nil? || a.last.empty? }
44
39
  a
45
40
  end
46
41
 
47
- def to_curl(vars={})
48
- to_curl_string interpolate(vars, strict=true)
42
+ def to_source
43
+ {@name.to_s => to_a}.to_yaml
49
44
  end
50
45
 
51
- def to_flex(name=nil)
52
- (name ? {name.to_s => to_a} : to_a).to_yaml
53
- end
54
46
 
55
47
  private
56
48
 
57
- def do_render(vars={})
58
- int = interpolate(vars, strict=true)
59
- path = build_path(int, vars)
60
- encoded_data = build_data(int, vars)
61
- response = Configuration.http_client.request(method, path, encoded_data)
62
-
63
- # used in Flex.exist?
64
- return response.status == 200 if method == 'HEAD'
65
-
66
- if Configuration.raise_proc.call(response)
49
+ def do_render(*vars)
50
+ vars = Vars.new(*vars)
51
+ int, path, encoded_data, response = try_clean_and_retry(vars)
52
+ return response.status == 200 if method == 'HEAD' # used in Flex.exist?
53
+ if Conf.http_client.raise_proc.call(response)
67
54
  int[:vars][:raise].is_a?(FalseClass) ? return : raise(HttpError.new(response, caller_line))
68
55
  end
69
-
70
56
  result = yield(response, int)
71
-
72
- rescue NameError => e
73
- if e.name == :request
74
- raise MissingHttpClientError,
75
- 'you should install the gem "patron" (recommended for performances) or "rest-client", ' +
76
- 'or provide your own http-client interface and set Flex::Configuration.http_client'
77
- else
78
- raise
79
- end
80
57
  ensure
81
- to_logger(path, encoded_data, result) if int && Configuration.debug && Configuration.logger.level == 0
58
+ log_render(int, path, encoded_data, result)
59
+ result
60
+ end
61
+
62
+ # This allows to use Lucene style search language in the :cleanable_query declared variable and
63
+ # in case of a syntax error it will remove all the problematic characters and retry with a cleaned query_string
64
+ # http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html
65
+ def try_clean_and_retry(vars)
66
+ response_vars = request(vars)
67
+ if !Prunable::VALUES.include?(vars[:cleanable_query]) && Conf.http_client.raise_proc.call(response_vars[3])
68
+ e = HttpError.new(response_vars[3], caller_line)
69
+ e.to_hash['error'] =~ /^SearchPhaseExecutionException/
70
+ (vars[:cleanable_query].is_a?(String) ? vars[:cleanable_query] : vars[:cleanable_query][:query]).tr!('"&|!(){}[]~^:+-\\', '')
71
+ request vars
72
+ else
73
+ response_vars
74
+ end
75
+ end
76
+
77
+ def request(vars)
78
+ int = interpolate(vars, strict=true)
79
+ path = build_path(int, vars)
80
+ encoded_data = build_data(int, vars)
81
+ response = Conf.http_client.request(method, path, encoded_data)
82
+ return int, path, encoded_data, response
82
83
  end
83
84
 
84
85
  def build_path(int, vars)
85
86
  params = int[:vars][:params]
86
87
  path = vars[:path] || int[:path]
87
- if params
88
+ unless params.empty?
88
89
  path << ((path =~ /\?/) ? '&' : '?')
89
90
  path << params.map { |p| p.join('=') }.join('&')
90
91
  end
@@ -92,60 +93,25 @@ module Flex
92
93
  end
93
94
 
94
95
  def build_data(int, vars)
95
- data = vars[:data] && Utils.data_from_source(vars[:data]) || int[:data]
96
+ data = vars[:data] && Utils.parse_source(vars[:data]) || int[:data]
96
97
  (data.nil? || data.is_a?(String)) ? data : MultiJson.encode(data)
97
98
  end
98
99
 
99
- def to_logger(path, encoded_data, result)
100
- h = {}
101
- h[:method] = method
102
- h[:path] = path
103
- h[:data] = MultiJson.decode(encoded_data) unless encoded_data.nil?
104
- h[:result] = result if result && Configuration.debug_result
105
- log = Configuration.debug_to_curl ? to_curl_string(h) : Utils.stringified_hash(h).to_yaml
106
- Configuration.logger.debug "[FLEX] Rendered #{caller_line}\n#{log}"
107
- end
108
-
109
- def caller_line
110
- method_name = @host_flex && @name && "#{@host_flex.host_class}.#@name"
111
- line = caller.find{|l| l !~ /#{LIB_PATH}/}
112
- ll = ''
113
- ll << "#{method_name} from " if method_name
114
- ll << "#{line}"
115
- ll
116
- end
117
-
118
- def to_curl_string(h)
119
- pretty = h[:path] =~ /\?/ ? '&pretty=1' : '?pretty=1'
120
- curl = %(curl -X#{method} "#{Configuration.base_uri}#{h[:path]}#{pretty}")
121
- if h[:data]
122
- data = if h[:data].is_a?(String)
123
- h[:data].length > 1024 ? h[:data][0,1024] + '...(truncated display)' : h[:data]
124
- else
125
- MultiJson.encode(h[:data], :pretty => true)
126
- end
127
- curl << %( -d '\n#{data}\n')
128
- end
129
- curl
130
- end
131
-
132
100
  def interpolate(*args)
133
- tags = Tags.new
134
- stringified = tags.stringify(:path => @path, :data => @data)
135
- @partials, @tags = tags.map(&:name).partition{|n| n.to_s =~ /^_/}
136
- @variables = Configuration.variables.deep_dup
137
- @variables.add(self.class.variables)
138
- @variables.add(@host_flex.variables) if @host_flex
139
- @variables.add(@source_vars, @instance_vars, tags.variables)
101
+ tags = Tags.new
102
+ stringified = tags.stringify(:path => @path, :data => @data)
103
+ @partials, @tags = tags.partial_and_tag_names
104
+ @base_variables = Conf.variables.deep_merge(self.class.variables)
105
+ @temp_variables = Vars.new(@source_vars, @instance_vars, tags.variables)
140
106
  instance_eval <<-ruby, __FILE__, __LINE__
141
107
  def interpolate(vars={}, strict=false)
108
+ vars = Vars.new(vars) unless vars.is_a?(Flex::Vars)
142
109
  return {:path => path, :data => data, :vars => vars} if vars.empty? && !strict
143
- sym_vars = {}
144
- vars.each{|k,v| sym_vars[k.to_sym] = v} # so you can pass the rails params hash
145
- merged = @variables.deep_merge sym_vars
146
- vars = process_vars(merged)
147
- obj = #{stringified}
148
- obj = prune(obj)
110
+ context_variables = vars[:context] ? vars[:context].flex.variables : (@host_flex && @host_flex.variables)
111
+ vars = @base_variables.deep_merge(context_variables, @temp_variables, vars).finalize
112
+ vars = interpolate_partials(vars)
113
+ obj = #{stringified}
114
+ obj = Prunable.prune(obj, Prunable::Value)
149
115
  obj[:path].tr_s!('/', '/') # removes empty path segments
150
116
  obj[:vars] = vars
151
117
  obj
@@ -154,31 +120,5 @@ module Flex
154
120
  interpolate(*args)
155
121
  end
156
122
 
157
- # prunes the branch when the leaf is a PrunableObject
158
- # and compact.flatten the Array values
159
- def prune(obj)
160
- case obj
161
- when Array
162
- return if obj.is_a?(PrunableObject)
163
- a = obj.map do |i|
164
- next if i.is_a?(PrunableObject)
165
- prune(i)
166
- end.compact.flatten
167
- a unless a.empty?
168
- when Hash
169
- return if obj.is_a?(PrunableObject)
170
- return obj if obj.empty?
171
- h = {}
172
- obj.each do |k, v|
173
- pruned = prune(v)
174
- next if pruned.is_a?(PrunableObject)
175
- h[k] = pruned
176
- end
177
- h unless h.empty?
178
- else
179
- obj
180
- end
181
- end
182
-
183
123
  end
184
124
  end