elasticsearch_record 1.2.3 → 1.3.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.
@@ -11,6 +11,7 @@ module ActiveRecord
11
11
  # ORIGINAL methods untouched:
12
12
  #
13
13
  # SUPPORTED but not used:
14
+ # -
14
15
  #
15
16
  # UNSUPPORTED methods that will be ignored:
16
17
  # - native_database_types
@@ -25,11 +26,6 @@ module ActiveRecord
25
26
  # - change_column_default
26
27
  # - change_column_null
27
28
  # - rename_column
28
- #
29
- # UPCOMING future methods:
30
- # - clone (option -> close, or read-only (#lock / unlock) )
31
- # - refresh
32
- # - rename_table
33
29
 
34
30
  define_unsupported_method :create_join_table, :drop_join_table, :create_alter_table,
35
31
  :change_column_default, :change_column_null, :rename_column, :rename_table
@@ -70,6 +66,26 @@ module ActiveRecord
70
66
  table_names.map { |table_name| close_table(table_name) }
71
67
  end
72
68
 
69
+ # refresh an index.
70
+ # A refresh makes recent operations performed on one or more indices available for search.
71
+ # raises an exception if the index could not be found.
72
+ #
73
+ # @param [String] table_name
74
+ # @return [Boolean] result state (returns false if refreshing failed)
75
+ def refresh_table(table_name)
76
+ api(:indices, :refresh, { index: table_name }, 'REFRESH TABLE').dig('_shards','failed') == 0
77
+ end
78
+
79
+ # refresh indices by provided names.
80
+ # @param [Array] table_names
81
+ # @return [Array] result state (returns false if refreshing failed)
82
+ def refresh_tables(*table_names)
83
+ table_names -= [schema_migration.table_name, InternalMetadata.table_name]
84
+ return if table_names.empty?
85
+
86
+ table_names.map { |table_name| refresh_table(table_name) }
87
+ end
88
+
73
89
  # truncates index by provided name.
74
90
  # HINT: Elasticsearch does not have a +truncate+ concept:
75
91
  # - so we have to store the current index' schema
@@ -132,15 +148,14 @@ module ActiveRecord
132
148
  end
133
149
  end
134
150
 
135
- # clones an entire table to the provided +target_name+.
151
+ # clones an entire table (index) to the provided +target_name+.
136
152
  # During cloning, the table will be automatically 'write'-blocked.
137
153
  # @param [String] table_name
138
154
  # @param [String] target_name
139
155
  # @param [Hash] options
140
- # @param [Proc] block
141
- def clone_table(table_name, target_name, **options, &block)
156
+ def clone_table(table_name, target_name, **options)
142
157
  # create new definition
143
- definition = clone_table_definition(table_name, target_name, **extract_table_options!(options), &block)
158
+ definition = clone_table_definition(table_name, target_name, **extract_table_options!(options))
144
159
 
145
160
  # yield optional block
146
161
  if block_given?
@@ -153,6 +168,24 @@ module ActiveRecord
153
168
  definition.exec!
154
169
  end
155
170
 
171
+ # renames a table (index) by executing multiple steps:
172
+ # - clone table
173
+ # - wait for 'green' state
174
+ # - drop old table
175
+ # The +timeout+ option will define how long to wait for the 'green' state.
176
+ #
177
+ # @param [String] table_name
178
+ # @param [String] target_name
179
+ # @param [String (frozen)] timeout (default: '30s')
180
+ # @param [Hash] options - additional 'clone' options (like settings, alias, ...)
181
+ def rename_table(table_name, target_name, timeout: '30s', **options)
182
+ schema_cache.clear_data_source_cache!(table_name)
183
+
184
+ clone_table(table_name, target_name, **options)
185
+ cluster_health(index: target_name, wait_for_status: 'green', timeout: timeout)
186
+ drop_table(table_name)
187
+ end
188
+
156
189
  # creates a new table (index).
157
190
  # [<tt>:force</tt>]
158
191
  # Set to +true+ to drop an existing table
@@ -168,9 +201,6 @@ module ActiveRecord
168
201
  # @param [Hash] options
