torque-postgresql 3.4.1 → 4.0.0.rc1

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql/adapter/database_statements.rb +63 -84
  3. data/lib/torque/postgresql/adapter/oid/array.rb +17 -0
  4. data/lib/torque/postgresql/adapter/oid/line.rb +2 -6
  5. data/lib/torque/postgresql/adapter/oid/range.rb +4 -4
  6. data/lib/torque/postgresql/adapter/oid.rb +1 -23
  7. data/lib/torque/postgresql/adapter/quoting.rb +13 -7
  8. data/lib/torque/postgresql/adapter/schema_creation.rb +7 -28
  9. data/lib/torque/postgresql/adapter/schema_definitions.rb +36 -0
  10. data/lib/torque/postgresql/adapter/schema_dumper.rb +90 -34
  11. data/lib/torque/postgresql/adapter/schema_overrides.rb +45 -0
  12. data/lib/torque/postgresql/adapter/schema_statements.rb +64 -49
  13. data/lib/torque/postgresql/arel/infix_operation.rb +15 -28
  14. data/lib/torque/postgresql/arel/nodes.rb +2 -2
  15. data/lib/torque/postgresql/arel/operations.rb +7 -1
  16. data/lib/torque/postgresql/arel/visitors.rb +3 -9
  17. data/lib/torque/postgresql/associations/association_scope.rb +23 -31
  18. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +25 -0
  19. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +16 -0
  20. data/lib/torque/postgresql/attributes/builder/enum.rb +12 -9
  21. data/lib/torque/postgresql/attributes/builder/full_text_search.rb +121 -0
  22. data/lib/torque/postgresql/attributes/builder/period.rb +21 -21
  23. data/lib/torque/postgresql/attributes/builder.rb +49 -11
  24. data/lib/torque/postgresql/attributes/enum.rb +7 -7
  25. data/lib/torque/postgresql/attributes/enum_set.rb +7 -7
  26. data/lib/torque/postgresql/attributes/full_text_search.rb +19 -0
  27. data/lib/torque/postgresql/attributes/period.rb +2 -2
  28. data/lib/torque/postgresql/attributes.rb +0 -4
  29. data/lib/torque/postgresql/auxiliary_statement/recursive.rb +3 -3
  30. data/lib/torque/postgresql/base.rb +3 -10
  31. data/lib/torque/postgresql/collector.rb +1 -1
  32. data/lib/torque/postgresql/config.rb +95 -5
  33. data/lib/torque/postgresql/function.rb +61 -0
  34. data/lib/torque/postgresql/inheritance.rb +52 -36
  35. data/lib/torque/postgresql/predicate_builder/arel_attribute_handler.rb +33 -0
  36. data/lib/torque/postgresql/predicate_builder/array_handler.rb +47 -0
  37. data/lib/torque/postgresql/predicate_builder/enumerator_lazy_handler.rb +37 -0
  38. data/lib/torque/postgresql/predicate_builder/regexp_handler.rb +21 -0
  39. data/lib/torque/postgresql/predicate_builder.rb +35 -0
  40. data/lib/torque/postgresql/railtie.rb +112 -30
  41. data/lib/torque/postgresql/reflection/abstract_reflection.rb +12 -44
  42. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +4 -0
  43. data/lib/torque/postgresql/reflection/has_many_reflection.rb +4 -0
  44. data/lib/torque/postgresql/reflection/runtime_reflection.rb +1 -1
  45. data/lib/torque/postgresql/relation/inheritance.rb +4 -7
  46. data/lib/torque/postgresql/relation.rb +6 -10
  47. data/lib/torque/postgresql/schema_cache.rb +6 -12
  48. data/lib/torque/postgresql/version.rb +1 -1
  49. data/lib/torque/postgresql.rb +2 -1
  50. data/spec/initialize.rb +58 -0
  51. data/spec/mocks/cache_query.rb +21 -21
  52. data/spec/mocks/create_table.rb +6 -26
  53. data/spec/schema.rb +19 -12
  54. data/spec/spec_helper.rb +5 -1
  55. data/spec/tests/arel_spec.rb +32 -7
  56. data/spec/tests/auxiliary_statement_spec.rb +3 -3
  57. data/spec/tests/belongs_to_many_spec.rb +72 -5
  58. data/spec/tests/enum_set_spec.rb +12 -11
  59. data/spec/tests/enum_spec.rb +4 -2
  60. data/spec/tests/full_text_seach_test.rb +252 -0
  61. data/spec/tests/function_spec.rb +42 -0
  62. data/spec/tests/has_many_spec.rb +21 -8
  63. data/spec/tests/interval_spec.rb +1 -7
  64. data/spec/tests/period_spec.rb +61 -61
  65. data/spec/tests/predicate_builder_spec.rb +132 -0
  66. data/spec/tests/schema_spec.rb +2 -8
  67. data/spec/tests/table_inheritance_spec.rb +25 -26
  68. metadata +34 -39
