arel_extensions 2.3.3 → 2.5.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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +92 -43
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +131 -5
  5. data/Makefile +48 -0
  6. data/NEWS.md +22 -0
  7. data/README.md +32 -5
  8. data/Rakefile +5 -3
  9. data/arel_extensions.gemspec +3 -5
  10. data/dev/arelx.dockerfile +13 -16
  11. data/dev/compose.yaml +26 -28
  12. data/gemfiles/rails5.gemfile +15 -8
  13. data/gemfiles/rails5_1_4.gemfile +11 -9
  14. data/gemfiles/rails5_2.gemfile +16 -9
  15. data/gemfiles/rails6.gemfile +13 -7
  16. data/gemfiles/rails6_1.gemfile +13 -7
  17. data/gemfiles/rails7.gemfile +15 -9
  18. data/gemfiles/rails7_1.gemfile +15 -8
  19. data/gemfiles/rails7_2.gemfile +12 -6
  20. data/gemfiles/rails8.gemfile +40 -0
  21. data/gemfiles/rails8_1.gemfile +41 -0
  22. data/gemspecs/arel_extensions-v1.gemspec +3 -5
  23. data/gemspecs/arel_extensions-v2.gemspec +3 -5
  24. data/lib/arel_extensions/aliases.rb +3 -3
  25. data/lib/arel_extensions/attributes.rb +2 -0
  26. data/lib/arel_extensions/boolean_functions.rb +9 -7
  27. data/lib/arel_extensions/common_sql_functions.rb +5 -3
  28. data/lib/arel_extensions/comparators.rb +6 -4
  29. data/lib/arel_extensions/constants.rb +13 -0
  30. data/lib/arel_extensions/date_duration.rb +2 -0
  31. data/lib/arel_extensions/helpers.rb +18 -19
  32. data/lib/arel_extensions/insert_manager.rb +5 -1
  33. data/lib/arel_extensions/math.rb +40 -44
  34. data/lib/arel_extensions/math_functions.rb +29 -31
  35. data/lib/arel_extensions/nodes/aggregate_function.rb +4 -4
  36. data/lib/arel_extensions/nodes/blank.rb +6 -4
  37. data/lib/arel_extensions/nodes/byte_size.rb +11 -0
  38. data/lib/arel_extensions/nodes/case.rb +16 -13
  39. data/lib/arel_extensions/nodes/cast.rb +3 -5
  40. data/lib/arel_extensions/nodes/char_length.rb +11 -0
  41. data/lib/arel_extensions/nodes/coalesce.rb +1 -1
  42. data/lib/arel_extensions/nodes/collate.rb +1 -1
  43. data/lib/arel_extensions/nodes/concat.rb +6 -6
  44. data/lib/arel_extensions/nodes/date_diff.rb +40 -49
  45. data/lib/arel_extensions/nodes/duration.rb +2 -2
  46. data/lib/arel_extensions/nodes/format.rb +2 -2
  47. data/lib/arel_extensions/nodes/formatted_date.rb +2 -2
  48. data/lib/arel_extensions/nodes/formatted_number.rb +2 -2
  49. data/lib/arel_extensions/nodes/function.rb +7 -7
  50. data/lib/arel_extensions/nodes/is_null.rb +2 -0
  51. data/lib/arel_extensions/nodes/json.rb +27 -15
  52. data/lib/arel_extensions/nodes/levenshtein_distance.rb +2 -2
  53. data/lib/arel_extensions/nodes/locate.rb +1 -1
  54. data/lib/arel_extensions/nodes/matches.rb +2 -2
  55. data/lib/arel_extensions/nodes/power.rb +2 -2
  56. data/lib/arel_extensions/nodes/rand.rb +2 -2
  57. data/lib/arel_extensions/nodes/repeat.rb +2 -2
  58. data/lib/arel_extensions/nodes/replace.rb +3 -3
  59. data/lib/arel_extensions/nodes/rollup.rb +4 -4
  60. data/lib/arel_extensions/nodes/round.rb +3 -3
  61. data/lib/arel_extensions/nodes/select.rb +1 -1
  62. data/lib/arel_extensions/nodes/std.rb +4 -4
  63. data/lib/arel_extensions/nodes/substring.rb +52 -2
  64. data/lib/arel_extensions/nodes/then.rb +1 -1
  65. data/lib/arel_extensions/nodes/trim.rb +2 -2
  66. data/lib/arel_extensions/nodes/union.rb +3 -3
  67. data/lib/arel_extensions/nodes/union_all.rb +3 -3
  68. data/lib/arel_extensions/nodes/wday.rb +2 -2
  69. data/lib/arel_extensions/null_functions.rb +5 -7
  70. data/lib/arel_extensions/predications.rb +23 -19
  71. data/lib/arel_extensions/string_functions.rb +37 -28
  72. data/lib/arel_extensions/version.rb +3 -1
  73. data/lib/arel_extensions/visitors/convert_format.rb +2 -2
  74. data/lib/arel_extensions/visitors/ibm_db.rb +5 -5
  75. data/lib/arel_extensions/visitors/mssql.rb +111 -97
  76. data/lib/arel_extensions/visitors/mysql.rb +101 -67
  77. data/lib/arel_extensions/visitors/oracle.rb +113 -103
  78. data/lib/arel_extensions/visitors/oracle12.rb +5 -6
  79. data/lib/arel_extensions/visitors/postgresql.rb +119 -92
  80. data/lib/arel_extensions/visitors/sqlite.rb +72 -64
  81. data/lib/arel_extensions/visitors/to_sql.rb +85 -75
  82. data/lib/arel_extensions/visitors.rb +7 -7
  83. data/lib/arel_extensions/warning.rb +5 -5
  84. data/lib/arel_extensions.rb +47 -31
  85. data/test/arelx_test_helper.rb +8 -6
  86. data/test/config_loader.rb +9 -0
  87. data/test/database.yml +8 -6
  88. data/test/real_db_test.rb +33 -33
  89. data/test/support/fake_record.rb +3 -1
  90. data/test/visitors/test_bulk_insert_oracle.rb +2 -2
  91. data/test/visitors/test_bulk_insert_sqlite.rb +2 -2
  92. data/test/visitors/test_to_sql.rb +2 -2
  93. data/test/with_ar/all_agnostic_test.rb +214 -32
  94. data/test/with_ar/insert_agnostic_test.rb +2 -2
  95. data/test/with_ar/test_bulk_sqlite.rb +2 -2
  96. data/test/with_ar/test_math_sqlite.rb +2 -2
  97. data/test/with_ar/test_string_mysql.rb +2 -2
  98. data/test/with_ar/test_string_sqlite.rb +2 -2
  99. data/version_v1.rb +3 -1
  100. data/version_v2.rb +3 -1
  101. metadata +9 -33
  102. data/bin/compose +0 -6
  103. data/gemfiles/rails3.gemfile +0 -20
  104. data/gemfiles/rails4_2.gemfile +0 -38