169
202
  # @return [Boolean] acknowledged status
170
203
  def create_table(table_name, force: false, copy_from: nil, if_not_exists: false, **options)
171
- # IMPORTANT: compute will add possible configured prefix & suffix
172
- table_name = compute_table_name(table_name)
173
-
174
204
  return if if_not_exists && table_exists?(table_name)
175
205
 
176
206
  # copy schema from existing table
@@ -204,12 +234,14 @@ module ActiveRecord
204
234
  # t.mapping :name, :string
205
235
  # # Other column alterations here
206
236
  # end
207
- def change_table(table_name, if_exists: false, **options)
208
- # IMPORTANT: compute will add possible configured prefix & suffix
209
- table_name = compute_table_name(table_name)
210
-
237
+ def change_table(table_name, if_exists: false, recreate: false, **options, &block)
211
238
  return if if_exists && !table_exists?(table_name)
212
239
 
240
+ # check 'recreate' flag.
241
+ # If true, a 'create_table' with copy of the current will be executed
242
+ return create_table(table_name, force: true, copy_from: table_name, **options, &block) if recreate
243
+
244
+ # build new update definition
213
245
  definition = update_table_definition(table_name, self, **options)
214
246
 
215
247
  # yield optional block
@@ -231,12 +263,19 @@ module ActiveRecord
231
263
 
232
264
  alias :add_column :add_mapping
233
265
 
266
+ # will fail unless +recreate:true+ option was provided
234
267
  def change_mapping(table_name, name, type, **options, &block)
235
268
  _exec_change_table_with(:change_mapping, table_name, name, type, **options, &block)
236
269
  end
237
270
 
238
271
  alias :change_column :change_mapping
239
272
 
273
+ def remove_mapping(table_name, name, **options)
274
+ _exec_change_table_with(:remove_mapping, table_name, name, **options)
275
+ end
276
+
277
+ alias :remove_column :remove_mapping
278
+
240
279
  def change_mapping_meta(table_name, name, **options)
241
280
  _exec_change_table_with(:change_mapping_meta, table_name, name, **options)
242
281
  end
@@ -245,14 +284,12 @@ module ActiveRecord
245
284
  _exec_change_table_with(:change_mapping_attributes, table_name, name, **options, &block)
246
285
  end
247
286
 
248
- alias :change_mapping_attribute :change_mapping_attributes
249
-
250
287
  def change_meta(table_name, name, value, **options)
251
288
  _exec_change_table_with(:change_meta, table_name, name, value, **options)
252
289
  end
253
290
 
254
- def delete_meta(table_name, name, **options)
255
- _exec_change_table_with(:delete_meta, table_name, name, **options)
291
+ def remove_meta(table_name, name, **options)
292
+ _exec_change_table_with(:remove_meta, table_name, name, **options)
256
293
  end
257
294
 
258
295
  # -- setting -------------------------------------------------------------------------------------------------
@@ -265,8 +302,8 @@ module ActiveRecord
265
302
  _exec_change_table_with(:change_setting, table_name, name, value, **options, &block)
266
303
  end
267
304
 
268
- def delete_setting(table_name, name, **options, &block)
269
- _exec_change_table_with(:delete_setting, table_name, name, **options, &block)
305
+ def remove_setting(table_name, name, **options, &block)
306
+ _exec_change_table_with(:remove_setting, table_name, name, **options, &block)
270
307
  end
271
308
 
272
309
  # -- alias ---------------------------------------------------------------------------------------------------
@@ -279,28 +316,31 @@ module ActiveRecord
279
316
  _exec_change_table_with(:change_alias, table_name, name, **options, &block)
280
317
  end
281
318
 
282
- def delete_alias(table_name, name, **options, &block)
283
- _exec_change_table_with(:delete_alias, table_name, name, **options, &block)
319
+ def remove_alias(table_name, name, **options, &block)
320
+ _exec_change_table_with(:remove_alias, table_name, name, **options, &block)
284
321
  end
285
322
 