@@ -4,13 +4,11 @@ module Torque
4
4
  module PostgreSQL
5
5
  module Attributes
6
6
  module Builder
7
- # TODO: Allow documenting by building the methods outside and importing
8
- # only the raw string
9
7
  class Period
10
8
  DIRECT_ACCESS_REGEX = /_?%s_?/
11
9
  SUPPORTED_TYPES = %i[daterange tsrange tstzrange].freeze
12
10
  CURRENT_GETTERS = {
13
- daterange: 'Date.today',
11
+ daterange: 'Date.current',
14
12
  tsrange: 'Time.zone.now',
15
13
  tstzrange: 'Time.zone.now',
16
14
  }.freeze
@@ -21,6 +19,8 @@ module Torque
21
19
  tstzrange: :timestamp,
22
20
  }.freeze
23
21
 
22
+ FN = '::Torque::PostgreSQL::FN'
23
+
24
24
  attr_accessor :klass, :attribute, :options, :type, :default, :current_getter,
25
25
  :type_caster, :threshold, :dynamic_threshold, :klass_module, :instance_module
26
26
 
@@ -208,11 +208,11 @@ module Torque
208
208
  end
209
209
 
210
210
  def arel_default_sql
211
- @arel_default_sql ||= arel_sql_quote(@default.inspect)
211
+ @arel_default_sql ||= arel_sql_bind(@default.inspect)
212
212
  end
213
213
 
214
- def arel_sql_quote(value)
215
- "::Arel.sql(connection.quote(#{value}))"
214
+ def arel_sql_bind(value)
215
+ "#{FN}.bind_with(#{arel_attribute}, #{value})"
216
216
  end
217
217
 
218
218
  # Check how to provide the threshold value
@@ -223,12 +223,12 @@ module Torque
223
223
  "arel_table['#{threshold}']"
224
224
  when ActiveSupport::Duration
225
225
  value = "'#{threshold.to_i} seconds'"
226
- "::Arel.sql(\"#{value}\").cast(:interval)"
226
+ "::Arel.sql(\"#{value}\").pg_cast(:interval)"
227
227
  when Numeric
228
228
  value = threshold.to_i.to_s
229
229
  value << type_caster.eql?(:date) ? ' days' : ' seconds'
230
230
  value = "'#{value}'"
231
- "::Arel.sql(\"#{value}\").cast(:interval)"
231
+ "::Arel.sql(\"#{value}\").pg_cast(:interval)"
232
232
  end
233
233
  end
234
234
  end
@@ -248,7 +248,7 @@ module Torque
248
248
  return arel_start_at unless threshold.present?
249
249
  @arel_real_start_at ||= begin
250
250
  result = +"(#{arel_start_at} - #{arel_threshold_value})"
251
- result << '.cast(:date)' if type.eql?(:daterange)
251
+ result << '.pg_cast(:date)' if type.eql?(:daterange)
252
252
  result
253
253
  end
254
254
  end
@@ -258,7 +258,7 @@ module Torque
258
258
  return arel_finish_at unless threshold.present?