@@ -1,11 +1,27 @@
1
1
  require 'arelx_test_helper'
2
2
  require 'date'
3
3
 
4
+ # class ActiveRecord::ConnectionAdapters::SQLServerAdapter
5
+ # # We use *args to avoid breaking signature changes between versions.
6
+ # # internal_exec_sql_query(sql, name = "SQL", binds = [], prepare: false)
7
+ # def internal_exec_sql_query(*args)
8
+ # sql = args.first
9
+ # puts "[DEBUG] MSSQL Query: #{sql}\n"
10
+
11
+ # # Call original implementation (defined in the DatabaseStatements module included in this class)
12
+ # super
13
+ # rescue => e
14
+ # # Catch the crash to print the specific query that caused it one last time
15
+ # puts "[CRASH]: #{args.first}\n"
16
+ # raise e
17
+ # end
18
+ # end
19
+
4
20
  module ArelExtensions
5
21
  module WithAr
6
22
  class ListTest < Minitest::Test
7
23
  def connect_db
8
- ActiveRecord::Base.configurations = YAML.load_file('test/database.yml')
24
+ ActiveRecord::Base.configurations = ConfigLoader.load('test/database.yml')
9
25
  if ENV['DB'] == 'oracle' && ((defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx') || (RUBY_PLATFORM == 'java')) # not supported
10
26
  @env_db = (RUBY_PLATFORM == 'java' ? 'jdbc-sqlite' : 'sqlite')
11
27
  skip "Platform not supported (DB: #{ENV['DB']}, RUBY_ENGINE: #{RUBY_ENGINE}, RUBY_PLATFORM: #{RUBY_PLATFORM})"
@@ -13,7 +29,7 @@ module ArelExtensions
13
29
  @env_db = ENV['DB']
14
30
  end
15
31
  ActiveRecord::Base.establish_connection(@env_db.try(:to_sym) || (RUBY_PLATFORM == 'java' ? :"jdbc-sqlite" : :sqlite))
16
- if ActiveRecord::VERSION::MAJOR >= 7
32
+ if ACTIVE_RECORD_VERSION >= V7_0
17
33
  ActiveRecord.default_timezone = :utc
18
34
  else
19
35
  ActiveRecord::Base.default_timezone = :utc
@@ -36,6 +52,7 @@ module ArelExtensions
36
52
  t.column :duration, :time
37
53
  t.column :other, :string
38
54
  t.column :score, :decimal, precision: 20, scale: 10
55
+ t.column :enabled, :boolean
39
56
  end
40
57
  @cnx.drop_table(:product_tests) rescue nil
41
58
  @cnx.create_table :product_tests do |t|
@@ -58,9 +75,9 @@ module ArelExtensions
58
75
  @lucas = User.where(id: u.id)
59
76
  u = User.create age: 15, name: 'Sophie', created_at: d, score: 20.16
60
77
  @sophie = User.where(id: u.id)
61
- u = User.create age: 20, name: 'Camille', created_at: d, score: -20.16, comments: ''
78
+ u = User.create age: 20, name: 'Camille', created_at: d, score: -20.16, comments: '', enabled: false
62
79
  @camille = User.where(id: u.id)
63
- u = User.create age: 21, name: 'Arthur', created_at: d, score: 65.62, comments: 'arrêté'
80
+ u = User.create age: 21, name: 'Arthur', created_at: d, score: 65.62, comments: 'arrêté', enabled: true
64
81
  @arthur = User.where(id: u.id)
65
82
  u = User.create age: 23, name: 'Myung', created_at: d, score: 20.16, comments: ' '
66
83
  @myung = User.where(id: u.id)
@@ -74,7 +91,14 @@ module ArelExtensions
74
91
  @justin = User.where(id: u.id)
75
92
  u = User.create age: nil, name: 'nilly', created_at: nil, score: nil
76
93
  @nilly = User.where(id: u.id)
77
-
94
+ u = User.create age: nil, name: 'esmé', created_at: nil, score: nil
95
+ @esme = User.where(id: u.id)
96
+ u = User.create age: nil, name: 'esmé ', created_at: nil, score: nil
97
+ @esme2 = User.where(id: u.id)
98
+ u = User.create age: nil, name: nil, created_at: nil, score: nil
99
+ @all_nil = User.where(id: u.id)
100
+
101
+ @id = User.arel_table[:id]
78
102
  @age = User.arel_table[:age]
79
103
  @name = User.arel_table[:name]
80
104
  @score = User.arel_table[:score]
@@ -84,6 +108,7 @@ module ArelExtensions
84
108
  @duration = User.arel_table[:duration]
85
109
  @price = Product.arel_table[:price]
86
110
  @other = User.arel_table[:other]
111
+ @enabled = User.arel_table[:enabled]
87
112
  @not_in_table = User.arel_table[:not_in_table]
88
113
 
89
114
  @ut = User.arel_table
@@ -96,7 +121,9 @@ module ArelExtensions
96
121
  end
97
122
 
98
123
  def t(scope, node)
99
- scope.select(node.as('res')).to_a.first.res
124
+ res = scope.select(node.as('res'))
125
+ # puts "[scope] #{res.to_sql}"
126
+ res.to_a.first.res
100
127
  end
101
128
 
102
129
  # manage the difference between adapters that handle or not json type
@@ -150,7 +177,7 @@ module ArelExtensions
150
177
  def test_rand
151
178
  assert 42 != User.select(Arel.rand.as('res')).first.res
152
179
  assert 0 <= User.select(Arel.rand.abs.as('res')).first.res
153
- assert_equal 10, User.order(Arel.rand).limit(50).count
180
+ assert_equal 13, User.order(Arel.rand).limit(50).count
154
181
  end
155
182
 
156
183
  def test_round
@@ -165,13 +192,13 @@ module ArelExtensions
165
192
  skip 'SQL Server forces order?' # TODO
166
193
  assert_equal 83, User.select((@age.sum + 1).as('res'), User.arel_table[:id].sum).take(50).reorder(@age).first.res
167
194
  assert_equal 164, User.reorder(nil).select((@age.sum + @age.sum).as('res'), User.arel_table[:id].sum).take(50).first.res
168
- assert_equal 246, User.reorder(nil).select(((@age * 3).sum).as('res'), User.arel_table[:id].sum).take(50).first.res
169
- assert_equal 4234, User.reorder(nil).select(((@age * @age).sum).as('res'), User.arel_table[:id].sum).take(50).first.res
195
+ assert_equal 246, User.reorder(nil).select((@age * 3).sum.as('res'), User.arel_table[:id].sum).take(50).first.res
196
+ assert_equal 4234, User.reorder(nil).select((@age * @age).sum.as('res'), User.arel_table[:id].sum).take(50).first.res
170
197
  else
171
198
  assert_equal 83, User.select((@age.sum + 1).as('res')).take(50).first.res
172
199
  assert_equal 164, User.select((@age.sum + @age.sum).as('res')).take(50).first.res
173
200
  assert_equal 246, User.select((@age * 3).sum.as('res')).take(50).first.res
174
- assert_equal 4234, User.select(((@age * @age).sum).as('res')).take(50).first.res
201
+ assert_equal 4234, User.select((@age * @age).sum.as('res')).take(50).first.res
175
202
  end
176
203
  end
177
204
 
@@ -188,7 +215,7 @@ module ArelExtensions
188
215
  end
189
216
 
190
217
  def test_rollup
191
- skip "sqlite not supported" if $sqlite
218
+ skip 'sqlite not supported' if $sqlite
192
219
  at = User.arel_table
193
220
  # single
194
221
  q = User.select(at[:name], at[:age].sum).group(Arel::Nodes::RollUp.new([at[:name]]))
@@ -235,8 +262,9 @@ module ArelExtensions
235
262
  assert_equal 'Camille Camille', t(@camille, @name + ' ' + @name)
236
263
  assert_equal 'Laure 2', t(@laure, @name + ' ' + 2)
237
264
  assert_equal 'Test Laure', t(@laure, Arel.quoted('Test ') + @name)
265
+ end
238
266
 
239
- skip 'No group_concat in SqlServer before 2017' if @env_db == 'mssql'
267
+ def test_group_concat
240
268
  assert_equal 'Lucas Sophie', t(User.where(name: %w[Lucas Sophie]), @name.group_concat(' '))
241
269
  assert_equal 'Lucas,Sophie', t(User.where(name: %w[Lucas Sophie]), @name.group_concat(','))
242
270
  assert_equal 'Lucas,Sophie', t(User.where(name: %w[Lucas Sophie]), @name.group_concat)
@@ -253,12 +281,27 @@ module ArelExtensions
253
281
  assert_equal 7, t(@camille, @name.length)
254
282
  assert_equal 7, t(@camille, @name.length.round.abs)
255
283
  assert_equal 42, t(@laure, @name.length + 37)
284
+
285
+ if @env_db == 'mssql'
286
+ # By default it's UTF-16, and configuring the CI to be UTF-8 is a bit of a hassle.
287
+ assert_equal 4, t(@esme, @name.byte_size)
288
+ assert_equal 5, t(@esme2, @name.byte_size)
289
+ else
290
+ assert_equal 5, t(@esme, @name.byte_size)
291
+ assert_equal 6, t(@esme2, @name.byte_size)
292
+ end
293
+ assert_equal 0, t(@all_nil, @name.byte_size)
294
+
295
+ assert_equal 4, t(@esme, @name.char_length)
296
+ assert_equal 5, t(@esme2, @name.char_length)
297
+ assert_equal 0, t(@all_nil, @name.char_length)
256
298
  end
257
299
 
258
300
  def test_md5
259
301
  skip "Sqlite can't do md5" if $sqlite
260
302
  assert_equal 'e2cf99ca82a7e829d2a4ac85c48154d0', t(@camille, @name.md5)
261
303
  assert_equal 'c3d41bf5efb468a1bcce53bd53726c85', t(@lucas, @name.md5)
304
+ assert_equal '6eb6a030bcce166534b95bc2ab45d9cf', t(@lucas, Arel.quoted('a' * 50).md5)
262
305
  end
263
306
 
264
307
  def test_locate
@@ -284,14 +327,96 @@ module ArelExtensions
284
327
  else
285
328
  assert_equal('', t(@lucas, @name[42]))
286
329
  end
287
- assert_equal 'Lu', t(@lucas, @name[0, 2])
288
- assert_equal 'Lu', t(@lucas, @name[0..1])
289
330
 
290
331
  # substring should accept string function
291
332
  assert_equal 'Ce', t(@camille, @name.substring(1, 1).concat('e'))
292
333
  assert_equal 'Ce', t(@camille, @name.substring(1, 1) + 'e')
293
334
  end
294
335
 
336
+ def assert_substring(expected, scope, node)
337
+ expected = nil if expected == '' && @env_db == 'oracle'
338
+ actual = t(scope, node)
339
+ # Ruby 3.0+ requires assert_nil
340
+ expected.nil? ? assert_nil(actual) : assert_equal(expected, actual)
341
+ end
342
+
343
+ def test_substring_range
344
+ # exclusive end
345
+ assert_substring 'uc', @lucas, @name[1...3]
346
+ assert_substring 'uca', @lucas, @name[1...-1]
347
+
348
+ # boundary: begin exactly at length is valid (empty result),
349
+ # begin one past length is out of range (nil)
350
+ assert_substring '', @lucas, @name[5..6]
351
+ assert_substring nil, @lucas, @name[6..7]
352
+ assert_substring nil, @lucas, @name[100..200]
353
+ assert_substring nil, @lucas, @name[-100..-1]
354
+
355
+ # reversed / empty-result ranges, same-sign and mixed-sign
356
+ assert_substring '', @lucas, @name[3..1]
357
+ assert_substring '', @lucas, @name[-3..1]
358
+
359
+ # singleton slices
360
+ assert_substring 'L', @lucas, @name[0..0]
361
+ assert_substring 's', @lucas, @name[4..4]
362
+
363
+ # a second fixture, sanity-checking against a longer string
364
+ assert_substring 'mil', @camille, @name[2..4]
365
+ assert_substring 'lle', @camille, @name[-3..-1]
366
+
367
+ # NULL column
368
+ assert_nil t(@all_nil, @name[0..2])
369
+
370
+ # trailing whitespace
371
+ assert_substring ' ', @esme2, @name[4..4]
372
+ assert_substring 'esmé ', @esme2, @name[0..-1]
373
+
374
+ # passing a Range and an explicit length together is contradictory.
375
+ assert_raises(ArgumentError) { @name.substring(1..2, 5) }
376
+
377
+ end
378
+
379
+ def endless_range_supported?
380
+ Range.new(0, nil)
381
+ true
382
+ rescue ArgumentError
383
+ false
384
+ end
385
+
386
+ def beginless_range_supported?
387
+ Range.new(nil, 0)
388
+ true
389
+ rescue ArgumentError
390
+ false
391
+ end
392
+
393
+ def test_substring_range_endless
394
+ skip 'endless ranges not supported on this interpreter' if !endless_range_supported?
395
+
396
+ assert_substring 'as', @lucas, @name[Range.new(-2, nil)]
397
+ assert_substring 'Lucas', @lucas, @name[Range.new(-5, nil)] # begin == -length: still in range
398
+ assert_substring nil, @lucas, @name[Range.new(-6, nil)] # begin == -length - 1: out of range
399
+ assert_substring nil, @lucas, @name[Range.new(-100, nil)]
400
+
401
+ assert_nil t(@all_nil, @name[Range.new(-2, nil)])
402
+ assert_substring ' ', @esme2, @name[Range.new(-1, nil)]
403
+
404
+ assert_substring 'as', @lucas, @name.substring(Range.new(-2, nil))
405
+ end
406
+
407
+ def test_substring_range_beginless
408
+ skip 'beginless ranges not supported on this interpreter' if !beginless_range_supported?
409
+
410
+ assert_substring 'Luc', @lucas, @name[Range.new(nil, 2)]
411
+ assert_substring 'Lucas', @lucas, @name[Range.new(nil, -1)]
412
+ assert_substring 'Luca', @lucas, @name[Range.new(nil, -2)]
413
+
414
+ assert_nil t(@all_nil, @name[Range.new(nil, 3)])
415
+ assert_substring 'esmé', @esme2, @name[Range.new(nil, 3)]
416
+
417
+ assert_substring 'Luc', @lucas, @name.substring(Range.new(nil, 2))
418
+ end
419
+
295
420
  def test_find_in_set
296
421
  skip "Sqlite version can't load extension for find_in_set" if $sqlite && $load_extension_disabled
297
422
  skip 'SQL Server does not know about FIND_IN_SET' if @env_db == 'mssql'
@@ -341,9 +466,9 @@ module ArelExtensions
341
466
  skip "Sqlite version can't load extension for regexp" if $sqlite && $load_extension_disabled
342
467
  skip 'SQL Server does not know about REGEXP without extensions' if @env_db == 'mssql'
343
468
  assert_equal 1, User.where(@name =~ '^M').count
344
- assert_equal 8, User.where(@name !~ '^L').count
469
+ assert_equal 10, User.where(@name !~ '^L').count
345
470
  assert_equal 1, User.where(@name =~ /^M/).count
346
- assert_equal 8, User.where(@name !~ /^L/).count
471
+ assert_equal 10, User.where(@name !~ /^L/).count
347
472
  end
348
473
 
349
474
  def test_regex_matches
@@ -356,8 +481,12 @@ module ArelExtensions
356
481
  def test_imatches
357
482
  # puts User.where(@name.imatches('m%')).to_sql
358
483
  assert_equal 1, User.where(@name.imatches('m%')).count
359
- assert_equal 4, User.where(@name.imatches_any(['L%', '%e'])).count
360
- assert_equal 8, User.where(@name.idoes_not_match('L%')).count
484
+ if @env_db == 'mysql'
485
+ assert_equal 5, User.where(@name.imatches_any(['L%', '%e'])).count
486
+ else
487
+ assert_equal 4, User.where(@name.imatches_any(['L%', '%e'])).count
488
+ end
489
+ assert_equal 10, User.where(@name.idoes_not_match('L%')).count
361
490
  end
362
491
 
363
492
  def test_replace
@@ -382,8 +511,8 @@ module ArelExtensions
382
511
  skip "Sqlite version can't load extension for soundex" if $sqlite && $load_extension_disabled
383
512
  skip "PostgreSql version can't load extension for soundex" if @env_db == 'postgresql'
384
513
  assert_equal 'C540', t(@camille, @name.soundex)
385
- assert_equal 10, User.where(@name.soundex.eq(@name.soundex)).count
386
- assert_equal 10, User.where(@name.soundex == @name.soundex).count
514
+ assert_equal 12, User.where(@name.soundex.eq(@name.soundex)).count
515
+ assert_equal 12, User.where(@name.soundex == @name.soundex).count
387
516
  end
388
517
 
389
518
  def test_change_case
@@ -495,7 +624,7 @@ module ArelExtensions
495
624
  # TODO: Standarize timezone conversion across all databases.
496
625
  # This test case will be refactored and should work the same across all vendors.
497
626
  if ENV['DB'] == 'mssql' && /Microsoft SQL Server (\d+)/.match(ActiveRecord::Base.connection.select_value('SELECT @@version'))[1].to_i < 2016
498
- skip "SQL Server < 2016 is not currently supported"
627
+ skip 'SQL Server < 2016 is not currently supported'
499
628
  end
500
629
 
501
630
  tz = ENV['DB'] == 'mssql' ? time_zones['mssql'] : time_zones['posix']
@@ -680,7 +809,7 @@ module ArelExtensions
680
809
  def test_date_comparator
681
810
  d = Date.new(2016, 5, 23)
682
811
  assert_equal 0, User.where(@created_at < d).count
683
- assert_equal 10, User.where(@created_at >= d).count
812
+ assert_equal 13, User.where(@created_at >= d).count
684
813
  end
685
814
 
686
815
  def test_date_duration
@@ -779,6 +908,7 @@ module ArelExtensions
779
908
  # TODO; cast types
780
909
  def test_cast_types
781
910
  assert_equal '5', t(@lucas, @age.cast(:string))
911
+ assert_equal 'a' * 50, t(@lucas, Arel.quoted('a' * 50).cast(:string))
782
912
  skip 'jdbc adapters does not work properly here (v52 works fine)' if RUBY_PLATFORM.match?(/java/i)
783
913
  if @env_db == 'mysql' || @env_db == 'postgresql' || @env_db == 'oracle' || @env_db == 'mssql'
784
914
  assert_equal 1, t(@laure, Arel.when(@duration.cast(:time).cast(:string).eq('12:42:21')).then(1).else(0)) unless @env_db == 'oracle' || @env_db == 'mssql'
@@ -863,7 +993,7 @@ module ArelExtensions
863
993
  def test_math_minus
864
994
  d = Date.new(2016, 5, 20)
865
995
  # Datediff
866
- assert_equal 10, User.where((@created_at - @created_at).eq(0)).count
996
+ assert_equal 13, User.where((@created_at - @created_at).eq(0)).count
867
997
  assert_equal 3, @laure.select((@created_at - d).as('res')).first.res.abs.to_i
868
998
  # Substraction
869
999
  assert_equal 0, User.where((@age - 10).eq(50)).count
@@ -894,9 +1024,9 @@ module ArelExtensions
894
1024
 
895
1025
  assert_equal 3, User.find_by_sql(@ut.project(@age).where(@age.gt(22)).union_all(@ut.project(@age).where(@age.lt(0))).to_sql).length
896
1026
  assert_equal 3, User.find_by_sql(@ut.project(@age).where(@age.eq(20)).union_all(@ut.project(@age).where(@age.eq(20))).union_all(@ut.project(@age).where(@age.eq(21))).to_sql).length
897
- assert_equal 3, User.select('*').from((@ut.project(@age).where(@age.gt(22)).union_all(@ut.project(@age).where(@age.lt(0)))).as('my_union')).length
898
- assert_equal 3, User.select('*').from((@ut.project(@age).where(@age.eq(20)).union_all(@ut.project(@age).where(@age.eq(23))).union_all(@ut.project(@age).where(@age.eq(21)))).as('my_union')).length
899
- assert_equal 3, User.select('*').from((@ut.project(@age).where(@age.eq(20)).union_all(@ut.project(@age).where(@age.eq(20))).union_all(@ut.project(@age).where(@age.eq(21)))).as('my_union')).length
1027
+ assert_equal 3, User.select('*').from(@ut.project(@age).where(@age.gt(22)).union_all(@ut.project(@age).where(@age.lt(0))).as('my_union')).length
1028
+ assert_equal 3, User.select('*').from(@ut.project(@age).where(@age.eq(20)).union_all(@ut.project(@age).where(@age.eq(23))).union_all(@ut.project(@age).where(@age.eq(21))).as('my_union')).length
1029
+ assert_equal 3, User.select('*').from(@ut.project(@age).where(@age.eq(20)).union_all(@ut.project(@age).where(@age.eq(20))).union_all(@ut.project(@age).where(@age.eq(21))).as('my_union')).length
900
1030
 
901
1031
  assert (@ut.project(@age) + @ut.project(@age)).as('toto').table_name # as on union should answer to table_name (TableAlias)
902
1032
  end
@@ -982,10 +1112,15 @@ module ArelExtensions
982
1112
  end
983
1113
 
984
1114
  def test_subquery_with_order
985
- skip if ['mssql'].include?(@env_db) && Arel::VERSION.to_i < 10
986
- assert_equal 10, User.where(name: User.select(:name).order(:name)).count
987
- assert_equal 10, User.where(@ut[:name].in(@ut.project(@ut[:name]).order(@ut[:name]))).count
988
- if !['mysql'].include?(@env_db) # MySql can't have limit in IN subquery
1115
+ skip if @env_db == 'mssql' && Arel::VERSION.to_i < 10
1116
+ assert_equal 12, User.where(name: User.select(:name).order(:name)).count
1117
+ assert_equal 12, User.where(@ut[:name].in(@ut.project(@ut[:name]).order(@ut[:name]))).count
1118
+
1119
+ if %w[mssql sqlite].include? @env_db
1120
+ # Sqlite and mssql are sensistive to the nil value in name, defined by @all_nil
1121
+ assert_equal 1, User.where(name: User.select(:name).order(:name).limit(2)).count
1122
+ elsif @env_db != 'mysql'
1123
+ # MySql can't have limit in IN subquery
989
1124
  assert_equal 2, User.where(name: User.select(:name).order(:name).limit(2)).count
990
1125
  # assert_equal 6, User.where(name: User.select(:name).order(:name).offset(2)).count
991
1126
  end
@@ -1097,15 +1232,16 @@ module ArelExtensions
1097
1232
  assert_equal 1, t(@arthur, @name.levenshtein_distance('Artehur'))
1098
1233
  end
1099
1234
 
1100
- def test_json
1101
- skip "Can't be tested on travis"
1235
+ def test_json_create
1102
1236
  # creation
1103
1237
  assert_equal 'Arthur', t(@arthur, Arel.json(@name))
1104
1238
  assert_equal %w[Arthur Arthur], parse_json(t(@arthur, Arel.json(@name, @name)))
1105
1239
  assert_equal ({'Arthur' => 'Arthur', 'Arthur2' => 'ArthurArthur'}), parse_json(t(@arthur, Arel.json({@name => @name, @name + '2' => @name + @name})))
1106
1240
  assert_equal ({'Arthur' => 'Arthur', 'Arthur2' => 1}), parse_json(t(@arthur, Arel.json({@name => @name, @name + '2' => 1})))
1107
1241
  assert_equal [{'age' => 21}, {'name' => 'Arthur', 'score' => 65.62}], parse_json(t(@arthur, Arel.json([{age: @age}, {name: @name, score: @score}])))
1242
+ end
1108
1243
 
1244
+ def test_json_aggregate
1109
1245
  # aggregate
1110
1246
  assert_equal ({'5' => 'Lucas', '15' => 'Sophie', '23' => 'Myung', '25' => 'Laure'}),
1111
1247
  parse_json(t(User.group(:score).where(@age.is_not_null).where(@score == 20.16), Arel.json({@age => @name}).group(false)))
@@ -1116,7 +1252,10 @@ module ArelExtensions
1116
1252
 
1117
1253
  # puts User.group(:score).where(@age.is_not_null).where(@score == 20.16).select(@score, Arel.json({@age => @name}).group(true,[@age])).to_sql
1118
1254
  # puts User.group(:score).where(@age.is_not_null).where(@score == 20.16).select(@score, Arel.json({@age => @name}).group(true,[@age])).to_a
1255
+ end
1119
1256
 
1257
+ def test_json_get
1258
+ skip 'Known failure: postgres ->> returns text, not parsed JSON' if @env_db == 'postgresql'
1120
1259
  skip 'Not Yet Implemented' if $sqlite || %w[oracle mssql].include?(@env_db)
1121
1260
  # get
1122
1261
  h1 = Arel.json({@name => @name + @name, @name + '2' => 1})
@@ -1125,17 +1264,60 @@ module ArelExtensions
1125
1264
  assert_equal ({'age' => 21}), parse_json(t(@arthur, h2.get(0)))
1126
1265
  assert_equal 21, parse_json(t(@arthur, h2.get(0).get('age')))
1127
1266
  assert_nil t(@arthur, h2.get('age'))
1267
+ end
1268
+
1269
+ def test_json_set
1270
+ skip 'Not Yet Implemented' if $sqlite || %w[oracle mssql].include?(@env_db)
1128
1271
  # set
1272
+ h1 = Arel.json({@name => @name + @name, @name + '2' => 1})
1129
1273
  assert_equal ({'Arthur' => %w[toto tata], 'Arthur2' => 1}), parse_json(t(@arthur, h1.set(@name, %w[toto tata])))
1130
1274
  assert_equal ({'Arthur' => 'ArthurArthur', 'Arthur2' => 1, 'Arthur3' => 2}), parse_json(t(@arthur, h1.set(@name + '3', 2)))
1131
1275
  assert_equal ({'Arthur' => 'ArthurArthur', 'Arthur2' => 1, 'Arthur3' => nil}), parse_json(t(@arthur, h1.set(@name + '3', nil)))
1132
1276
  assert_equal ({'Arthur' => 'ArthurArthur', 'Arthur2' => 1, 'Arthur3' => {'a' => 2}}), parse_json(t(@arthur, h1.set(@name + '3', {a: 2})))
1277
+ end
1278
+
1279
+ def test_json_merge
1280
+ skip 'Not Yet Implemented' if $sqlite || %w[oracle mssql].include?(@env_db)
1133
1281
  # merge
1282
+ h1 = Arel.json({@name => @name + @name, @name + '2' => 1})
1134
1283
  assert_equal ({'Arthur' => %w[toto tata], 'Arthur2' => 1, 'Arthur3' => 2}), parse_json(t(@arthur, h1.merge({@name => %w[toto tata]}, {@name + '3' => 2})))
1135
1284
  assert_equal ({'Arthur' => %w[toto tata], 'Arthur2' => 1, 'Arthur3' => 2}), parse_json(t(@arthur, h1.merge({@name => %w[toto tata], @name + '3' => 2})))
1136
1285
  assert_equal ({'Arthur' => 'ArthurArthur', 'Arthur2' => 1}), parse_json(t(@arthur, h1.merge({})))
1137
1286
  end
1138
1287
 
1288
+ def test_json_scalar_values
1289
+ assert_nil t(@arthur, Arel.json(nil))
1290
+
1291
+ assert_equal 42, parse_json(t(@arthur, Arel.json(42)))
1292
+ assert_equal({}, parse_json(t(@arthur, Arel.json({}))))
1293
+ assert_equal [], parse_json(t(@arthur, Arel.json([])))
1294
+
1295
+ assert_equal true, parse_json(t(@arthur, Arel.json(true)))
1296
+ assert_equal ({'Arthur' => false}), parse_json(t(@arthur, Arel.json({@name => false})))
1297
+
1298
+ assert_equal ({'Arthur' => true}), parse_json(t(@arthur, Arel.json({@name => @enabled})))
1299
+ assert_equal ({'Camille' => false}), parse_json(t(@camille, Arel.json({@name => @enabled})))
1300
+ assert_equal ({'Lucas' => nil}), parse_json(t(@lucas, Arel.json({@name => @enabled})))
1301
+ end
1302
+
1303
+ def test_json_special_chars
1304
+ assert_equal ({'Arthur' => 'say "hi"'}), parse_json(t(@arthur, Arel.json({@name => 'say "hi"'})))
1305
+ assert_equal ({'Arthur' => 'a\\b'}), parse_json(t(@arthur, Arel.json({@name => 'a\\b'})))
1306
+ assert_equal ({'Arthur' => "line1\nline2"}), parse_json(t(@arthur, Arel.json({@name => "line1\nline2"})))
1307
+ assert_equal ({'we"ird' => 'Arthur'}), parse_json(t(@arthur, Arel.json({'we"ird' => @name})))
1308
+ end
1309
+
1310
+ def test_json_date_values
1311
+ assert_equal ({'Arthur' => '2016-05-23'}), parse_json(t(@arthur, Arel.json({@name => @created_at})))
1312
+ res = parse_json(t(@laure, Arel.json({@name => @duration})))['Laure']
1313
+ assert_equal '12:42:21', res.sub(/\.\d+\z/, '') # MySQL (unlinke MariaDB) adds `.\d+` to a datetime column inside a json.
1314
+
1315
+ # the generic builder's `:datetime` branch is only used by sqlite and mssql.
1316
+ if $sqlite || @env_db == 'mssql'
1317
+ assert_equal ({'Lucas' => '2014-03-03T12:42:00'}), parse_json(t(@lucas, Arel.json({@name => @updated_at})))
1318
+ end
1319
+ end
1320
+
1139
1321
  def test_as_on_everything
1140
1322
  name = @arthur.select(@name.as('NaMe')).first.attributes
1141
1323
  assert_equal 'Arthur', name['NaMe'] || name['name'] # because of Oracle
@@ -5,7 +5,7 @@ module ArelExtensions
5
5
  module WithAr
6
6
  class InsertManagerTest < Minitest::Test
7
7
  def connect_db
8
- ActiveRecord::Base.configurations = YAML.load_file('test/database.yml')
8
+ ActiveRecord::Base.configurations = ConfigLoader.load('test/database.yml')
9
9
  if ENV['DB'] == 'oracle' && ((defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx') || (RUBY_PLATFORM == 'java')) # not supported
10
10
  @env_db = (RUBY_PLATFORM == 'java' ? 'jdbc-sqlite' : 'sqlite')
11
11
  skip "Platform not supported (DB: #{ENV['DB']}, RUBY_ENGINE: #{RUBY_ENGINE}, RUBY_PLATFORM: #{RUBY_PLATFORM})"
@@ -13,7 +13,7 @@ module ArelExtensions
13
13
  @env_db = ENV['DB']
14
14
  end
15
15
  ActiveRecord::Base.establish_connection(@env_db.try(:to_sym) || (RUBY_PLATFORM == 'java' ? :"jdbc-sqlite" : :sqlite))
16
- if ActiveRecord::VERSION::MAJOR >= 7
16
+ if ACTIVE_RECORD_VERSION >= V7_0
17
17
  ActiveRecord.default_timezone = :utc
18
18
  else
19
19
  ActiveRecord::Base.default_timezone = :utc
@@ -4,9 +4,9 @@ module ArelExtensions
4
4
  module WithAr
5
5
  describe 'the sqlite visitor' do
6
6
  before do
7
- ActiveRecord::Base.configurations = YAML.load_file('test/database.yml')
7
+ ActiveRecord::Base.configurations = ConfigLoader.load('test/database.yml')
8
8
  ActiveRecord::Base.establish_connection(ENV['DB'] || (RUBY_PLATFORM == 'java' ? :"jdbc-sqlite" : :sqlite))
9
- if ActiveRecord::VERSION::MAJOR >= 7
9
+ if ACTIVE_RECORD_VERSION >= V7_0
10
10
  ActiveRecord.default_timezone = :utc
11
11
  else
12
12
  ActiveRecord::Base.default_timezone = :utc
@@ -4,9 +4,9 @@ module ArelExtensions
4
4
  module WithAr
5
5
  describe 'the sqlite visitor can do maths' do
6
6
  before do
7
- ActiveRecord::Base.configurations = YAML.load_file('test/database.yml')
7
+ ActiveRecord::Base.configurations = ConfigLoader.load('test/database.yml')
8
8
  ActiveRecord::Base.establish_connection(ENV['DB'] || (RUBY_PLATFORM == 'java' ? :"jdbc-sqlite" : :sqlite))
9
- if ActiveRecord::VERSION::MAJOR >= 7
9
+ if ACTIVE_RECORD_VERSION >= V7_0
10
10
  ActiveRecord.default_timezone = :utc
11
11
  else
12
12
  ActiveRecord::Base.default_timezone = :utc
@@ -5,9 +5,9 @@ module ArelExtensions
5
5
  module WithAr
6
6
  describe 'the mysql visitor can do string operations' do
7
7
  before do
8
- ActiveRecord::Base.configurations = YAML.load_file('test/database.yml')
8
+ ActiveRecord::Base.configurations = ConfigLoader.load('test/database.yml')
9
9
  ActiveRecord::Base.establish_connection(ENV['DB'] || (RUBY_PLATFORM == 'java' ? :"jdbc-mysql" : :mysql))
10
- if ActiveRecord::VERSION::MAJOR >= 7
10
+ if ACTIVE_RECORD_VERSION >= V7_0
11
11
  ActiveRecord.default_timezone = :utc
12
12
  else
13
13
  ActiveRecord::Base.default_timezone = :utc
@@ -5,9 +5,9 @@ module ArelExtensions
5
5
  module WithAr
6
6
  describe 'the sqlite visitor can do string operations' do
7
7
  before do
8
- ActiveRecord::Base.configurations = YAML.load_file('test/database.yml')
8
+ ActiveRecord::Base.configurations = ConfigLoader.load('test/database.yml')
9
9
  ActiveRecord::Base.establish_connection(ENV['DB'] || (RUBY_PLATFORM == 'java' ? :"jdbc-sqlite" : :sqlite))
10
- if ActiveRecord::VERSION::MAJOR >= 7
10
+ if ACTIVE_RECORD_VERSION >= V7_0
11
11
  ActiveRecord.default_timezone = :utc
12
12
  else
13
13
  ActiveRecord::Base.default_timezone = :utc
data/version_v1.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ArelExtensions
2
- VERSION = '1.5.3'.freeze
4
+ VERSION = '1.7.0'
3
5
  end
data/version_v2.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ArelExtensions
2
- VERSION = '2.3.3'.freeze
4
+ VERSION = '2.5.0'
3
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arel_extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.3
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yann Azoury
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2025-02-11 00:00:00.000000000 Z
13
+ date: 2026-09-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -26,34 +26,6 @@ dependencies:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
28
  version: '6.0'
29
- - !ruby/object:Gem::Dependency
30
- name: minitest
31
- requirement: !ruby/object:Gem::Requirement
32
- requirements:
33
- - - "~>"
34
- - !ruby/object:Gem::Version
35
- version: '5.9'
36
- type: :development
37
- prerelease: false
38
- version_requirements: !ruby/object:Gem::Requirement
39
- requirements:
40
- - - "~>"
41
- - !ruby/object:Gem::Version
42
- version: '5.9'
43
- - !ruby/object:Gem::Dependency
44
- name: rake
45
- requirement: !ruby/object:Gem::Requirement
46
- requirements:
47
- - - "~>"
48
- - !ruby/object:Gem::Version
49
- version: 12.3.3
50
- type: :development
51
- prerelease: false
52
- version_requirements: !ruby/object:Gem::Requirement
53
- requirements:
54
- - - "~>"
55
- - !ruby/object:Gem::Version
56
- version: 12.3.3
57
29
  description: Adds new features to Arel
58
30
  email:
59
31
  - yann.azoury@faveod.com
@@ -75,6 +47,7 @@ files:
75
47
  - CONTRIBUTING.md
76
48
  - Gemfile
77
49
  - MIT-LICENSE.txt
50
+ - Makefile
78
51
  - NEWS.md
79
52
  - README.md
80
53
  - Rakefile
@@ -83,15 +56,12 @@ files:
83
56
  - appveyor.yml
84
57
  - arel_extensions.gemspec
85
58
  - bin/build
86
- - bin/compose
87
59
  - bin/publish
88
60
  - dev/arelx.dockerfile
89
61
  - dev/compose.yaml
90
62
  - dev/postgres.dockerfile
91
63
  - dev/rbenv
92
64
  - functions.html
93
- - gemfiles/rails3.gemfile
94
- - gemfiles/rails4_2.gemfile
95
65
  - gemfiles/rails5.gemfile
96
66
  - gemfiles/rails5_1_4.gemfile
97
67
  - gemfiles/rails5_2.gemfile
@@ -100,6 +70,8 @@ files:
100
70
  - gemfiles/rails7.gemfile
101
71
  - gemfiles/rails7_1.gemfile
102
72
  - gemfiles/rails7_2.gemfile
73
+ - gemfiles/rails8.gemfile
74
+ - gemfiles/rails8_1.gemfile
103
75
  - gemspecs/arel_extensions-v1.gemspec
104
76
  - gemspecs/arel_extensions-v2.gemspec
105
77
  - generate_gems.sh
@@ -114,6 +86,7 @@ files:
114
86
  - lib/arel_extensions/boolean_functions.rb
115
87
  - lib/arel_extensions/common_sql_functions.rb
116
88
  - lib/arel_extensions/comparators.rb
89
+ - lib/arel_extensions/constants.rb
117
90
  - lib/arel_extensions/date_duration.rb
118
91
  - lib/arel_extensions/helpers.rb
119
92
  - lib/arel_extensions/insert_manager.rb
@@ -123,10 +96,12 @@ files:
123
96
  - lib/arel_extensions/nodes/abs.rb
124
97
  - lib/arel_extensions/nodes/aggregate_function.rb
125
98
  - lib/arel_extensions/nodes/blank.rb
99
+ - lib/arel_extensions/nodes/byte_size.rb
126
100
  - lib/arel_extensions/nodes/case.rb
127
101
  - lib/arel_extensions/nodes/cast.rb
128
102
  - lib/arel_extensions/nodes/ceil.rb
129
103
  - lib/arel_extensions/nodes/change_case.rb
104
+ - lib/arel_extensions/nodes/char_length.rb
130
105
  - lib/arel_extensions/nodes/coalesce.rb
131
106
  - lib/arel_extensions/nodes/collate.rb
132
107
  - lib/arel_extensions/nodes/concat.rb
@@ -181,6 +156,7 @@ files:
181
156
  - lib/arel_extensions/visitors/to_sql.rb
182
157
  - lib/arel_extensions/warning.rb
183
158
  - test/arelx_test_helper.rb
159
+ - test/config_loader.rb
184
160
  - test/database.yml
185
161
  - test/real_db_test.rb
186
162
  - test/support/alter_system_user_password.sql
data/bin/compose DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- IFS=$'\n\t'
4
- set -euo pipefail
5
-
6
- docker compose -f dev/compose.yaml up --exit-code-from arelx