286
- # computes a provided +table_name+ with optionally configured +table_name_prefix+ & +table_name_suffix+.
323
+ # recaps a provided +table_name+ with optionally configured +table_name_prefix+ & +table_name_suffix+.
324
+ # This depends on the connection config of the current environment.
325
+ #
287
326
  # @param [String] table_name
288
327
  # @return [String]
289
- def compute_table_name(table_name)
328
+ def _env_table_name(table_name)
290
329
  table_name = table_name.to_s
291
-
330
+
292
331
  # HINT: +"" creates a new +unfrozen+ string!
293
- str = +""
294
- str << table_name_prefix unless table_name.start_with?(table_name_prefix)
295
- str << table_name
296
- str << table_name_suffix unless table_name.end_with?(table_name_suffix)
297
- str
332
+ name = +""
333
+ name << table_name_prefix unless table_name.start_with?(table_name_prefix)
334
+ name << table_name
335
+ name << table_name_suffix unless table_name.end_with?(table_name_suffix)
336
+
337
+ name
298
338
  end
299
339
 
300
340
  private
301
341
 
302
- def _exec_change_table_with(method, table_name, *args, **kwargs, &block)
303
- change_table(table_name) do |t|
342
+ def _exec_change_table_with(method, table_name, *args, recreate: false, **kwargs, &block)
343
+ change_table(table_name, recreate: recreate) do |t|
304
344
  t.send(method, *args, **kwargs, &block)
305
345
  end
306
346
  end
@@ -9,6 +9,9 @@ module Arel # :nodoc: all
9
9
  # required for ActiveRecord
10
10
  attr_accessor :preparable
11
11
 
12
+ # returns the current bind index (default: 1)
13
+ attr_reader :bind_index
14
+
12
15
  def initialize
13
16
  # force initialize a body as hash
14
17
  super(body: {})
@@ -44,7 +47,8 @@ module Arel # :nodoc: all
44
47
  # adds / sets any argument
45
48
  if args.length == 2
46
49
  @arguments[args[0]] = args[1]
47
- else # should be a hash
50
+ else
51
+ # should be a hash
48
52
  @arguments.merge!(args[0])
49
53
  end
50
54
  when :body
@@ -90,10 +94,13 @@ module Arel # :nodoc: all
90
94
 
91
95
  private
92
96
 
93
- # calls a assign on the body
97
+ # calls a assign on the body.
98
+ # forwards value to the +#claim+ method if key is +:__query__+.
99
+ # @param [Symbol] key
100
+ # @param [Object] value
94
101
  def assign(key, value)
95
102
  # check for special provided key, to claim through an assign
96
- if key == :__claim__
103
+ if key == :__query__
97
104
  if value.is_a?(Array)
98
105
  value.each do |arg|
99
106
  vkey = arg.keys.first
@@ -13,7 +13,7 @@ module Arel # :nodoc: all
13
13
 
14
14
  # SELECT // SEARCH
15
15
  def visit_Arel_Nodes_SelectStatement(o)
16
- # prepare query
16
+ # prepare query type
17
17
  claim(:type, ::ElasticsearchRecord::Query::TYPE_SEARCH)
18
18
 
19
19
  resolve(o.cores) # visit_Arel_Nodes_SelectCore
@@ -26,20 +26,23 @@ module Arel # :nodoc: all
26
26
  resolve(o.configure)
27
27
  end
28
28
 
29
- # UPDATE
29
+ # UPDATE (by query - not a single record...)
30
30
  def visit_Arel_Nodes_UpdateStatement(o)
31
31
  # switch between updating a single Record or multiple by query
32
32
  if o.relation.is_a?(::Arel::Table)
33
33
  raise NotImplementedError, "if you've made it this far, something went wrong ..."
34
34
  end
35
35
 
36
- # prepare query
36
+ # prepare query type
37
37
  claim(:type, ::ElasticsearchRecord::Query::TYPE_UPDATE_BY_QUERY)
38
38
 
39
+ # force refresh after update - but it can be unset again through the 'configure' ...
40
+ claim(:refresh, true)
41
+
39
42
  # sets the index