259
259
  @arel_real_finish_at ||= begin
260
260
  result = +"(#{arel_finish_at} + #{arel_threshold_value})"
261
- result << '.cast(:date)' if type.eql?(:daterange)
261
+ result << '.pg_cast(:date)' if type.eql?(:daterange)
262
262
  result
263
263
  end
264
264
  end
@@ -278,9 +278,9 @@ module Torque
278
278
 
279
279
  # Create an arel named function
280
280
  def arel_named_function(name, *args)
281
- result = +"::Arel::Nodes::NamedFunction.new(#{name.to_s.inspect}"
282
- result << ', [' << args.join(', ') << ']' if args.present?
283
- result << ')'
281
+ result = +"#{FN}.#{name}"
282
+ result << '(' << args.join(', ') << ')' if args.present?
283
+ result
284
284
  end
285
285
 
286
286
  # Create an arel version of +nullif+ function
@@ -302,24 +302,24 @@ module Torque
302
302
  def arel_daterange(real = false)
303
303
  arel_named_function(
304
304
  'daterange',
305
- (real ? arel_real_start_at : arel_start_at) + '.cast(:date)',
306
- (real ? arel_real_finish_at : arel_finish_at) + '.cast(:date)',
305
+ (real ? arel_real_start_at : arel_start_at) + '.pg_cast(:date)',
306
+ (real ? arel_real_finish_at : arel_finish_at) + '.pg_cast(:date)',
307
307
  '::Arel.sql("\'[]\'")',
308
308
  )
309
309
  end
310
310
 
311
311
  def arel_check_condition(type)
312
312
  checker = arel_nullif(arel_real_attribute, arel_empty_value)
313
- checker << ".#{type}(value.cast(#{type_caster.inspect}))"
313
+ checker << ".#{type}(value.pg_cast(#{type_caster.inspect}))"
314
314
  arel_coalesce(checker, arel_default_sql)
315
315
  end
316
316
 
317
317
  def arel_formatting_value(condition = nil, value = 'value', cast: nil)
318
318
  [
319
319
  "#{value} = arel_table[#{value}] if #{value}.is_a?(Symbol)",
320
- "unless #{value}.respond_to?(:cast)",
321
- " #{value} = ::Arel.sql(connection.quote(#{value}))",
322
- (" #{value} = #{value}.cast(#{cast.inspect})" if cast),
320
+ "unless #{value}.respond_to?(:pg_cast)",
321
+ " #{value} = #{FN}.bind_with(#{arel_attribute}, #{value})",
322
+ (" #{value} = #{value}.pg_cast(#{cast.inspect})" if cast),
323
323
  'end',
324
324
  condition,
325
325
  ].compact.join("\n")
@@ -347,14 +347,14 @@ module Torque
347
347
 
348
348
  def klass_current
349
349
  [
350
- "value = #{arel_sql_quote(current_getter)}",
350
+ "value = #{arel_sql_bind(current_getter)}",
351
351
  "where(#{arel_check_condition(:contains)})",
352
352
  ].join("\n")
353
353
  end
354
354
 
355
355
  def klass_not_current
356
356
  [
357
- "value = #{arel_sql_quote(current_getter)}",
357
+ "value = #{arel_sql_bind(current_getter)}",
358
358
  "where.not(#{arel_check_condition(:contains)})",
359
359
  ].join("\n")
360
360
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'builder/enum'
4
4
  require_relative 'builder/period'
5
+ require_relative 'builder/full_text_search'
5
6
 
6
7
  module Torque
7
8
  module PostgreSQL
@@ -12,20 +13,57 @@ module Torque
12
13
  return unless table_exists?
13
14
 
14
15
  args.each do |attribute|
