torque-postgresql 0.2.16 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +76 -3
  3. data/lib/torque-postgresql.rb +1 -0
  4. data/lib/torque/postgresql.rb +6 -0
  5. data/lib/torque/postgresql/adapter.rb +2 -4
  6. data/lib/torque/postgresql/adapter/database_statements.rb +23 -9
  7. data/lib/torque/postgresql/adapter/oid.rb +12 -1
  8. data/lib/torque/postgresql/adapter/oid/box.rb +28 -0
  9. data/lib/torque/postgresql/adapter/oid/circle.rb +37 -0
  10. data/lib/torque/postgresql/adapter/oid/enum.rb +9 -5
  11. data/lib/torque/postgresql/adapter/oid/enum_set.rb +44 -0
  12. data/lib/torque/postgresql/adapter/oid/line.rb +59 -0
  13. data/lib/torque/postgresql/adapter/oid/range.rb +52 -0
  14. data/lib/torque/postgresql/adapter/oid/segment.rb +73 -0
  15. data/lib/torque/postgresql/adapter/quoting.rb +21 -0
  16. data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -0
  17. data/lib/torque/postgresql/adapter/schema_dumper.rb +10 -1
  18. data/lib/torque/postgresql/arel.rb +3 -0
  19. data/lib/torque/postgresql/arel/infix_operation.rb +42 -0
  20. data/lib/torque/postgresql/arel/nodes.rb +32 -0
  21. data/lib/torque/postgresql/arel/operations.rb +18 -0
  22. data/lib/torque/postgresql/arel/visitors.rb +28 -2
  23. data/lib/torque/postgresql/associations.rb +8 -0
  24. data/lib/torque/postgresql/associations/association.rb +30 -0
  25. data/lib/torque/postgresql/associations/association_scope.rb +116 -0
  26. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +117 -0
  27. data/lib/torque/postgresql/associations/builder.rb +2 -0
  28. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +121 -0
  29. data/lib/torque/postgresql/associations/builder/has_many.rb +15 -0
  30. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +15 -0
  31. data/lib/torque/postgresql/associations/preloader.rb +25 -0
  32. data/lib/torque/postgresql/associations/preloader/association.rb +64 -0
  33. data/lib/torque/postgresql/attributes.rb +2 -0
  34. data/lib/torque/postgresql/attributes/builder.rb +1 -0
  35. data/lib/torque/postgresql/attributes/builder/enum.rb +23 -15
  36. data/lib/torque/postgresql/attributes/builder/period.rb +452 -0
  37. data/lib/torque/postgresql/attributes/enum.rb +11 -8
  38. data/lib/torque/postgresql/attributes/enum_set.rb +256 -0
  39. data/lib/torque/postgresql/attributes/lazy.rb +1 -1
  40. data/lib/torque/postgresql/attributes/period.rb +31 -0
  41. data/lib/torque/postgresql/attributes/type_map.rb +3 -5
  42. data/lib/torque/postgresql/autosave_association.rb +40 -0
  43. data/lib/torque/postgresql/auxiliary_statement.rb +201 -198
  44. data/lib/torque/postgresql/auxiliary_statement/settings.rb +20 -12
  45. data/lib/torque/postgresql/base.rb +161 -2
  46. data/lib/torque/postgresql/config.rb +91 -9
  47. data/lib/torque/postgresql/geometry_builder.rb +92 -0
  48. data/lib/torque/postgresql/i18n.rb +1 -1
  49. data/lib/torque/postgresql/railtie.rb +18 -5
  50. data/lib/torque/postgresql/reflection.rb +21 -0
  51. data/lib/torque/postgresql/reflection/abstract_reflection.rb +109 -0
  52. data/lib/torque/postgresql/reflection/association_reflection.rb +30 -0
  53. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +44 -0
  54. data/lib/torque/postgresql/reflection/has_many_reflection.rb +13 -0
  55. data/lib/torque/postgresql/reflection/runtime_reflection.rb +12 -0
  56. data/lib/torque/postgresql/reflection/through_reflection.rb +11 -0
  57. data/lib/torque/postgresql/relation.rb +11 -10
  58. data/lib/torque/postgresql/relation/auxiliary_statement.rb +11 -18
  59. data/lib/torque/postgresql/relation/inheritance.rb +2 -2
  60. data/lib/torque/postgresql/relation/merger.rb +11 -7
  61. data/lib/torque/postgresql/schema_cache.rb +1 -1
  62. data/lib/torque/postgresql/version.rb +1 -1
  63. data/lib/torque/range.rb +40 -0
  64. metadata +41 -9