40
43
  resolve(o.relation)
41
44
 
42
- # updating multiple entries need a script
45
+ # updating multiple entries needs a script
43
46
  assign(:script, {}) do
44
47
  assign(:inline, "") do
45
48
  updates = collect(o.values)
@@ -59,16 +62,19 @@ module Arel # :nodoc: all
59
62
  resolve(o.configure)
60
63
  end
61
64
 
62
- # DELETE
65
+ # DELETE (by query - not a single record...)
63
66
  def visit_Arel_Nodes_DeleteStatement(o)
64
67
  # switch between updating a single Record or multiple by query
65
68
  if o.relation.is_a?(::Arel::Table)
66
69
  raise NotImplementedError, "if you've made it this far, something went wrong ..."
67
70
  end
68
71
 
69
- # prepare query
72
+ # prepare query type
70
73
  claim(:type, ::ElasticsearchRecord::Query::TYPE_DELETE_BY_QUERY)
71
74
 
75
+ # force refresh after delete - but it can be unset again through the 'configure' ...
76
+ claim(:refresh, true)
77
+
72
78
  # sets the index
73
79
  resolve(o.relation)
74
80
 
@@ -84,13 +90,15 @@ module Arel # :nodoc: all
84
90
  resolve(o.configure)
85
91
  end
86
92
 
87
- # INSERT
93
+ # INSERT (by query - not a single record...)
94
+ # this is also used by 'meta' or 'schema_migrations' tables ...
88
95
  def visit_Arel_Nodes_InsertStatement(o)
89
-
90
96
  # switch between updating a single Record or multiple by query
91
97
  if o.relation.is_a?(::Arel::Table)
92
- # prepare query
98
+ # prepare query type
93
99
  claim(:type, ::ElasticsearchRecord::Query::TYPE_CREATE)
100
+
101
+ # force refresh after insert
94
102
  claim(:refresh, true)
95
103
 
96
104
  # sets the index
@@ -355,6 +363,23 @@ module Arel # :nodoc: all
355
363
  end
356
364
  end
357
365
 
366
+ # DIRECT ASSIGNMENT
367
+ def visit_Arel_Nodes_In(o)
368
+ self.collector.preparable = false
369
+
370
+ attr, values = o.left, o.right
371
+
372
+ if Array === values
373
+ unless values.empty?
374
+ values.delete_if { |value| unboundable?(value) }
375
+ end
376
+
377
+ return failed! if values.empty?
378
+ end
379
+
380
+ assign(:filter, [{ terms: { visit(attr) => visit(values) } }])
381
+ end
382
+
358
383
  def visit_Arel_Nodes_And(o)
359
384
  collect(o.children)
360
385
  end
@@ -393,6 +418,7 @@ module Arel # :nodoc: all
393
418
  claim(:index, o.name)
394
419
  end
395
420
 
421
+ # RAW RETURN
396
422
  def visit_Struct_Raw(o)
397
423
  o
398
424
  end
@@ -89,8 +89,9 @@ module Arel # :nodoc: all
89
89
  # prepare query
90
90
  claim(:type, ::ElasticsearchRecord::Query::TYPE_INDEX_UPDATE_SETTING)
91
91
 
92
- # special overcomplicated blocks to assign a hash of settings directly to the body
93
- assign(:__claim__, {}) do
92
+ # special overcomplicated blocks to assign a hash of settings directly to the body...
93
+ # todo: refactor this in future versions
94
+ assign(:__query__, {}) do
94
95
  assign(:body, {}) do
95
96
  resolve(o.items, :visit_TableSettingDefinition)
96
97
  end
@@ -98,7 +99,7 @@ module Arel # :nodoc: all
98
99
  end
99
100
 
100
101
  alias :visit_AddSettingDefinition :visit_ChangeSettingDefinition
101
- alias :visit_DeleteSettingDefinition :visit_ChangeSettingDefinition
102
+ alias :visit_RemoveSettingDefinition :visit_ChangeSettingDefinition
102
103
 
103
104
  def visit_ChangeAliasDefinition(o)
104
105
  # prepare query