15
- begin
16
- # Generate methods on self class
17
- builder = builder_klass.new(self, attribute, extra.merge(options))
18
- builder.conflicting?
19
- builder.build
20
-
21
- # Additional settings for the builder
22
- instance_exec(builder, &block) if block.present?
23
- rescue Interrupt
24
- # Not able to build the attribute, maybe pending migrations
25
- end
16
+ # Generate methods on self class
17
+ builder = builder_klass.new(self, attribute, extra.merge(options))
18
+ builder.conflicting?
19
+ builder.build
20
+
21
+ # Additional settings for the builder
22
+ instance_exec(builder, &block) if block.present?
23
+ rescue Interrupt
24
+ # Not able to build the attribute, maybe pending migrations
26
25
  end
27
26
  end
28
27
  end
28
+
29
+ def self.search_vector_options(columns:, language: nil, stored: true, **options)
30
+ weights = to_search_weights(columns)
31
+ operation = to_search_vector_operation(language, weights).to_sql
32
+
33
+ options[:index] = {
34
+ using: PostgreSQL.config.full_text_search.default_index_type,
35
+ } if options[:index] == true
36
+
37
+ options.merge(type: :tsvector, as: operation, stored: stored)
38
+ end
39
+
40
+ def self.to_search_weights(columns)
41
+ if !columns.is_a?(Hash)
42
+ extras = columns.size > 3 ? columns.size - 3 : 0
43
+ weights = %w[A B C] + (['D'] * extras)
44
+ columns = Array.wrap(columns).zip(weights).to_h
45
+ end
46
+
47
+ columns.transform_keys(&:to_s)
48
+ end
49
+
50
+ def self.to_search_vector_operation(language, weights)
51
+ language ||= PostgreSQL.config.full_text_search.default_language
52
+ language = ::Arel.sql(language.is_a?(Symbol) ? language.to_s : "'#{language}'")
53
+ simple = weights.size == 1
54
+
55
+ empty_string = ::Arel.sql("''")
56
+ operations = weights.map do |column, weight|
57
+ column = ::Arel.sql(column.to_s)
58
+ weight = ::Arel.sql("'#{weight}'")
59
+
60
+ op = FN.to_tsvector(language, FN.coalesce(column, empty_string))
61
+ op = FN.setweight(op, weight) unless simple
62
+ op
63
+ end
64
+
65
+ FN.concat(*operations)
66
+ end
29
67
  end
30
68
  end
31
69
  end
@@ -18,7 +18,7 @@ module Torque
18
18
  # Find or create the class that will handle the value
19
19
  def lookup(name)
20
20
  const = name.to_s.camelize
21
- namespace = Torque::PostgreSQL.config.enum.namespace
21
+ namespace = PostgreSQL.config.enum.namespace
22
22
 
23
23
  return namespace.const_get(const) if namespace.const_defined?(const)
24
24
  namespace.const_set(const, Class.new(Enum))
@@ -27,7 +27,7 @@ module Torque
27
27
  # Provide a method on the given class to setup which enums will be
28
28
  # manually initialized
29
29
  def include_on(klass, method_name = nil)
30
- method_name ||= Torque::PostgreSQL.config.enum.base_method
30
+ method_name ||= PostgreSQL.config.enum.base_method
31
31
  Builder.include_on(klass, method_name, Builder::Enum) do |builder|
32
32
  defined_enums[builder.attribute.to_s] = builder.subtype.klass
33
33
  end
@@ -46,7 +46,7 @@ module Torque
46
46
  end
47
47
  end
48
48
 
49
- # List of valus as symbols
49
+ # List of values as symbols
50
50
  def keys
51
51
  values.map(&:to_sym)
52
52
  end
@@ -86,7 +86,7 @@ module Torque
86
86
  self.values.include?(value.to_s)
87
87
  end
88
88
 
89
- # Build an active record scope for a given atribute agains a value
89
+ # Build an active record scope for a given attribute against a value
90
90
  def scope(attribute, value)
91
91
  attribute.eq(value)
92
92
  end
@@ -183,7 +183,7 @@ module Torque
183
183
  list_from = :i18n_scopes
184
184
  end
185
185
 