@@ -0,0 +1,452 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Attributes
4
+ module Builder
5
+ # TODO: Allow methods to have nil in order to not include that specific method
6
+ class Period
7
+ SUPPORTED_TYPES = %i[daterange tsrange tstzrange].freeze
8
+ CURRENT_GETTERS = {
9
+ daterange: 'Date.today',
10
+ tsrange: 'Time.zone.now',
11
+ tstzrange: 'Time.zone.now',
12
+ }.freeze
13
+
14
+ TYPE_CASTERS = {
15
+ daterange: :date,
16
+ tsrange: :timestamp,
17
+ tstzrange: :timestamp,
18
+ }.freeze
19
+
20
+ attr_accessor :klass, :attribute, :options, :type, :arel_attribute, :default,
21
+ :current_getter, :type_caster, :default_sql, :threshold, :dynamic_threshold,
22
+ :period_module
23
+
24
+ # Start a new builder of methods for period values on
25
+ # ActiveRecord::Base
26
+ def initialize(klass, attribute, _, options)
27
+ @klass = klass
28
+ @attribute = attribute.to_s
29
+ @options = options
30
+ @type = klass.attribute_types[@attribute].type
31
+
32
+ @arel_attribute = klass.arel_table[@attribute]
33
+ @current_getter = CURRENT_GETTERS[type]
34
+ @type_caster = TYPE_CASTERS[type]
35
+
36
+ @default = options[:pessimistic].blank?
37
+ @default_sql = ::Arel.sql(klass.connection.quote(@default))
38
+
39
+ @threshold = options[:threshold].presence
40
+
41
+ raise ArgumentError, <<-MSG.squish unless SUPPORTED_TYPES.include?(type)
42
+ Period cannot be generated for #{attribute} because its type
43
+ #{type} is not supported. Only #{SUPPORTED_TYPES.join(', ')} are supported.
44
+ MSG
45
+ end
46
+
47
+ # Generate all the method names
48
+ def method_names
49
+ @method_names ||= options.fetch(:methods, {}).symbolize_keys
50
+ .reverse_merge(default_method_names)
51
+ end
52
+
53
+ # Get the list of methods associated withe the class
54
+ def klass_method_names
55
+ @klass_method_names ||= method_names.to_a[0..20].to_h
56
+ end
57
+
58
+ # Get the list of methods associated withe the instances
59
+ def instance_method_names
60
+ @instance_method_names ||= method_names.to_a[21..27].to_h
61
+ end
62
+
63
+ # Check if any of the methods that will be created get in conflict
64
+ # with the base class methods
65
+ def conflicting?
66
+ return false if options[:force] == true
67
+
68
+ klass_method_names.values.each { |name| dangerous?(name, true) }
69
+ instance_method_names.values.each { |name| dangerous?(name) }
70
+
71
+ return false
72
+ rescue Interrupt => err
73
+ raise ArgumentError, <<-MSG.squish
74
+ #{subtype.class.name} was not able to generate requested
75
+ methods because the method #{err} already exists in
76
+ #{klass.name}.
77
+ MSG
78
+ end
79
+
80
+ # Create all methods needed
81
+ def build
82
+ @period_module = Module.new
83
+
84
+ build_singleton_methods
85
+ build_instance_methods
86
+
87
+ klass.include period_module
88
+ end
89
+
90
+ # When the time has a threshold, then the real attribute is complex
91
+ def real_arel_attribute
92
+ return arel_attribute unless threshold.present?
93
+
94
+ left = named_function(:lower, arel_attribute) - threshold_value
95
+ right = named_function(:upper, arel_attribute) + threshold_value
96
+
97
+ if type.eql?(:daterange)
98
+ left = left.cast(:date)
99
+ right = right.cast(:date)
100
+ end
101
+
102
+ @real_arel_attribute ||= named_function(type, left, right)
103
+ end
104
+
105
+ # Create an arel named function
106
+ def named_function(name, *args)
107
+ ::Arel::Nodes::NamedFunction.new(name.to_s, args)
108
+ end
109
+
110
+ # Create an arel version of +nullif+ function
111
+ def arel_nullif(*args)
112
+ named_function('nullif', args)
113
+ end
114
+
115
+ # Create an arel version of +coalesce+ function
116
+ def arel_coalesce(*args)
117
+ named_function('coalesce', args)
118
+ end
119
+
120
+ # Create an arel version of the type with the following values
121
+ def arel_convert_to_type(left, right = nil, set_type = nil)
122
+ named_function(set_type || type, [left, right || left])
123
+ end
124
+
125
+ # Convert timestamp range to date range format
126
+ def arel_daterange
127
+ named_function(
128
+ :daterange,
129
+ named_function(:lower, real_arel_attribute).cast(:date),
130
+ named_function(:upper, real_arel_attribute).cast(:date),
131
+ )
132
+ end
133
+
134
+ # Create an arel version of an empty value for the range
135
+ def arel_empty_value
136
+ arel_convert_to_type(::Arel.sql('NULL'))
137
+ end
138
+
139
+ # Create an arel not condition
140
+ def arel_not(value)
141
+ named_function(:not, value)
142
+ end
143
+
144
+ # Get the main arel condition to check the value
145
+ def arel_check_condition(type, value)
146
+ value = ::Arel.sql(klass.connection.quote(value))
147
+
148
+ checker = arel_nullif(real_arel_attribute, arel_empty_value)
149
+ checker = checker.public_send(type, value.cast(type_caster))
150
+ arel_coalesce(checker, default_sql)
151
+ end
152
+
153
+ # Check how to provide the threshold value
154
+ def threshold_value
155
+ @threshold_value ||= begin
156
+ case threshold
157
+ when Symbol, String
158
+ klass.arel_table[threshold]
159
+ when ActiveSupport::Duration
160
+ ::Arel.sql("'#{threshold.to_i} seconds'").cast(:interval)
161
+ when Numeric
162
+ value = threshold.to_i.to_s
163
+ value << type_caster.eql?(:date) ? ' days' : ' seconds'
164
+ ::Arel.sql("'#{value}'").cast(:interval)
165
+ end
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ # Generates the default method names
172
+ def default_method_names
173
+ Torque::PostgreSQL.config.period.method_names.transform_values do |value|
174
+ format(value, attribute)
175
+ end
176
+ end
177
+
178
+ # Check if the method already exists in the reference class
179
+ def dangerous?(method_name, class_method = false)
180
+ if class_method
181
+ if klass.dangerous_class_method?(method_name)
182
+ raise Interrupt, method_name.to_s
183
+ end
184
+ else
185
+ if klass.dangerous_attribute_method?(method_name)
186
+ raise Interrupt, method_name.to_s
187
+ end
188
+ end
189
+ end
190
+
191
+ # Build model methods
192
+ def build_singleton_methods
193
+ attr = attribute
194
+ builder = self
195
+
196
+ # TODO: Rewrite these as string
197
+ klass.scope method_names[:current_on], ->(value) do
198
+ where(builder.arel_check_condition(:contains, value))
199
+ end
200
+
201
+ klass.scope method_names[:current], -> do
202
+ public_send(builder.method_names[:current_on], eval(builder.current_getter))
203
+ end
204
+
205
+ klass.scope method_names[:not_current], -> do
206
+ current_value = eval(builder.current_getter)
207
+ where.not(builder.arel_check_condition(:contains, current_value))
208
+ end
209
+
210
+ klass.scope method_names[:containing], ->(value) do
211
+ value = arel_table[value] if value.is_a?(Symbol)
212
+ value = ::Arel.sql(connection.quote(value)) unless value.respond_to?(:cast)
213
+ where(builder.arel_attribute.contains(value))
214
+ end
215
+
216
+ klass.scope method_names[:not_containing], ->(value) do
217
+ value = arel_table[value] if value.is_a?(Symbol)
218
+ value = ::Arel.sql(connection.quote(value)) unless value.respond_to?(:cast)
219
+ where.not(builder.arel_attribute.contains(value))
220
+ end
221
+
222
+ klass.scope method_names[:overlapping], ->(value, right = nil) do
223
+ value = arel_table[value] if value.is_a?(Symbol)
224
+
225
+ if right.present?
226
+ value = ::Arel.sql(connection.quote(value))
227
+ right = ::Arel.sql(connection.quote(right))
228
+ value = builder.arel_convert_to_type(value, right)
229
+ elsif !value.respond_to?(:cast)
230
+ value = ::Arel.sql(connection.quote(value))
231
+ end
232
+
233
+ where(builder.arel_attribute.overlaps(value))
234
+ end
235
+
236
+ klass.scope method_names[:not_overlapping], ->(value, right = nil) do
237
+ value = arel_table[value] if value.is_a?(Symbol)
238
+
239
+ if right.present?
240
+ value = ::Arel.sql(connection.quote(value))
241
+ right = ::Arel.sql(connection.quote(right))
242
+ value = builder.arel_convert_to_type(value, right)
243
+ elsif !value.respond_to?(:cast)
244
+ value = ::Arel.sql(connection.quote(value))
245
+ end
246
+
247
+ where.not(builder.arel_attribute.overlaps(value))
248
+ end
249
+
250
+ klass.scope method_names[:starting_after], ->(value) do
251
+ value = arel_table[value] if value.is_a?(Symbol)
252
+ value = ::Arel.sql(connection.quote(value)) \
253
+ unless value.is_a?(::Arel::Attributes::Attribute)
254
+
255
+ where(builder.named_function(:lower, builder.arel_attribute).gt(value))
256
+ end
257
+
258
+ klass.scope method_names[:starting_before], ->(value) do
259
+ value = arel_table[value] if value.is_a?(Symbol)
260
+ value = ::Arel.sql(connection.quote(value)) \
261
+ unless value.is_a?(::Arel::Attributes::Attribute)
262
+
263
+ where(builder.named_function(:lower, builder.arel_attribute).lt(value))
264
+ end
265
+
266
+ klass.scope method_names[:finishing_after], ->(value) do
267
+ value = arel_table[value] if value.is_a?(Symbol)
268
+ value = ::Arel.sql(connection.quote(value)) \
269
+ unless value.is_a?(::Arel::Attributes::Attribute)
270
+
271
+ where(builder.named_function(:upper, builder.arel_attribute).gt(value))
272
+ end
273
+
274
+ klass.scope method_names[:finishing_before], ->(value) do
275
+ value = arel_table[value] if value.is_a?(Symbol)
276
+ value = ::Arel.sql(connection.quote(value)) \
277
+ unless value.is_a?(::Arel::Attributes::Attribute)
278
+
279
+ where(builder.named_function(:upper, builder.arel_attribute).lt(value))
280
+ end
281
+
282
+ if threshold.present?
283
+ klass.scope method_names[:real_containing], ->(value) do
284
+ value = arel_table[value] if value.is_a?(Symbol)
285
+ value = ::Arel.sql(connection.quote(value)) unless value.respond_to?(:cast)
286
+ where(builder.real_arel_attribute.contains(value))
287
+ end
288
+
289
+ klass.scope method_names[:real_overlapping], ->(value, right = nil) do
290
+ value = arel_table[value] if value.is_a?(Symbol)
291
+
292
+ if right.present?
293
+ value = ::Arel.sql(connection.quote(value))
294
+ right = ::Arel.sql(connection.quote(right))
295
+ value = builder.arel_convert_to_type(value, right)
296
+ elsif !value.respond_to?(:cast)
297
+ value = ::Arel.sql(connection.quote(value))
298
+ end
299
+
300
+ where(builder.real_arel_attribute.overlaps(value))
301
+ end
302
+
303
+ klass.scope method_names[:real_starting_after], ->(value) do
304
+ value = arel_table[value] if value.is_a?(Symbol)
305
+ condition = builder.named_function(:lower, builder.arel_attribute)
306
+ condition -= builder.threshold_value
307
+ condition = condition.cast(:date) if builder.type.eql?(:daterange)
308
+ where(condition.gt(value))
309
+ end
310
+
311
+ klass.scope method_names[:real_starting_before], ->(value) do
312
+ value = arel_table[value] if value.is_a?(Symbol)
313
+ condition = builder.named_function(:lower, builder.arel_attribute)
314
+ condition -= builder.threshold_value
315
+ condition = condition.cast(:date) if builder.type.eql?(:daterange)
316
+ where(condition.lt(value))
317
+ end
318
+
319
+ klass.scope method_names[:real_finishing_after], ->(value) do
320
+ value = arel_table[value] if value.is_a?(Symbol)
321
+ condition = builder.named_function(:upper, builder.arel_attribute)
322
+ condition += builder.threshold_value
323
+ condition = condition.cast(:date) if builder.type.eql?(:daterange)
324
+ where(condition.gt(value))
325
+ end
326
+
327
+ klass.scope method_names[:real_finishing_before], ->(value) do
328
+ value = arel_table[value] if value.is_a?(Symbol)
329
+ condition = builder.named_function(:upper, builder.arel_attribute)
330
+ condition += builder.threshold_value
331
+ condition = condition.cast(:date) if builder.type.eql?(:daterange)
332
+ where(condition.lt(value))
333
+ end
334
+ end
335
+
336
+ unless type.eql?(:daterange)
337
+ klass.scope method_names[:containing_date], ->(value) do
338
+ value = arel_table[value] if value.is_a?(Symbol)
339
+ value = ::Arel.sql(connection.quote(value)) unless value.respond_to?(:cast)
340
+ where(builder.arel_daterange.contains(value))
341
+ end
342
+
343
+ klass.scope method_names[:not_containing_date], ->(value) do
344
+ value = arel_table[value] if value.is_a?(Symbol)
345
+ value = ::Arel.sql(connection.quote(value)) unless value.respond_to?(:cast)
346
+ where.not(builder.arel_daterange.contains(value))
347
+ end
348
+
349
+ klass.scope method_names[:overlapping_date], ->(value, right = nil) do
350
+ value = arel_table[value] if value.is_a?(Symbol)
351
+
352
+ if right.present?
353
+ value = ::Arel.sql(connection.quote(value))
354
+ right = ::Arel.sql(connection.quote(right))
355
+ value = builder.arel_convert_to_type(value, right, :daterange)
356
+ elsif !value.respond_to?(:cast)
357
+ value = ::Arel.sql(connection.quote(value))
358
+ end
359
+
360
+ where(builder.arel_daterange.overlaps(value))
361
+ end
362
+
363
+ klass.scope method_names[:not_overlapping_date], ->(value, right = nil) do
364
+ value = arel_table[value] if value.is_a?(Symbol)
365
+
366
+ if right.present?
367
+ value = ::Arel.sql(connection.quote(value))
368
+ right = ::Arel.sql(connection.quote(right))
369
+ value = builder.arel_convert_to_type(value, right, :daterange)
370
+ elsif !value.respond_to?(:cast)
371
+ value = ::Arel.sql(connection.quote(value))
372
+ end
373
+
374
+ where.not(builder.arel_daterange.overlaps(value))
375
+ end
376
+ end
377
+ end
378
+
379
+ # Build model instance methods
380
+ def build_instance_methods
381
+ attr = attribute
382
+ builder = self
383
+
384
+ attr_threshold = threshold
385
+ attr_threshold = attr_threshold.to_sym if attr_threshold.is_a?(String)
386
+ attr_threshold = attr_threshold.seconds if attr_threshold.is_a?(Numeric)
387
+
388
+ # TODO: Rewrite these as string
389
+ period_module.module_eval do
390
+ define_method(builder.method_names[:current?]) do
391
+ public_send(builder.method_names[:current_on?], eval(builder.current_getter))
392
+ end
393
+
394
+ define_method(builder.method_names[:current_on?]) do |value|
395
+ attr_value = builder.threshold ? builder.method_names[:real] : attr
396
+ attr_value = public_send(attr_value)
397
+
398
+ return builder.default if attr_value.nil? ||
399
+ (attr_value.min.try(:infinite?) && attr_value.max.try(:infinite?))
400
+
401
+ attr_value.min < value && attr_value.max > value
402
+ end
403
+
404
+ define_method(builder.method_names[:start]) do
405
+ public_send(attr)&.min
406
+ end
407
+
408
+ define_method(builder.method_names[:finish]) do
409
+ public_send(attr)&.max
410
+ end
411
+
412
+ if attr_threshold.present?
413
+ define_method(builder.method_names[:start]) do
414
+ public_send(attr)&.min
415
+ end
416
+
417
+ define_method(builder.method_names[:finish]) do
418
+ public_send(attr)&.max
419
+ end
420
+
421
+ define_method(builder.method_names[:real]) do
422
+ left = public_send(builder.method_names[:real_start])
423
+ right = public_send(builder.method_names[:real_finish])
424
+ return unless left || right
425
+
426
+ left ||= -::Float::INFINITY
427
+ right ||= ::Float::INFINITY
428
+
429
+ (left..right)
430
+ end
431
+
432
+ define_method(builder.method_names[:real_start]) do
433
+ threshold = attr_threshold
434
+ threshold = public_send(threshold) if threshold.is_a?(Symbol)
435
+ result = public_send(attr)&.min.try(:-, threshold)
436
+ builder.type.eql?(:daterange) ? result&.to_date : result
437
+ end
438
+
439
+ define_method(builder.method_names[:real_finish]) do
440
+ threshold = attr_threshold
441
+ threshold = public_send(threshold) if threshold.is_a?(Symbol)
442
+ result = public_send(attr)&.max.try(:+, threshold)
443
+ builder.type.eql?(:daterange) ? result&.to_date : result
444
+ end
445
+ end
446
+ end
447
+ end
448
+ end
449
+ end
450
+ end
451
+ end
452
+ end
@@ -11,7 +11,7 @@ module Torque
11
11
  class << self