@@ -110,7 +111,7 @@ module Arel # :nodoc: all
110
111
 
111
112
  alias :visit_AddAliasDefinition :visit_ChangeAliasDefinition
112
113
 
113
- def visit_DeleteAliasDefinition(o)
114
+ def visit_RemoveAliasDefinition(o)
114
115
  # prepare query
115
116
  claim(:type, ::ElasticsearchRecord::Query::TYPE_INDEX_DELETE_ALIAS)
116
117
 
@@ -8,8 +8,8 @@ module ElasticsearchRecord
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 1
11
- MINOR = 2
12
- TINY = 3
11
+ MINOR = 3
12
+ TINY = 0
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -33,9 +33,9 @@ module ElasticsearchRecord
33
33
  # since total will be limited to 10000 results, we need to resolve the real values by a custom query.
34
34
  # This query is called through +#select_count+.
35
35
  #
36
- # HINT: :__claim__ directly interacts with the query-object and sets a 'terminate_after' argument
37
- # (see @ Arel::Collectors::ElasticsearchQuery#assign)
38
- arel = spawn.unscope!(:offset, :limit, :order, :configure, :aggs).configure!(:__claim__, argument: { terminate_after: limit_value }).arel
36
+ # HINT: +:__query__+ directly interacts with the query-object and sets the 'terminate_after' argument
37
+ # see @ ElasticsearchRecord::Query#arguments & Arel::Collectors::ElasticsearchQuery#assign
38
+ arel = spawn.unscope!(:offset, :limit, :order, :configure, :aggs).configure!(:__query__, argument: { terminate_after: limit_value }).arel
39
39
  klass.connection.select_count(arel, "#{klass.name} Count")
40
40
  else
41
41
  # since total will be limited to 10000 results, we need to resolve the real values by a custom query.
@@ -27,9 +27,12 @@ module ElasticsearchRecord
27
27
  self
28
28
  end
29
29
 
30
- # sets or overwrites additional arguments for the query (not the :query-node, the whole query).
31
- # You can also force a overwrite of previously defined arguments, like +size+ or +from+.
32
- # This is useful to force remove of keys.
30
+ # sets or overwrites additional arguments for the whole query (not the current 'query-node' - the whole query).
31
+ # Previously defined arguments (like +size+ or +from+) can also be overwritten.
32
+ # Providing a +nil+ value will remove the key - this is useful to force remove of keys.
33
+ #
34
+ # Providing the special key +:__query__+ will directly access the query object, to alter query-related values
35
+ # (like 'refresh, arguments, columns, ...' - see @ Arel::Collectors::ElasticsearchQuery
33
36
  #
34
37
  # @example
35
38
  # # adds {refresh true} to the query
@@ -37,6 +40,10 @@ module ElasticsearchRecord
37
40
  #
38
41
  # # overwrites or sets {from: 50} but removes the :sort key
39
42
  # configure({from: 50, sort: nil})
43
+ #
44
+ # # sets the query's 'refresh' to true
45
+ # configure(:__query__, refresh: true)
46
+ #
40
47
  # @param [Array] args
41
48
  def configure(*args)
42
49
  spawn.configure!(*args)
@@ -48,10 +55,10 @@ module ElasticsearchRecord
48
55
 
49
56
  if args.length == 1 && args.first.is_a?(Hash)
50
57
  self.configure_value = self.configure_value.merge(args[0])
51
- elsif args.length == 2 && args[0] == :__claim__
52
- tmp = self.configure_value[:__claim__] || []
58
+ elsif args.length == 2 && args[0] == :__query__
59
+ tmp = self.configure_value[:__query__] || []
53
60
  tmp << args[1]
54
- self.configure_value = self.configure_value.merge(:__claim__ => tmp)
61
+ self.configure_value = self.configure_value.merge(:__query__ => tmp)
55
62
  elsif args.length == 2
56
63
  self.configure_value = self.configure_value.merge(args[0] => args[1])
57
64
  end
@@ -69,7 +76,8 @@ module ElasticsearchRecord
69
76
 
70
77
  alias_method :aggs, :aggregate