186
- Torque::PostgreSQL.config.enum.send(list_from).map do |key|
186
+ PostgreSQL.config.enum.send(list_from).map do |key|
187
187
  (key % values).to_sym
188
188
  end
189
189
  end
@@ -209,7 +209,7 @@ module Torque
209
209
  end
210
210
  end
211
211
 
212
- # Throw an exception for invalid valus
212
+ # Throw an exception for invalid values
213
213
  def raise_invalid(value)
214
214
  if value.is_a?(Numeric)
215
215
  raise EnumError, "#{value.inspect} is out of bounds of #{self.class.name}"
@@ -218,7 +218,7 @@ module Torque
218
218
  end
219
219
  end
220
220
 
221
- # Throw an exception for comparasion between different enums
221
+ # Throw an exception for comparison between different enums
222
222
  def raise_comparison(other)
223
223
  raise EnumError, "Comparison of #{self.class.name} with #{self.inspect} failed"
224
224
  end
@@ -18,7 +18,7 @@ module Torque
18
18
  # Find or create the class that will handle the value
19
19
  def lookup(name, enum_klass)
20
20
  const = name.to_s.camelize + 'Set'
21
- namespace = Torque::PostgreSQL.config.enum.namespace
21
+ namespace = PostgreSQL.config.enum.namespace
22
22
 
23
23
  return namespace.const_get(const) if namespace.const_defined?(const)
24
24
 
@@ -30,7 +30,7 @@ module Torque
30
30
  # Provide a method on the given class to setup which enum sets will be
31
31
  # manually initialized
32
32
  def include_on(klass, method_name = nil)
33
- method_name ||= Torque::PostgreSQL.config.enum.set_method
33
+ method_name ||= PostgreSQL.config.enum.set_method
34
34
  Builder.include_on(klass, method_name, Builder::Enum, set_features: true) do |builder|
35
35
  defined_enums[builder.attribute.to_s] = builder.subtype
36
36
  end
@@ -76,14 +76,14 @@ module Torque
76
76
  end.reduce(:+)
77
77
  end
78
78
 
79
- # Build an active record scope for a given atribute agains a value
79
+ # Build an active record scope for a given attribute against a value
80
80
  def scope(attribute, value)
81
- attribute.contains(::Arel.array(value, cast: enum_source.type_name))
81
+ attribute.contains(FN.bind_with(attribute, value).pg_cast(type_name))
82
82
  end
83
83
 
84
84
  private
85
85
 
86
- # Allows checking value existance
86
+ # Allows checking value existence
87
87
  def respond_to_missing?(method_name, include_private = false)
88
88
  valid?(method_name) || super
89
89
  end
@@ -226,7 +226,7 @@ module Torque
226
226
  end
227
227
  end
228
228
 
229
- # Throw an exception for invalid valus
229
+ # Throw an exception for invalid values
230
230
  def raise_invalid(value)
231
231
  if value.is_a?(Numeric)
232
232
  raise EnumSetError, "#{value.inspect} is out of bounds of #{self.class.name}"
@@ -235,7 +235,7 @@ module Torque
235
235
  end
236
236
  end
237
237
 
238
- # Throw an exception for comparasion between different enums
238
+ # Throw an exception for comparison between different enums
239
239
  def raise_comparison(other)
240
240
  raise EnumSetError, "Comparison of #{self.class.name} with #{self.inspect} failed"
241
241
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ module Attributes
6
+ # For now, full text search doesn't have it's own class
7
+ module FullTextSearch
8
+ class << self
9
+ # Provide a method on the given class to setup which full text search
10
+ # columns will be manually initialized
11
+ def include_on(klass, method_name = nil)
12
+ method_name ||= PostgreSQL.config.full_text_search.base_method
13
+ Builder.include_on(klass, method_name, Builder::FullTextSearch)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -3,13 +3,13 @@
3
3
  module Torque
4
4
  module PostgreSQL
5
5
  module Attributes
6
- # For naw, period doesn't have it's own class
6
+ # For now, period doesn't have it's own class
7
7
  module Period