12
12
  include Enumerable
13
13
 
14
- delegate :each, :sample, to: :members
14
+ delegate :each, :sample, :size, :length, to: :members
15
15
 
16
16
  # Find or create the class that will handle the value
17
17
  def lookup(name)
@@ -72,8 +72,7 @@ module Torque
72
72
  # see https://github.com/rails/rails/blob/v5.0.0/activerecord/lib/active_record/fixtures.rb#L656
73
73
  # see https://github.com/rails/rails/blob/v5.0.0/activerecord/lib/active_record/validations/uniqueness.rb#L101
74
74
  def fetch(value, *)
75
- return nil unless values.include?(value)
76
- send(value)
75
+ new(value.to_s) if values.include?(value)
77
76
  end
78
77
  alias [] fetch
79
78
 
@@ -89,11 +88,16 @@ module Torque
89
88
  self.values.include?(value.to_s)
90
89
  end
91
90
 
91
+ # Build an active record scope for a given atribute agains a value
92
+ def scope(attribute, value)
93
+ attribute.eq(value)
94
+ end
95
+
92
96
  private
93
97
 
94
98
  # Allows checking value existance
95
99
  def respond_to_missing?(method_name, include_private = false)
96
- valid?(method_name)
100
+ valid?(method_name) || super
97
101
  end
98
102
 
99
103
  # Allow fast creation of values
@@ -165,7 +169,7 @@ module Torque
165
169
 
166
170
  # Change the inspection to show the enum name
167
171
  def inspect
168
- nil? ? 'nil' : "#<#{self.class.name} #{super}>"
172
+ nil? ? 'nil' : ":#{to_s}"
169
173
  end
170
174
 
171
175
  private
@@ -199,9 +203,9 @@ module Torque
199
203
  name = method_name.to_s
200
204
 
201
205
  if name.chomp!('?')
202
- self == name.tr('_', '-') || self == name
206
+ self == name
203
207
  elsif name.chomp!('!')
204
- replace(name)
208
+ replace(name) unless self == name
205
209
  else
206
210
  super
207
211
  end
@@ -220,7 +224,6 @@ module Torque
220
224
  def raise_comparison(other)
221
225
  raise EnumError, "Comparison of #{self.class.name} with #{self.inspect} failed"
222
226
  end
223
-
224
227
  end
225
228
 
226
229
  # Create the methods related to the attribute to handle the enum type