71
78
 
72
- def aggregate!(opts, *rest) # :nodoc:
79
+ def aggregate!(opts, *rest)
80
+ # :nodoc:
73
81
  case opts
74
82
  when Symbol, String
75
83
  self.aggs_clause += build_query_clause(opts, rest)
@@ -84,6 +92,16 @@ module ElasticsearchRecord
84
92
  self
85
93
  end
86
94
 
95
+ # sets the query's +refresh+ value.
96
+ # @param [Boolean] value (default: true)
97
+ def refresh(value = true)
98
+ spawn.refresh!(value)
99
+ end
100
+
101
+ def refresh!(value = true)
102
+ configure!(:__query__, refresh: value)
103
+ end
104
+
87
105
  # add a whole query 'node' to the query.
88
106
  # @example
89
107
  # query(:bool, {filter: ...})
@@ -92,7 +110,8 @@ module ElasticsearchRecord
92
110
  spawn.query!(*args)
93
111
  end
94
112
 
95
- def query!(kind, opts, *rest) # :nodoc:
113
+ def query!(kind, opts, *rest)
114
+ # :nodoc:
96
115
  kind!(kind)
97
116
  self.query_clause += build_query_clause(opts.keys[0], opts.values[0], rest)
98
117
  self
@@ -106,7 +125,8 @@ module ElasticsearchRecord
106
125
  spawn.filter!(*args)
107
126
  end
108
127
 
109
- def filter!(opts, *rest) # :nodoc:
128
+ def filter!(opts, *rest)
129
+ # :nodoc:
110
130
  set_default_kind!
111
131
  self.query_clause += build_query_clause(:filter, opts, rest)
112
132
  self
@@ -120,7 +140,8 @@ module ElasticsearchRecord
120
140
  spawn.must_not!(*args)
121
141
  end
122
142
 
123
- def must_not!(opts, *rest) # :nodoc:
143
+ def must_not!(opts, *rest)
144
+ # :nodoc:
124
145
  set_default_kind!
125
146
  self.query_clause += build_query_clause(:must_not, opts, rest)
126
147
  self
@@ -134,7 +155,8 @@ module ElasticsearchRecord
134
155
  spawn.must!(*args)
135
156
  end
136
157
 
137
- def must!(opts, *rest) # :nodoc:
158
+ def must!(opts, *rest)
159
+ # :nodoc:
138
160
  set_default_kind!
139
161
  self.query_clause += build_query_clause(:must, opts, rest)
140
162
  self
@@ -148,7 +170,8 @@ module ElasticsearchRecord
148
170
  spawn.should!(*args)
149
171
  end
150
172
 
151
- def should!(opts, *rest) # :nodoc:
173
+ def should!(opts, *rest)
174
+ # :nodoc:
152
175
  set_default_kind!
153
176
  self.query_clause += build_query_clause(:should, opts, rest)
154
177
  self
@@ -176,7 +199,8 @@ module ElasticsearchRecord
176
199
  super
177
200
  end
178
201
 
179
- def where!(opts, *rest) # :nodoc:
202
+ def where!(opts, *rest)
203
+ # :nodoc:
180
204
  case opts
181
205
  # check the first provided parameter +opts+ and validate, if this is an alias for "must, must_not, should or filter"
182
206
  # if true, we expect the rest[0] to be a hash.
@@ -186,7 +210,7 @@ module ElasticsearchRecord
186
210
  when :filter, :must, :must_not, :should
187
211
  send("#{opts}!", *rest)
188
212
  else
189
- raise ArgumentError, "Unsupported argument type for where: #{opts}"
213
+ raise ArgumentError, "Unsupported prefix type '#{opts}'. Allowed types are: :filter, :must, :must_not, :should"
190
214
  end
191
215
  when Array
192
216
  # check if this is a nested array of multiple [<kind>,<data>]
@@ -197,42 +221,8 @@ module ElasticsearchRecord
197
221
  else
198
222
  where!(*opts, *rest)
199
223
  end