8
8
  class << self
9
9
  # Provide a method on the given class to setup which period columns
10
10
  # will be manually initialized
11
11
  def include_on(klass, method_name = nil)
12
- method_name ||= Torque::PostgreSQL.config.period.base_method
12
+ method_name ||= PostgreSQL.config.period.base_method
13
13
  Builder.include_on(klass, method_name, Builder::Period)
14
14
  end
15
15
  end
@@ -1,6 +1,2 @@
1
1
  require_relative 'attributes/lazy'
2
2
  require_relative 'attributes/builder'
3
-
4
- require_relative 'attributes/enum'
5
- require_relative 'attributes/enum_set'
6
- require_relative 'attributes/period'
@@ -136,10 +136,10 @@ module Torque
136
136
 
137
137
  col = table[name]
138
138
  base.select_extra_values += [col.as(as)] unless as.nil?
139
- parts = [col, source.cast(:varchar)]
139
+ parts = [col, source.pg_cast(:varchar)]
140
140
 
141
- columns << ::Arel.array([source]).cast(:varchar, true).as(name)
142
- sub_columns << ::Arel::Nodes::NamedFunction.new('array_append', parts).as(name)
141
+ columns << ::Arel.array([source]).pg_cast(:varchar, true).as(name)
142
+ sub_columns << FN.array_append(*parts).as(name)
143
143
  end
144
144
  end
145
145
 
@@ -16,11 +16,12 @@ module Torque
16
16
  class_attribute :schema, instance_writer: false
17
17
  end
18
18
 
19
- module ClassMethods
19
+ class_methods do
20
20
  delegate :distinct_on, :with, :itself_only, :cast_records, to: :all
21
21
 
22
22
  # Make sure that table name is an instance of TableName class
23
23
  def reset_table_name
24
+ return super unless PostgreSQL.config.schemas.enabled
24
25
  self.table_name = TableName.new(self, super)
25
26
  end
26
27
 
@@ -41,7 +42,7 @@ module Torque
41
42
  next klass.table_name unless klass.physically_inheritances?
42
43
 
43
44
  query = klass.unscoped.where(subclass.primary_key => id)
44
- query.pluck(klass.arel_table['tableoid'].cast('regclass')).first
45
+ query.pluck(klass.arel_table['tableoid'].pg_cast('regclass')).first
45
46
  end
46
47
  end
47
48
 
@@ -200,14 +201,6 @@ module Torque
200
201
  ::ActiveRecord::Reflection.add_reflection(self, name, reflection)
201
202
  end
202
203
 
203
- # Allow extra keyword arguments to be sent to +InsertAll+
204
- unless Torque::PostgreSQL::AR720
205
- def upsert_all(attributes, **xargs)
206
- xargs = xargs.reverse_merge(on_duplicate: :update)
207
- ::ActiveRecord::InsertAll.new(self, attributes, **xargs).execute
208
- end
209
- end
210
-
211
204
  protected
212
205
 
213
206
  # Allow optional select attributes to be loaded manually when they are
@@ -4,7 +4,7 @@ module Torque
4
4
  module PostgreSQL
5
5
  module Collector
6
6
 
7
- # This classe helps to collect data in different ways. Used to configure
7
+ # This class helps to collect data in different ways. Used to configure
8
8
  # auxiliary statements
9
9
  def self.new(*args)
10
10
  klass = Class.new
@@ -4,10 +4,6 @@ module Torque
4
4
  module PostgreSQL
5
5
  include ActiveSupport::Configurable
6
6
 
7
- # Stores a version check for compatibility purposes
8
- AR710 = (ActiveRecord.gem_version >= Gem::Version.new('7.1.0'))
9
- AR720 = (ActiveRecord.gem_version >= Gem::Version.new('7.2.0'))
10
-
11
7
  # Use the same logger as the Active Record one
12
8
  def self.logger
13
9
  ActiveRecord::Base.logger
@@ -41,11 +37,19 @@ module Torque
41
37
  # default. False means that no validation will be performed
42
38
  assoc.belongs_to_many_required_by_default = false
43
39
 
40
+ # Although +belongs_to_many+ does not need a custom handler when joining
41
+ # the last chain scope, this can allow devs to pick which way they prefer:
42
+ # Rails default, or ANY with a single bind to improve prepared statements
43
+ # assoc.optimize_for_binds = false TODO: Add support
44
+
44
45
  end
45
46
 
46
47
  # Configure multiple schemas
47
48
  config.nested(:schemas) do |schemas|
48
49
 
50
+ # Enables schemas handler by this gem, not Rails's own implementation
51
+ schemas.enabled = true
52
+
49
53
  # Defines a list of LIKE-based schemas to not consider for a multiple
50
54
  # schema database
51
55
  schemas.blacklist = %w[information_schema pg_%]
@@ -59,6 +63,10 @@ module Torque
59
63
  # Configure auxiliary statement features
60
64
  config.nested(:auxiliary_statement) do |cte|
61
65
 
66
+ # Enables auxiliary statements handler by this gem, not Rails's own
67
+ # implementation
68
+ cte.enabled = true
69
+
62
70
  # Define the key that is used on auxiliary statements to send extra
63
71
  # arguments to format string or send on a proc
64
72
  cte.send_arguments_key = :args
@@ -76,6 +84,9 @@ module Torque
76
84
  # Configure ENUM features
77
85
  config.nested(:enum) do |enum|
78
86
 
87
+ # Enables enum handler by this gem, not Rails's own implementation
88
+ enum.enabled = true
89
+
79
90
  # The name of the method to be used on any ActiveRecord::Base to
80
91
  # initialize model-based enum features
81
92
  enum.base_method = :torque_enum
@@ -93,7 +104,7 @@ module Torque
93
104
  enum.raise_conflicting = false
94
105
 
95
106
  # Specify the namespace of each enum type of value
96
- enum.namespace = ::Object.const_set('Enum', Module.new)
107
+ enum.namespace = nil
97
108
 
98
109
  # Specify the scopes for I18n translations
99
110
  enum.i18n_scopes = [
@@ -117,6 +128,9 @@ module Torque
117
128
  # Configure geometry data types
118
129
  config.nested(:geometry) do |geometry|
119
130
 
131
+ # Enables geometry handler by this gem, not Rails's own implementation
132
+ geometry.enabled = true
133
+
120
134
  # Define the class that will be handling Point data types after decoding
121
135
  # it. Any class provided here must respond to 'x', and 'y'
122
136
  geometry.point_class = ActiveRecord::Point
@@ -162,6 +176,9 @@ module Torque
162
176
  # Configure period features
163
177
  config.nested(:period) do |period|
164
178
 
179
+ # Enables period handler by this gem
180
+ period.enabled = true
181
+
165
182
  # The name of the method to be used on any ActiveRecord::Base to
166
183
  # initialize model-based period features
167
184
  period.base_method = :period_for
@@ -228,5 +245,78 @@ module Torque
228
245
  }
229
246
 
230
247
  end