200
- when String
201
- # fallback to ActiveRecords +#where_clause+
202
- # currently NOT supported
203
- super(opts, rest)
204
224
  else
205
- # hash -> {name: 'hans'}
206
- # protects against forwarding params directly to where ...
207
- # User.where(params) <- will never work
208
- # User.where(params.permit(:user)) <- ok
209
- opts = sanitize_forbidden_attributes(opts)
210
-
211
- # resolve possible aliases
212
- opts = opts.transform_keys do |key|
213
- key = key.to_s
214
- klass.attribute_aliases[key] || key
215
- end
216
-
217
- # check if we have keys without Elasticsearch fields
218
- if (invalid = (opts.keys - klass.searchable_column_names)).present?
219
- raise(ActiveRecord::UnknownAttributeReference,
220
- "Unable to build query with unknown searchable attributes: #{invalid.map(&:inspect).join(", ")}. " \
221
- "If you want to build a custom query you should use one of those methods: 'filter, must, must_not, should'. " \
222
- "#{klass.name}.filter('#{invalid[0]}' => '...')"
223
- )
224
- end
225
-
226
- # force set default kind, if not previously set
227
- set_default_kind!
228
-
229
- # builds predicates from opts (transforms this in a more unreadable way but is required for nested assignment & binds ...)
230
- parts = opts.map do |key,value|
231
- # builds and returns a new Arel Node from provided key/value pair
232
- predicate_builder[key,value]
233
- end
234
-
235
- self.where_clause += ::ActiveRecord::Relation::WhereClause.new(parts)
225
+ self.where_clause += build_where_clause(opts, rest)
236
226
  end
237
227
 
238
228
  self
@@ -271,6 +261,45 @@ module ElasticsearchRecord
271
261
 
272
262
  private
273
263
 
264
+ def build_where_clause(opts, _rest = [])
265
+ case opts
266
+ when Symbol, Array, String
267
+ raise ArgumentError, "Unsupported or unresolved argument class '#{opts.class}' for build_where_clause: #{opts}"
268
+ else
269
+ # hash -> {name: 'hans'}
270
+ # protects against forwarding params directly to where ...
271
+ # User.where(params) <- will never work
272
+ # User.where(params.permit(:user)) <- ok
273
+ opts = sanitize_forbidden_attributes(opts)
274
+
275
+ # resolve possible aliases
276
+ opts = opts.transform_keys do |key|
277
+ key = key.to_s
278
+ klass.attribute_aliases[key] || key
279
+ end
280
+
281
+ # check if we have keys without Elasticsearch fields
282
+ if (invalid = (opts.keys - klass.searchable_column_names)).present?
283
+ raise(ActiveRecord::UnknownAttributeReference,
284
+ "Unable to build query with unknown searchable attributes: #{invalid.map(&:inspect).join(", ")}. " \
285
+ "If you want to build a custom query you should use one of those methods: 'filter, must, must_not, should'. " \
286
+ "#{klass.name}.filter('#{invalid[0]}' => '...')"
287
+ )
288
+ end
289
+
290
+ # force set default kind, if not previously set
291
+ set_default_kind!
292
+
293
+ # builds predicates from opts (transforms this in a more unreadable way but is required for nested assignment & binds ...)
294
+ parts = opts.map do |key, value|
295
+ # builds and returns a new Arel Node from provided key/value pair
296
+ predicate_builder[key, value]
297
+ end
298
+
299
+ ::ActiveRecord::Relation::WhereClause.new(parts)
300
+ end
301
+ end
302
+
274
303
  def build_query_clause(kind, data, rest = [])
275
304
  ElasticsearchRecord::Relation::QueryClause.new(kind, Array.wrap(data), rest.extract_options!)
276
305
  end
@@ -104,7 +104,7 @@ module ElasticsearchRecord
104
104
  relation.offset!(nil).limit!(nil)
105
105
 
106
106
  # remove the 'index' from the query arguments (pit doesn't like that)
107
- relation.configure!(:__claim__, { index: nil })
107
+ relation.configure!(:__query__, { index: nil })
108
108
 
109
109
  # we store the results in this array
110
110
  results = []