248
+
249
+ # Configure period features
250
+ config.nested(:interval) do |interval|
251
+
252
+ # Enables interval handler by this gem, not Rails's own implementation
253
+ interval.enabled = true
254
+
255
+ end
256
+
257
+ # Configure arel additional features
258
+ config.nested(:arel) do |arel|
259
+
260
+ # When provided, the initializer will expose the Arel function helper on
261
+ # the given module
262
+ config.expose_function_helper_on = nil
263
+
264
+ # List of Arel INFIX operators that will be made available for using as
265
+ # methods on Arel::Nodes::Node and Arel::Attribute
266
+ arel.infix_operators = {
267
+ 'contained_by' => '<@',
268
+ 'has_key' => '?',
269
+ 'has_all_keys' => '?&',
270
+ 'has_any_keys' => '?|',
271
+ 'strictly_left' => '<<',
272
+ 'strictly_right' => '>>',
273
+ 'doesnt_right_extend' => '&<',
274
+ 'doesnt_left_extend' => '&>',
275
+ 'adjacent_to' => '-|-',
276
+ }
277
+
278
+ end
279
+
280
+ # Configure full text search features
281
+ config.nested(:full_text_search) do |fts|
282
+
283
+ # Enables full text search handler by this gem
284
+ fts.enabled = true
285
+
286
+ # The name of the method to be used on any ActiveRecord::Base to
287
+ # initialize model-based full text search features
288
+ fts.base_method = :torque_search_for
289
+
290
+ # Defines the default language when generating search vector columns
291
+ fts.default_language = 'english'
292
+
293
+ # Defines the default index type to be used when creating search vector.
294
+ # It still requires that the column requests an index
295
+ fts.default_index_type = :gin
296
+
297
+ end
298
+
299
+ # Configure predicate builder additional features
300
+ config.nested(:predicate_builder) do |builder|
301
+
302
+ # List which handlers are enabled by default
303
+ builder.enabled = %i[regexp arel_attribute enumerator_lazy]
304
+
305
+ # When active, values provided to array attributes will be handled more
306
+ # efficiently. It will use the +ANY+ operator on a equality check and
307
+ # overlaps when the given value is an array
308
+ builder.handle_array_attributes = false
309
+
310
+ # Make sure that the predicate builder will not spend more than 20ms
311
+ # trying to produce the underlying array
312
+ builder.lazy_timeout = 0.02
313
+
314
+ # Since lazy array is uncommon, it is better to limit the number of
315
+ # entries we try to pull so we don't cause a timeout or a long wait
316
+ # iteration
317
+ builder.lazy_limit = 2_000
318
+
319
+ end
320
+
231
321
  end
232
322
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Torque
4
+ module PostgreSQL
5
+ # Simplified module for creating arel functions. This is used internally
6
+ # but can also be made available to other devs on their own projects
7
+ module Function
8
+ class << self
9
+
10
+ # A facilitator to create a bind param that is fully compatible with
11
+ # Arel and ActiveRecord
12
+ def bind(*args)
13
+ attr = ::ActiveRecord::Relation::QueryAttribute.new(*args)
14
+ ::Arel::Nodes::BindParam.new(attr)
15
+ end
16
+
17
+ # Just a shortcut to create a bind param for a model attribute and a
18
+ # value for it
19
+ def bind_for(model, attribute, value)
20
+ bind(attribute, value, model.attribute_types[attribute])
21
+ end
22
+
23
+ # Another shortcut, when we already have the arel attribute at hand
24
+ def bind_with(arel_attribute, value)
25
+ bind(arel_attribute.name, value, arel_attribute.type_caster)
26
+ end
27
+
28
+ # A facilitator to create an infix operation
29
+ def infix(op, left, right)
30
+ ::Arel::Nodes::InfixOperation.new(op, left, right)
31
+ end
32
+
33
+ # A facilitator to use several Infix operators to concatenate all the
34
+ # provided arguments. Arguments won't be sanitized, as other methods
35
+ # under this module
36
+ def concat(*args)
37
+ return args.first if args.one?
38
+ args.reduce { |left, right| infix(:"||", left, right) }
39
+ end
40
+
41
+ # As of now, this indicates that it supports any direct calls, since
42
+ # the idea is to simply map to an Arel function with the same name,
43
+ # without checking if it actually exists
44
+ def respond_to_missing?(*)
45
+ true
46
+ end
47
+
48
+ # This method is used to catch any method calls that are not defined
49
+ # in this module. It will simply return an Arel function with the same
50
+ # name as the method called, passing all arguments to it, without
51
+ # any sanitization
52
+ def method_missing(name, *args, &block)
53
+ ::Arel::Nodes::NamedFunction.new(name.to_s.upcase, args)
54
+ end
55
+
56
+ end
57
+ end
58
+
59
+ FN = Function
60
+ end
61
+ end