arel_extensions 1.3.6 → 1.3.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0076504922a3f08d3770d9bedca4b24f0b2d19b080abcfd925f3ab928a1d7802'
4
- data.tar.gz: '080a98b49a9a43f1ad319933c77befd0a8947b0d3f3fc503058cfc81b863d815'
3
+ metadata.gz: db1001f3712bc329382b77345bde00ff5c1a074a6ea757841b40fcaa230f9c08
4
+ data.tar.gz: de6cb9e86796ac091ae148f80888a4093ecbe0df9312ad5cbbcd16f7efc6b8f4
5
5
  SHA512:
6
- metadata.gz: d658b44c16c1af76c9c6e55c084186a663683269c974757e70333755b8986b2226e7561261776ce0fb41acf063469628f215627ea80991e977968d47851af831
7
- data.tar.gz: c9f0b4f123fac561f1aedcf93bc162d8bfa0f31740b539efe599f7f12725b995279b541dd7d501d3d8bdec636880ceade3eb1346f6c5fed90d67ea7124d6e9fd
6
+ metadata.gz: f225335f190b18b56c88e64cea0eef39771f6e937bebd57d814a6c4562c96236e2708f905eef4ec80801c9d6c475aebc717b99c265c2cbccbda46b30e0798786
7
+ data.tar.gz: e89e25e2336dee7d1f617f05ceb30df609dfdca52ff65e73e86fc2baaff91c1ae8d51e8d50510ba0981cc8bac9d62327164204957afbf18a1cf1c43728a3bf5d
@@ -13,19 +13,25 @@ jobs:
13
13
  fail-fast: false
14
14
  matrix:
15
15
  versions: [
16
- {ruby: 3.1, rails: 7, arelx: 2},
17
- {ruby: 3.1, rails: 6_1, arelx: 2},
18
- {ruby: 3.0, rails: 7, arelx: 2},
19
- {ruby: 3.0, rails: 6_1, arelx: 2},
20
- {ruby: 2.7, rails: 7, arelx: 2},
21
- {ruby: 2.7, rails: 6_1, arelx: 2},
22
- {ruby: 2.7, rails: 6, arelx: 2},
23
- {ruby: 2.7, rails: 5_2, arelx: 1},
24
- {ruby: 2.7, rails: 4_2, arelx: 1},
25
- {ruby: 2.5, rails: 6_1, arelx: 2},
26
- {ruby: 2.5, rails: 6, arelx: 2},
27
- {ruby: 2.5, rails: 5_2, arelx: 1},
28
- {ruby: 2.5, rails: 4_2, arelx: 1},
16
+ {ruby: '3.2', rails: 7_1, arelx: 2},
17
+ {ruby: '3.2', rails: 7, arelx: 2},
18
+ {ruby: '3.2', rails: 6_1, arelx: 2},
19
+ {ruby: '3.1', rails: 7_1, arelx: 2},
20
+ {ruby: '3.1', rails: 7, arelx: 2},
21
+ {ruby: '3.1', rails: 6_1, arelx: 2},
22
+ {ruby: '3.0', rails: 7_1, arelx: 2},
23
+ {ruby: '3.0', rails: 7, arelx: 2},
24
+ {ruby: '3.0', rails: 6_1, arelx: 2},
25
+ {ruby: '2.7', rails: 7_1, arelx: 2},
26
+ {ruby: '2.7', rails: 7, arelx: 2},
27
+ {ruby: '2.7', rails: 6_1, arelx: 2},
28
+ {ruby: '2.7', rails: 6, arelx: 2},
29
+ {ruby: '2.7', rails: 5_2, arelx: 1},
30
+ {ruby: '2.7', rails: 4_2, arelx: 1},
31
+ {ruby: '2.5', rails: 6_1, arelx: 2},
32
+ {ruby: '2.5', rails: 6, arelx: 2},
33
+ {ruby: '2.5', rails: 5_2, arelx: 1},
34
+ {ruby: '2.5', rails: 4_2, arelx: 1},
29
35
  {ruby: jruby-9.2, rails: 6_1, arelx: 2},
30
36
  {ruby: jruby-9.2, rails: 6, arelx: 2},
31
37
  {ruby: jruby-9.2, rails: 5_2, arelx: 1},
@@ -44,6 +50,7 @@ jobs:
44
50
  sudo apt-get update -q
45
51
  sudo apt-get install -y freetds-dev
46
52
  - name: Update system-wide gems
53
+ if: ${{ !contains(fromJson('["2.5", "jruby-9.2"]'), matrix.versions.ruby) }}
47
54
  run: gem update --system --no-document
48
55
  - name: Setup Gemfile for arelx 2.x
49
56
  if: ${{ matrix.versions.arelx == 2 }}
@@ -65,19 +72,25 @@ jobs:
65
72
  fail-fast: false
66
73
  matrix:
67
74
  versions: [
68
- {ruby: 3.1, rails: 7, arelx: 2},
69
- {ruby: 3.1, rails: 6_1, arelx: 2},
70
- {ruby: 3.0, rails: 7, arelx: 2},
71
- {ruby: 3.0, rails: 6_1, arelx: 2},
72
- {ruby: 2.7, rails: 7, arelx: 2},
73
- {ruby: 2.7, rails: 6_1, arelx: 2},
74
- {ruby: 2.7, rails: 6, arelx: 2},
75
- {ruby: 2.7, rails: 5_2, arelx: 1},
76
- {ruby: 2.7, rails: 4_2, arelx: 1},
77
- {ruby: 2.5, rails: 6_1, arelx: 2},
78
- {ruby: 2.5, rails: 6, arelx: 2},
79
- {ruby: 2.5, rails: 5_2, arelx: 1},
80
- {ruby: 2.5, rails: 4_2, arelx: 1},
75
+ {ruby: '3.2', rails: 7_1, arelx: 2},
76
+ {ruby: '3.2', rails: 7, arelx: 2},
77
+ {ruby: '3.2', rails: 6_1, arelx: 2},
78
+ {ruby: '3.1', rails: 7_1, arelx: 2},
79
+ {ruby: '3.1', rails: 7, arelx: 2},
80
+ {ruby: '3.1', rails: 6_1, arelx: 2},
81
+ {ruby: '3.0', rails: 7_1, arelx: 2},
82
+ {ruby: '3.0', rails: 7, arelx: 2},
83
+ {ruby: '3.0', rails: 6_1, arelx: 2},
84
+ {ruby: '2.7', rails: 7_1, arelx: 2},
85
+ {ruby: '2.7', rails: 7, arelx: 2},
86
+ {ruby: '2.7', rails: 6_1, arelx: 2},
87
+ {ruby: '2.7', rails: 6, arelx: 2},
88
+ {ruby: '2.7', rails: 5_2, arelx: 1},
89
+ {ruby: '2.7', rails: 4_2, arelx: 1},
90
+ {ruby: '2.5', rails: 6_1, arelx: 2},
91
+ {ruby: '2.5', rails: 6, arelx: 2},
92
+ {ruby: '2.5', rails: 5_2, arelx: 1},
93
+ {ruby: '2.5', rails: 4_2, arelx: 1},
81
94
  {ruby: jruby-9.2, rails: 6_1, arelx: 2},
82
95
  {ruby: jruby-9.2, rails: 6, arelx: 2},
83
96
  {ruby: jruby-9.2, rails: 5_2, arelx: 1},
@@ -97,6 +110,7 @@ jobs:
97
110
  sudo apt-get update -q
98
111
  sudo apt-get install -y freetds-dev
99
112
  - name: Update system-wide gems
113
+ if: ${{ !contains(fromJson('["2.5", "jruby-9.2"]'), matrix.versions.ruby) }}
100
114
  run: gem update --system --no-document
101
115
  - name: Setup Gemfile
102
116
  if: ${{ matrix.versions.arelx == 2 }}
@@ -118,19 +132,25 @@ jobs:
118
132
  fail-fast: false
119
133
  matrix:
120
134
  versions: [
121
- {ruby: 3.1, rails: 7, arelx: 2},
122
- {ruby: 3.1, rails: 6_1, arelx: 2},
123
- {ruby: 3.0, rails: 7, arelx: 2},
124
- {ruby: 3.0, rails: 6_1, arelx: 2},
125
- {ruby: 2.7, rails: 7, arelx: 2},
126
- {ruby: 2.7, rails: 6_1, arelx: 2},
127
- {ruby: 2.7, rails: 6, arelx: 2},
128
- {ruby: 2.7, rails: 5_2, arelx: 1},
129
- {ruby: 2.7, rails: 4_2, arelx: 1},
130
- {ruby: 2.5, rails: 6_1, arelx: 2},
131
- {ruby: 2.5, rails: 6, arelx: 2},
132
- {ruby: 2.5, rails: 5_2, arelx: 1},
133
- {ruby: 2.5, rails: 4_2, arelx: 1},
135
+ {ruby: '3.2', rails: 7_1, arelx: 2},
136
+ {ruby: '3.2', rails: 7, arelx: 2},
137
+ {ruby: '3.2', rails: 6_1, arelx: 2},
138
+ {ruby: '3.1', rails: 7_1, arelx: 2},
139
+ {ruby: '3.1', rails: 7, arelx: 2},
140
+ {ruby: '3.1', rails: 6_1, arelx: 2},
141
+ {ruby: '3.0', rails: 7_1, arelx: 2},
142
+ {ruby: '3.0', rails: 7, arelx: 2},
143
+ {ruby: '3.0', rails: 6_1, arelx: 2},
144
+ {ruby: '2.7', rails: 7_1, arelx: 2},
145
+ {ruby: '2.7', rails: 7, arelx: 2},
146
+ {ruby: '2.7', rails: 6_1, arelx: 2},
147
+ {ruby: '2.7', rails: 6, arelx: 2},
148
+ {ruby: '2.7', rails: 5_2, arelx: 1},
149
+ {ruby: '2.7', rails: 4_2, arelx: 1},
150
+ {ruby: '2.5', rails: 6_1, arelx: 2},
151
+ {ruby: '2.5', rails: 6, arelx: 2},
152
+ {ruby: '2.5', rails: 5_2, arelx: 1},
153
+ {ruby: '2.5', rails: 4_2, arelx: 1},
134
154
  {ruby: jruby-9.2, rails: 6_1, arelx: 2},
135
155
  {ruby: jruby-9.2, rails: 6, arelx: 2},
136
156
  {ruby: jruby-9.2, rails: 5_2, arelx: 1},
@@ -172,6 +192,7 @@ jobs:
172
192
  sudo apt-get update -q
173
193
  sudo apt-get install -y freetds-dev
174
194
  - name: Update system-wide gems
195
+ if: ${{ !contains(fromJson('["2.5", "jruby-9.2"]'), matrix.versions.ruby) }}
175
196
  run: gem update --system --no-document
176
197
  - name: Setup Gemfile
177
198
  if: ${{ matrix.versions.arelx == 2 }}
@@ -196,19 +217,25 @@ jobs:
196
217
  fail-fast: false
197
218
  matrix:
198
219
  versions: [
199
- {ruby: 3.1, rails: 7, arelx: 2},
200
- {ruby: 3.1, rails: 6_1, arelx: 2},
201
- {ruby: 3.0, rails: 7, arelx: 2},
202
- {ruby: 3.0, rails: 6_1, arelx: 2},
203
- {ruby: 2.7, rails: 7, arelx: 2},
204
- {ruby: 2.7, rails: 6_1, arelx: 2},
205
- {ruby: 2.7, rails: 6, arelx: 2},
206
- {ruby: 2.7, rails: 5_2, arelx: 1},
207
- {ruby: 2.7, rails: 4_2, arelx: 1},
208
- {ruby: 2.5, rails: 6_1, arelx: 2},
209
- {ruby: 2.5, rails: 6, arelx: 2},
210
- {ruby: 2.5, rails: 5_2, arelx: 1},
211
- {ruby: 2.5, rails: 4_2, arelx: 1},
220
+ {ruby: '3.2', rails: 7_1, arelx: 2},
221
+ {ruby: '3.2', rails: 7, arelx: 2},
222
+ {ruby: '3.2', rails: 6_1, arelx: 2},
223
+ {ruby: '3.1', rails: 7_1, arelx: 2},
224
+ {ruby: '3.1', rails: 7, arelx: 2},
225
+ {ruby: '3.1', rails: 6_1, arelx: 2},
226
+ {ruby: '3.0', rails: 7_1, arelx: 2},
227
+ {ruby: '3.0', rails: 7, arelx: 2},
228
+ {ruby: '3.0', rails: 6_1, arelx: 2},
229
+ {ruby: '2.7', rails: 7_1, arelx: 2},
230
+ {ruby: '2.7', rails: 7, arelx: 2},
231
+ {ruby: '2.7', rails: 6_1, arelx: 2},
232
+ {ruby: '2.7', rails: 6, arelx: 2},
233
+ {ruby: '2.7', rails: 5_2, arelx: 1},
234
+ {ruby: '2.7', rails: 4_2, arelx: 1},
235
+ {ruby: '2.5', rails: 6_1, arelx: 2},
236
+ {ruby: '2.5', rails: 6, arelx: 2},
237
+ {ruby: '2.5', rails: 5_2, arelx: 1},
238
+ {ruby: '2.5', rails: 4_2, arelx: 1},
212
239
  {ruby: jruby-9.2, rails: 6_1, arelx: 2},
213
240
  {ruby: jruby-9.2, rails: 6, arelx: 2},
214
241
  {ruby: jruby-9.2, rails: 5_2, arelx: 1},
@@ -242,6 +269,7 @@ jobs:
242
269
  sudo apt-get update -q
243
270
  sudo apt-get install -y freetds-dev
244
271
  - name: Update system-wide gems
272
+ if: ${{ !contains(fromJson('["2.5", "jruby-9.2"]'), matrix.versions.ruby) }}
245
273
  run: gem update --system --no-document
246
274
  - name: Setup Gemfile
247
275
  if: ${{ matrix.versions.arelx == 2 }}
@@ -269,19 +297,21 @@ jobs:
269
297
  fail-fast: false
270
298
  matrix:
271
299
  versions: [
272
- # {ruby: 3.1, rails: 7, arelx: 2},
273
- {ruby: 3.1, rails: 6_1, arelx: 2},
274
- # {ruby: 3.0, rails: 7, arelx: 2},
275
- {ruby: 3.0, rails: 6_1, arelx: 2},
276
- # {ruby: 2.7, rails: 7, arelx: 2},
277
- {ruby: 2.7, rails: 6_1, arelx: 2},
278
- {ruby: 2.7, rails: 6, arelx: 2},
279
- {ruby: 2.7, rails: 5_2, arelx: 1},
280
- {ruby: 2.7, rails: 4_2, arelx: 1},
281
- {ruby: 2.5, rails: 6_1, arelx: 2},
282
- {ruby: 2.5, rails: 6, arelx: 2},
283
- {ruby: 2.5, rails: 5_2, arelx: 1},
284
- {ruby: 2.5, rails: 4_2, arelx: 1},
300
+ # {ruby: '3.2', rails: 7, arelx: 2},
301
+ {ruby: '3.2', rails: 6_1, arelx: 2},
302
+ # {ruby: '3.1', rails: 7, arelx: 2},
303
+ {ruby: '3.1', rails: 6_1, arelx: 2},
304
+ # {ruby: '3.0', rails: 7, arelx: 2},
305
+ {ruby: '3.0', rails: 6_1, arelx: 2},
306
+ # {ruby: '2.7', rails: 7, arelx: 2},
307
+ {ruby: '2.7', rails: 6_1, arelx: 2},
308
+ {ruby: '2.7', rails: 6, arelx: 2},
309
+ {ruby: '2.7', rails: 5_2, arelx: 1},
310
+ {ruby: '2.7', rails: 4_2, arelx: 1},
311
+ {ruby: '2.5', rails: 6_1, arelx: 2},
312
+ {ruby: '2.5', rails: 6, arelx: 2},
313
+ {ruby: '2.5', rails: 5_2, arelx: 1},
314
+ {ruby: '2.5', rails: 4_2, arelx: 1},
285
315
  # {ruby: jruby-9.2, rails: 6_1, arelx: 2},
286
316
  # {ruby: jruby-9.2, rails: 6, arelx: 2},
287
317
  {ruby: jruby-9.2, rails: 5_2, arelx: 1},
@@ -308,6 +338,7 @@ jobs:
308
338
  install: sqlengine, sqlclient, sqlpackage, localdb
309
339
  sa-password: Password12!
310
340
  - name: Update system-wide gems
341
+ if: ${{ !contains(fromJson('["2.5", "jruby-9.2"]'), matrix.versions.ruby) }}
311
342
  run: gem update --system --no-document
312
343
  - name: Setup Gemfile
313
344
  if: ${{ matrix.versions.arelx == 2 }}
data/NEWS.md CHANGED
@@ -1,13 +1,30 @@
1
- # Release
1
+ # News
2
2
 
3
- ## Bug Fixes
3
+ ## Current Release
4
+
5
+ ### New Features
6
+
7
+ - `o.format_date` as an alternative to `o.format`
8
+ The actual behavior of `format` is inconsistent across DB vendors: in mysql we
9
+ can format dates and numbers with it, and in the other ones we only format
10
+ dates.
11
+
12
+ We're planning on normalizing this behavior. We want `format` to "cleverly"
13
+ format dates or numbers, and `format_date` / `format_number` to strictly
14
+ format dates / numbers.
15
+
16
+ The introduction of `format_date` is the first step in this direction.
17
+
18
+ ## Release v2.1.6/v1.3.6
19
+
20
+ ### Bug Fixes
4
21
 
5
22
  - This used to fail.
6
23
  ```
7
24
  Arel.when(a).then(b).format('%Y-%m-%d')
8
25
  ```
9
26
 
10
- ## New Features
27
+ ### New Features
11
28
 
12
29
  - `o.present`, a synonym for `o.not_blank`
13
30
  - `o.coalesce_blank(a, b, c)`
data/README.md CHANGED
@@ -36,7 +36,8 @@ It will add common SQL features in your DB to align ti with current routines. Te
36
36
 
37
37
  ## Examples
38
38
 
39
- t is an Arel::Table for table my_table
39
+ In the following examples
40
+ `t` is an `Arel::Table` for table `my_table` (i.e., `t = Arel::Table.new('my_table')`).
40
41
 
41
42
  ## Comparators
42
43
 
@@ -50,7 +51,7 @@ t is an Arel::Table for table my_table
50
51
  # => my_table.nb > 42
51
52
  ```
52
53
 
53
- Other operators : <, >=, <=, =~
54
+ Other operators: <, >=, <=, =~
54
55
 
55
56
 
56
57
  ## Maths
@@ -73,7 +74,7 @@ With Arel Extensions:
73
74
  # => SUM(my_table.nb) + 42
74
75
  ```
75
76
 
76
- Other functions : ABS, RAND, ROUND, FLOOR, CEIL, FORMAT
77
+ Other functions: ABS, RAND, ROUND, FLOOR, CEIL, FORMAT
77
78
 
78
79
  For Example:
79
80
  ```ruby
@@ -94,14 +95,29 @@ t[:price].format_number("%07.2f €","fr_FR")
94
95
  # => TRIM(TRIM(TRIM(COALESCE(my_table.name, '')), '\t'), '\n') = ''
95
96
 
96
97
  (t[:name] =~ /\A[a-d_]+/).to_sql
97
- # => my_table.name REGEXP '\^[a-d_]+'
98
+ # => my_table.name REGEXP '^[a-d_]+'
98
99
  ```
99
100
 
100
- Other functions : SOUNDEX, LENGTH, REPLACE, LOCATE, SUBSTRING, TRIM
101
+ The `replace` function supports string and regex patterns.
102
+ For instance
103
+
104
+ ```ruby
105
+ t[:email].replace('@', ' at ').replace('.', ' dot ').to_sql
106
+ # => REPLACE(REPLACE(`my_table`.`email`, '@', ' at '), '.', ' dot ')
107
+ ```
108
+
109
+ Captures are supported when using regex patterns. The replace string may then reference the capture groups using `\1`, `\2`, etc. For instance
110
+
111
+ ```ruby
112
+ t[:email].replace(/^(.*)@(.*)$/, 'user: \1, host: \2').to_sql
113
+ # => REGEXP_REPLACE(`my_table`.`email`, '(?-mix:^(.*)@(.*)$)', 'user: \\1, host: \\2')
114
+ ```
115
+
116
+ Other functions: SOUNDEX, LENGTH, REPLACE, LOCATE, SUBSTRING, TRIM
101
117
 
102
118
  ### String Array operations
103
119
 
104
- ```t[:list]``` is a classical varchar containing a comma separated list ("1,2,3,4")
120
+ `t[:list]` is a classical varchar containing a comma separated list (`"1,2,3,4"`).
105
121
 
106
122
  ```ruby
107
123
  (t[:list] & 3).to_sql
@@ -228,15 +244,15 @@ t[:id].cast('char').to_sql
228
244
  ## Stored Procedures and User-defined functions
229
245
 
230
246
  To optimize queries, some classical functions are defined in databases missing any alternative native functions.
231
- Examples :
232
- - FIND_IN_SET
247
+ Examples:
248
+ - `FIND_IN_SET`
233
249
 
234
250
  ## BULK INSERT / UPSERT
235
251
 
236
252
  Arel Extensions improves InsertManager by adding bulk_insert method, which allows to insert multiple rows in one insert.
237
253
 
238
254
 
239
- ```
255
+ ```ruby
240
256
  @cols = ['id', 'name', 'comments', 'created_at']
241
257
  @data = [
242
258
  [23, 'name1', "sdfdsfdsfsdf", '2016-01-01'],
@@ -437,6 +453,15 @@ User.connection.execute(insert_manager.to_sql)
437
453
  <td class="ok">✔</td>
438
454
  <td class="ok">✔</td>
439
455
  </tr>
456
+ <tr>
457
+ <td class="tg-yw4l">REPLACE<br>column.replace(/re/,"X")</td>
458
+ <td class="ok">✔</td>
459
+ <td class="ok">✔</td>
460
+ <td class="ok">✔</td>
461
+ <td class="ok">✔</td>
462
+ <td class="ok">✔</td>
463
+ <td class="ok">✔</td>
464
+ </tr>
440
465
  <tr>
441
466
  <td class="tg-yw4l">SOUNDEX<br>column.soundex</td>
442
467
  <td class="ok">✔</td>
@@ -746,3 +771,15 @@ bundle exec rake test:to_sql
746
771
 
747
772
  Refer to the [Version Compatibility](#version-compatibility) section to correctly
748
773
  set your gemfile.
774
+
775
+ ### MariaDB and Postgres
776
+
777
+ We provide a `docker compose` to set up some databases for testing:
778
+
779
+ ```bash
780
+ docker-compose -f dev/compose.yaml up
781
+ ```
782
+
783
+ ⚠️ Note: localization tests will fail for postgresql because the image is made
784
+ for `en_US.UTF`. We should fix it later, but for the time being the CI works
785
+ correctly for these tests, so you can safely ignore them for the time being.
data/Rakefile CHANGED
@@ -48,3 +48,10 @@ end
48
48
  # Make sure the adapter test evaluates the env setting task
49
49
  task "test_#{adapter}" => ["#{adapter}:env", "test:#{adapter}"]
50
50
  end
51
+
52
+ # Useful shorthands.
53
+ namespace :test do
54
+ task :sql => :to_sql
55
+ task :pg => :postgresql
56
+ task :postgres => :postgresql
57
+ end
data/dev/compose.yaml ADDED
@@ -0,0 +1,29 @@
1
+ name: arelx-dbs
2
+ services:
3
+ mariadb:
4
+ image: mariadb:11.0
5
+ container_name: mariadb
6
+ environment:
7
+ MARIADB_DATABASE: arelx_test
8
+ MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
9
+ ports:
10
+ - "3306:3306"
11
+ healthcheck:
12
+ test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
13
+ interval: 10s
14
+ timeout: 5s
15
+ retries: 3
16
+ postgres:
17
+ image: postgres:15
18
+ container_name: postgres
19
+ environment:
20
+ POSTGRES_USER: postgres
21
+ POSTGRES_PASSWORD: secret
22
+ POSTGRES_DB: arelx_test
23
+ ports:
24
+ - "5432:5432"
25
+ healthcheck:
26
+ test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
27
+ interval: 10s
28
+ timeout: 5s
29
+ retries: 3
@@ -9,7 +9,7 @@ group :development, :test do
9
9
  gem 'activerecord', '~> 6.1.0'
10
10
 
11
11
  gem 'sqlite3', '~> 1.4', platforms: [:mri]
12
- gem 'mysql2', '0.5.2', platforms: [:mri]
12
+ gem 'mysql2', '~>0.5', platforms: [:mri]
13
13
  gem 'pg', '~> 1.1', platforms: [:mri]
14
14
 
15
15
  gem 'tiny_tds', platforms: %i[mri mingw x64_mingw mswin]
@@ -9,7 +9,7 @@ group :development, :test do
9
9
  gem 'activerecord', '~> 7.0.1'
10
10
 
11
11
  gem 'sqlite3', '~> 1.4', platforms: [:mri]
12
- gem 'mysql2', '0.5.2', platforms: [:mri]
12
+ gem 'mysql2', '~>0.5', platforms: [:mri]
13
13
  gem 'pg', '~> 1.1', platforms: [:mri]
14
14
 
15
15
  gem 'tiny_tds', platforms: %i[mri mingw x64_mingw mswin]
@@ -0,0 +1,22 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rails', '~> 7.1'
4
+
5
+ group :development, :test do
6
+ gem 'activesupport', '~> 7.1'
7
+ gem 'activemodel', '~> 7.1'
8
+ gem 'activerecord', '~> 7.1'
9
+
10
+ gem 'sqlite3', '~> 1.6', platforms: [:mri]
11
+ gem 'mysql2', '~>0.5', platforms: [:mri]
12
+ gem 'pg', '~> 1.5', platforms: [:mri]
13
+
14
+ gem 'tiny_tds', platforms: %i[mri mingw x64_mingw mswin]
15
+ gem 'activerecord-sqlserver-adapter', '~> 7.1.0.beta1', platforms: %i[mri mingw x64_mingw mswin]
16
+ gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw]
17
+
18
+ gem 'ruby-oci8', platforms: %i[mri mswin mingw] if ENV.has_key? 'ORACLE_HOME'
19
+ gem 'activerecord-oracle_enhanced-adapter', '~> 7.0.0' if ENV.has_key? 'ORACLE_HOME'
20
+ end
21
+
22
+ gemspec path: Dir.pwd
@@ -1,4 +1,5 @@
1
1
  require 'arel_extensions/nodes/format'
2
+ require 'arel_extensions/nodes/formatted_date'
2
3
  require 'arel_extensions/nodes/duration'
3
4
  require 'arel_extensions/nodes/wday'
4
5
 
@@ -43,5 +44,9 @@ module ArelExtensions
43
44
  def format(tpl, time_zone = nil)
44
45
  ArelExtensions::Nodes::Format.new [self, tpl, time_zone]
45
46
  end
47
+
48
+ def format_date(tpl, time_zone = nil)
49
+ ArelExtensions::Nodes::FormattedDate.new [self, tpl, time_zone]
50
+ end
46
51
  end
47
52
  end
@@ -20,7 +20,8 @@ module ArelExtensions
20
20
  rescue NoMethodError
21
21
  nil
22
22
  rescue => e
23
- warn("Warning: Unexpected exception caught while fetching column name for #{table_name}.#{column_name} in `column_of_via_arel_table`\n#{e.class}: #{e}")
23
+ warn("Warning: Unexpected exception caught while fetching column name for #{table_name}.#{column_name} in `column_of_via_arel_table`\n#{e.class}")
24
+ warn(e.backtrace)
24
25
  nil
25
26
  end
26
27
 
@@ -33,10 +34,14 @@ module ArelExtensions
33
34
  column_of_via_arel_table(table_name, column_name)
34
35
  else
35
36
  if pool.respond_to?(:pool_config)
36
- pool.pool_config.schema_cache.columns_hash(table_name)[column_name]
37
- elsif pool.respond_to?(:schema_cache)
37
+ if pool.pool_config.respond_to?(:schema_reflection) # activerecord >= 7.1
38
+ pool.pool_config.schema_reflection.columns_hash(ActiveRecord::Base.connection, table_name)[column_name]
39
+ else # activerecord < 7.1
40
+ pool.pool_config.schema_cache.columns_hash(table_name)[column_name]
41
+ end
42
+ elsif pool.respond_to?(:schema_cache) # activerecord < 6.1
38
43
  pool.schema_cache.columns_hash(table_name)[column_name]
39
- else
44
+ else # activerecord < 5.0
40
45
  column_of_via_arel_table(table_name, column_name)
41
46
  end
42
47
  end
@@ -45,7 +50,8 @@ module ArelExtensions
45
50
  rescue ActiveRecord::StatementInvalid
46
51
  nil
47
52
  rescue => e
48
- warn("Warning: Unexpected exception caught while fetching column name for #{table_name}.#{column_name} in `column_of`\n#{e.class}: #{e}")
53
+ warn("Warning: Unexpected exception caught while fetching column name for #{table_name}.#{column_name} in `column_of`\n#{e.class}")
54
+ warn(e.backtrace)
49
55
  nil
50
56
  end
51
57
  end
@@ -16,7 +16,7 @@ module ArelExtensions::Nodes
16
16
  if b.is_a?(Arel::Nodes::Quoted) && b.expr == ''
17
17
  res
18
18
  elsif res.last && res.last.is_a?(Arel::Nodes::Quoted) && b.is_a?(Arel::Nodes::Quoted)
19
- res[-1] = Arel.quoted(res.last.expr + b.expr)
19
+ res[-1] = Arel.quoted(res.last.expr.to_s + b.expr.to_s)
20
20
  else
21
21
  res << b
22
22
  end
@@ -0,0 +1,42 @@
1
+ require 'strscan'
2
+
3
+ module ArelExtensions
4
+ module Nodes
5
+ class FormattedDate < Function
6
+ RETURN_TYPE = :string
7
+
8
+ attr_accessor :col_type, :iso_format, :time_zone
9
+
10
+ def initialize expr
11
+ col = expr[0]
12
+ @iso_format = convert_format(expr[1])
13
+ @time_zone = expr[2]
14
+ @col_type = type_of_attribute(col)
15
+ super [col, convert_to_string_node(@iso_format)]
16
+ end
17
+
18
+ private
19
+
20
+ # Address portability issues with some of the formats.
21
+ def convert_format(fmt)
22
+ s = StringScanner.new fmt
23
+ res = StringIO.new
24
+ while !s.eos?
25
+ res <<
26
+ case
27
+ when s.scan(/%D/) then '%m/%d/%y'
28
+ when s.scan(/%F/) then '%Y-%m-%d'
29
+ when s.scan(/%R/) then '%H:%M'
30
+ when s.scan(/%r/) then '%I:%M:%S %p'
31
+ when s.scan(/%T/) then '%H:%M:%S'
32
+ when s.scan(/%v/) then '%e-%b-%Y'
33
+
34
+ when s.scan(/[^%]+/) then s.matched
35
+ when s.scan(/./) then s.matched
36
+ end
37
+ end
38
+ res.string
39
+ end
40
+ end
41
+ end
42
+ end
@@ -18,8 +18,12 @@ module ArelExtensions
18
18
  JsonSet.new(self, key, value)
19
19
  end
20
20
 
21
- def group as_array = true, orders = nil
22
- JsonGroup.new(self, as_array, orders)
21
+ def group as_array = true, orders = nil, distinct: false
22
+ if distinct
23
+ JsonGroup.new(Arel::Nodes::NamedFunction.new('DISTINCT', [self]), as_array, orders)
24
+ else
25
+ JsonGroup.new(self, as_array, orders)
26
+ end
23
27
  end
24
28
 
25
29
  def hash
@@ -1,3 +1,3 @@
1
1
  module ArelExtensions
2
- VERSION = '1.3.6'.freeze
2
+ VERSION = '1.3.8'.freeze
3
3
  end
@@ -355,6 +355,10 @@ module ArelExtensions
355
355
  end
356
356
 
357
357
  def visit_ArelExtensions_Nodes_Format o, collector
358
+ visit_ArelExtensions_Nodes_FormattedDate o, collector
359
+ end
360
+
361
+ def visit_ArelExtensions_Nodes_FormattedDate o, collector
358
362
  f = ArelExtensions::Visitors::strftime_to_format(o.iso_format, LOADED_VISITOR::DATE_FORMAT_DIRECTIVES)
359
363
  if fmt = LOADED_VISITOR::DATE_CONVERT_FORMATS[f]
360
364
  collector << "CONVERT(VARCHAR(#{f.length})"
@@ -258,33 +258,16 @@ module ArelExtensions
258
258
  # In this case, `o.col_type` is `nil` but we have a legitimate type in
259
259
  # the expression to be formatted. The following is a best effort to
260
260
  # infer the proper type.
261
+ first = o.expressions[0]
261
262
  type =
262
- o.col_type.nil? && !o.expressions[0].return_type.nil? \
263
- ? o.expressions[0].return_type \
263
+ o.col_type.nil? \
264
+ && (first.respond_to?(:return_type) && !first&.return_type.nil?) \
265
+ ? first&.return_type \
264
266
  : o.col_type
265
267
 
266
268
  case type
267
269
  when :date, :datetime, :time
268
- fmt = ArelExtensions::Visitors::strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
269
- collector << 'DATE_FORMAT('
270
- collector << 'CONVERT_TZ(' if o.time_zone
271
- collector = visit o.left, collector
272
- case o.time_zone
273
- when Hash
274
- src_tz, dst_tz = o.time_zone.first
275
- collector << COMMA
276
- collector = visit Arel.quoted(src_tz), collector
277
- collector << COMMA
278
- collector = visit Arel.quoted(dst_tz), collector
279
- collector << ')'
280
- when String
281
- collector << COMMA << "'UTC'" << COMMA
282
- collector = visit Arel.quoted(o.time_zone), collector
283
- collector << ')'
284
- end
285
- collector << COMMA
286
- collector = visit Arel.quoted(fmt), collector
287
- collector << ')'
270
+ visit_ArelExtensions_Nodes_FormattedDate o, collector
288
271
  when :integer, :float, :decimal
289
272
  collector << 'FORMAT('
290
273
  collector = visit o.left, collector
@@ -299,6 +282,29 @@ module ArelExtensions
299
282
  collector
300
283
  end
301
284
 
285
+ def visit_ArelExtensions_Nodes_FormattedDate o, collector
286
+ fmt = ArelExtensions::Visitors::strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
287
+ collector << 'DATE_FORMAT('
288
+ collector << 'CONVERT_TZ(' if o.time_zone
289
+ collector = visit o.left, collector
290
+ case o.time_zone
291
+ when Hash
292
+ src_tz, dst_tz = o.time_zone.first
293
+ collector << COMMA
294
+ collector = visit Arel.quoted(src_tz), collector
295
+ collector << COMMA
296
+ collector = visit Arel.quoted(dst_tz), collector
297
+ collector << ')'
298
+ when String
299
+ collector << COMMA << "'UTC'" << COMMA
300
+ collector = visit Arel.quoted(o.time_zone), collector
301
+ collector << ')'
302
+ end
303
+ collector << COMMA
304
+ collector = visit Arel.quoted(fmt), collector
305
+ collector << ')'
306
+ end
307
+
302
308
  def visit_ArelExtensions_Nodes_DateDiff o, collector
303
309
  case o.right_node_type
304
310
  when :ruby_date, :ruby_time, :date, :datetime, :time
@@ -451,6 +451,10 @@ module ArelExtensions
451
451
  end
452
452
 
453
453
  def visit_ArelExtensions_Nodes_Format o, collector
454
+ visit_ArelExtensions_Nodes_FormattedDate o, collector
455
+ end
456
+
457
+ def visit_ArelExtensions_Nodes_FormattedDate o, collector
454
458
  fmt = ArelExtensions::Visitors::strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
455
459
  collector << 'TO_CHAR('
456
460
  collector << 'CAST(' if o.time_zone
@@ -174,6 +174,10 @@ module ArelExtensions
174
174
  end
175
175
 
176
176
  def visit_ArelExtensions_Nodes_Format o, collector
177
+ visit_ArelExtensions_Nodes_FormattedDate o, collector
178
+ end
179
+
180
+ def visit_ArelExtensions_Nodes_FormattedDate o, collector
177
181
  fmt = ArelExtensions::Visitors::strftime_to_format(o.iso_format, DATE_FORMAT_DIRECTIVES)
178
182
  collector << 'TO_CHAR('
179
183
  collector << '(' if o.time_zone
@@ -9,7 +9,13 @@ module ArelExtensions
9
9
  Arel.quoted('"') \
10
10
  + expr
11
11
  .coalesce('')
12
- .replace('\\', '\\\\').replace('"', '\"').replace("\n", '\n') \
12
+ .replace('\\', '\\\\')
13
+ .replace('"', '\"')
14
+ .replace("\b", '\b')
15
+ .replace("\f", '\f')
16
+ .replace("\n", '\n')
17
+ .replace("\r", '\r')
18
+ .replace("\t", '\t') \
13
19
  + '"'
14
20
  end
15
21
 
@@ -289,6 +295,26 @@ module ArelExtensions
289
295
  collector
290
296
  end
291
297
 
298
+ def visit_ArelExtensions_Nodes_FormattedDate o, collector
299
+ case o.col_type
300
+ when :date, :datetime, :time
301
+ collector << 'STRFTIME('
302
+ collector = visit o.right, collector
303
+ collector << COMMA
304
+ collector = visit o.left, collector
305
+ collector << ')'
306
+ when :integer, :float, :decimal
307
+ collector << 'FORMAT('
308
+ collector = visit o.left, collector
309
+ collector << COMMA
310
+ collector = visit o.right, collector
311
+ collector << ')'
312
+ else
313
+ collector = visit o.left, collector
314
+ end
315
+ collector
316
+ end
317
+
292
318
  # comparators
293
319
 
294
320
  def visit_ArelExtensions_Nodes_Cast o, collector
@@ -256,6 +256,13 @@ end
256
256
 
257
257
  class Arel::Table
258
258
  alias_method(:old_alias, :alias) rescue nil
259
+
260
+ # activerecord 7.1 removed the alias. We might need to remove our dependency
261
+ # on the alias if it proves problematic.
262
+ if !self.respond_to?(:table_name)
263
+ alias :table_name :name
264
+ end
265
+
259
266
  def alias(name = "#{self.name}_2")
260
267
  if name.present?
261
268
  Arel::Nodes::TableAlias.new(self, name)
@@ -12,10 +12,6 @@ end
12
12
 
13
13
  YELLOW = '33'
14
14
 
15
- def warn(msg)
16
- $stderr.puts(colored(YELLOW, msg))
17
- end
18
-
19
15
  # Load gems specific to databases
20
16
  # NOTE:
21
17
  # It's strongly advised to test each database on its own. Loading multiple
@@ -437,123 +437,134 @@ module ArelExtensions
437
437
  end
438
438
 
439
439
  def test_format
440
- assert_equal '2016-05-23', t(@lucas, @created_at.format('%Y-%m-%d'))
441
- assert_equal '2014/03/03 12:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S'))
442
- assert_equal '12:42%', t(@lucas, @updated_at.format('%R%%'))
443
-
444
- # The following tests will ensure proper conversion of timestamps to
445
- # requested timezones.
446
- #
447
- # The names of the timezones is highly dependant on the underlying
448
- # operating system, and this is why we need to handle each database
449
- # separately: the images we're using to test these databases are
450
- # different. So don't rely on the provided examples. Your setup is your
451
- # reference.
452
- #
453
- # One could always have portable code if s/he uses standard
454
- # abbreviations, like:
455
- #
456
- # 1. CET => Central European Time
457
- # 2. CEST => Central European Summer Time
458
- #
459
- # Which implies that the caller should handle daylight saving detection.
460
- # In fact, CET will handle daylight saving in MySQL but not Postgres.
461
- #
462
- # It looks like the posix convention is supported by mysql and
463
- # postgresql, e.g.:
464
- #
465
- # posix/Europe/Paris
466
- # posix/America/Nipigon
467
- #
468
- # so it looks like a more reliably portable way of specifying it.
469
- time_zones = {
470
- 'mssql' => {
471
- 'utc' => 'UTC',
472
- 'sao_paulo' => 'Argentina Standard Time',
473
- 'tahiti' => 'Hawaiian Standard Time',
474
- 'paris' => 'Central European Standard Time'
475
- },
476
- 'posix' => {
477
- 'utc' => 'UTC',
478
- 'sao_paulo' => 'America/Sao_Paulo',
479
- 'tahiti' => 'Pacific/Tahiti',
480
- 'paris' => 'Europe/Paris'
481
- }
482
- }
483
-
484
- skip "Unsupported timezone conversion for DB=#{ENV['DB']}" if !%w[mssql mysql oracle postgresql].include?(ENV['DB'])
485
- # TODO: Standarize timezone conversion across all databases.
486
- # This test case will be refactored and should work the same across all vendors.
487
- if ENV['DB'] == 'mssql' && /Microsoft SQL Server (\d+)/.match(ActiveRecord::Base.connection.select_value('SELECT @@version'))[1].to_i < 2016
488
- skip "SQL Server < 2016 is not currently supported"
489
- end
490
-
491
- tz = ENV['DB'] == 'mssql' ? time_zones['mssql'] : time_zones['posix']
492
-
493
- assert_equal '2014/03/03 12:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S', tz['utc']))
494
- assert_equal '2014/03/03 09:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['sao_paulo']}))
495
- assert_equal '2014/03/03 02:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['tahiti']}))
440
+ %i[format format_date].each do |method|
441
+ assert_equal '2016-05-23', t(@lucas, @created_at.send(method, '%Y-%m-%d'))
442
+ assert_equal '2014/03/03 12:42:00', t(@lucas, @updated_at.send(method, '%Y/%m/%d %H:%M:%S'))
443
+ assert_equal '12:42%', t(@lucas, @updated_at.send(method, '%R%%'))
444
+
445
+ # The following tests will ensure proper conversion of timestamps to
446
+ # requested timezones.
447
+ #
448
+ # The names of the timezones is highly dependant on the underlying
449
+ # operating system, and this is why we need to handle each database
450
+ # separately: the images we're using to test these databases are
451
+ # different. So don't rely on the provided examples. Your setup is your
452
+ # reference.
453
+ #
454
+ # One could always have portable code if s/he uses standard
455
+ # abbreviations, like:
456
+ #
457
+ # 1. CET => Central European Time
458
+ # 2. CEST => Central European Summer Time
459
+ #
460
+ # Which implies that the caller should handle daylight saving detection.
461
+ # In fact, CET will handle daylight saving in MySQL but not Postgres.
462
+ #
463
+ # It looks like the posix convention is supported by mysql and
464
+ # postgresql, e.g.:
465
+ #
466
+ # posix/Europe/Paris
467
+ # posix/America/Nipigon
468
+ #
469
+ # so it looks like a more reliably portable way of specifying it.
470
+ time_zones = {
471
+ 'mssql' => {
472
+ 'utc' => 'UTC',
473
+ 'sao_paulo' => 'Argentina Standard Time',
474
+ 'tahiti' => 'Hawaiian Standard Time',
475
+ 'paris' => 'Central European Standard Time'
476
+ },
477
+ 'posix' => {
478
+ 'utc' => 'UTC',
479
+ 'sao_paulo' => 'America/Sao_Paulo',
480
+ 'tahiti' => 'Pacific/Tahiti',
481
+ 'paris' => 'Europe/Paris'
482
+ }
483
+ }
484
+
485
+ skip "Unsupported timezone conversion for DB=#{ENV['DB']}" if !%w[mssql mysql oracle postgresql].include?(ENV['DB'])
486
+ # TODO: Standarize timezone conversion across all databases.
487
+ # This test case will be refactored and should work the same across all vendors.
488
+ if ENV['DB'] == 'mssql' && /Microsoft SQL Server (\d+)/.match(ActiveRecord::Base.connection.select_value('SELECT @@version'))[1].to_i < 2016
489
+ skip "SQL Server < 2016 is not currently supported"
490
+ end
496
491
 
497
- # Skipping conversion from UTC to the desired timezones fails in SQL
498
- # Server and Postgres. This is mainly due to the fact that timezone
499
- # information is not preserved in the column itself.
500
- #
501
- # MySQL is happy to consider that times by default are in UTC.
502
- assert_equal '2014/03/03 13:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['paris']}))
503
- refute_equal '2014/03/03 13:42:00', t(@lucas, @updated_at.format('%Y/%m/%d %H:%M:%S', tz['paris'])) if !['mysql'].include?(ENV['DB'])
504
-
505
- # Winter/Summer time
506
- assert_equal '2014/08/03 14:42:00', t(@lucas, (@updated_at + 5.months).format('%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['paris']}))
507
- if ENV['DB'] == 'mssql'
508
- assert_equal '2022/02/01 11:42:00', t(@lucas, Arel.quoted('2022-02-01 10:42:00').cast(:datetime).format('%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['paris']}))
509
- assert_equal '2022/08/01 12:42:00', t(@lucas, Arel.quoted('2022-08-01 10:42:00').cast(:datetime).format('%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['paris']}))
510
- else
511
- assert_equal '2022/02/01 11:42:00', t(@lucas, Arel.quoted('2022-02-01 10:42:00').cast(:datetime).format('%Y/%m/%d %H:%M:%S', tz['paris']))
512
- assert_equal '2022/08/01 12:42:00', t(@lucas, Arel.quoted('2022-08-01 10:42:00').cast(:datetime).format('%Y/%m/%d %H:%M:%S', tz['paris']))
492
+ tz = ENV['DB'] == 'mssql' ? time_zones['mssql'] : time_zones['posix']
493
+
494
+ assert_equal '2014/03/03 12:42:00', t(@lucas, @updated_at.send(method, '%Y/%m/%d %H:%M:%S', tz['utc']))
495
+ assert_equal '2014/03/03 09:42:00', t(@lucas, @updated_at.send(method, '%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['sao_paulo']}))
496
+ assert_equal '2014/03/03 02:42:00', t(@lucas, @updated_at.send(method, '%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['tahiti']}))
497
+
498
+ # Skipping conversion from UTC to the desired timezones fails in SQL
499
+ # Server and Postgres. This is mainly due to the fact that timezone
500
+ # information is not preserved in the column itself.
501
+ #
502
+ # MySQL is happy to consider that times by default are in UTC.
503
+ assert_equal '2014/03/03 13:42:00', t(@lucas, @updated_at.send(method, '%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['paris']}))
504
+ refute_equal '2014/03/03 13:42:00', t(@lucas, @updated_at.send(method, '%Y/%m/%d %H:%M:%S', tz['paris'])) if !['mysql'].include?(ENV['DB'])
505
+
506
+ # Winter/Summer time
507
+ assert_equal '2014/08/03 14:42:00', t(@lucas, (@updated_at + 5.months).send(method, '%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['paris']}))
508
+ if ENV['DB'] == 'mssql'
509
+ assert_equal '2022/02/01 11:42:00', t(@lucas, Arel.quoted('2022-02-01 10:42:00').cast(:datetime).send(method, '%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['paris']}))
510
+ assert_equal '2022/08/01 12:42:00', t(@lucas, Arel.quoted('2022-08-01 10:42:00').cast(:datetime).send(method, '%Y/%m/%d %H:%M:%S', {tz['utc'] => tz['paris']}))
511
+ else
512
+ assert_equal '2022/02/01 11:42:00', t(@lucas, Arel.quoted('2022-02-01 10:42:00').cast(:datetime).send(method, '%Y/%m/%d %H:%M:%S', tz['paris']))
513
+ assert_equal '2022/08/01 12:42:00', t(@lucas, Arel.quoted('2022-08-01 10:42:00').cast(:datetime).send(method, '%Y/%m/%d %H:%M:%S', tz['paris']))
514
+ end
513
515
  end
514
516
  end
515
517
 
516
518
  def test_format_iso_week
517
- skip "Unsupported ISO week number for DB=#{ENV['DB']}" if ['sqlite'].include?(ENV['DB'])
518
- assert_equal '10', t(@lucas, @updated_at.format('%V'))
519
- {
520
- '2024-01-01 10:42:00' => '01', # Monday
521
- '2030-01-01 10:42:00' => '01', # Tuesday
522
- '2025-01-01 10:42:00' => '01', # Wednesday
523
- '2026-01-01 10:42:00' => '01', # Thursday
524
- '2027-01-01 10:42:00' => '53', # Friday
525
- '2028-01-01 10:42:00' => '52', # Saturday
526
- '2034-01-01 10:42:00' => '52', # Sunday
527
- }.each do |date, exp|
528
- assert_equal exp, t(@lucas, Arel.quoted(date).cast(:datetime).format('%V'))
519
+ %i[format format_date].each do |method|
520
+ skip "Unsupported ISO week number for DB=#{ENV['DB']}" if ['sqlite'].include?(ENV['DB'])
521
+ assert_equal '10', t(@lucas, @updated_at.send(method, '%V'))
522
+ {
523
+ '2024-01-01 10:42:00' => '01', # Monday
524
+ '2030-01-01 10:42:00' => '01', # Tuesday
525
+ '2025-01-01 10:42:00' => '01', # Wednesday
526
+ '2026-01-01 10:42:00' => '01', # Thursday
527
+ '2027-01-01 10:42:00' => '53', # Friday
528
+ '2028-01-01 10:42:00' => '52', # Saturday
529
+ '2034-01-01 10:42:00' => '52', # Sunday
530
+ }.each do |date, exp|
531
+ assert_equal exp, t(@lucas, Arel.quoted(date).cast(:datetime).send(method, '%V'))
532
+ end
529
533
  end
530
534
  end
531
535
 
532
536
  def test_format_iso_year_of_week
533
537
  skip "Unsupported ISO year of week for DB=#{ENV['DB']}" if %w[mssql sqlite].include?(ENV['DB'])
534
- assert_equal '2014', t(@lucas, @updated_at.format('%G'))
535
-
536
- {
537
- '2024-01-01 10:42:00' => '2024', # Monday
538
- '2030-01-01 10:42:00' => '2030', # Tuesday
539
- '2025-01-01 10:42:00' => '2025', # Wednesday
540
- '2026-01-01 10:42:00' => '2026', # Thursday
541
- '2027-01-01 10:42:00' => '2026', # Friday
542
- '2028-01-01 10:42:00' => '2027', # Saturday
543
- '2034-01-01 10:42:00' => '2033', # Sunday
544
- }.each do |date, exp|
545
- assert_equal exp, t(@lucas, Arel.quoted(date).cast(:datetime).format('%G'))
538
+ %i[format format_date].each do |method|
539
+ assert_equal '2014', t(@lucas, @updated_at.send(method, '%G'))
540
+
541
+ {
542
+ '2024-01-01 10:42:00' => '2024', # Monday
543
+ '2030-01-01 10:42:00' => '2030', # Tuesday
544
+ '2025-01-01 10:42:00' => '2025', # Wednesday
545
+ '2026-01-01 10:42:00' => '2026', # Thursday
546
+ '2027-01-01 10:42:00' => '2026', # Friday
547
+ '2028-01-01 10:42:00' => '2027', # Saturday
548
+ '2034-01-01 10:42:00' => '2033', # Sunday
549
+ }.each do |date, exp|
550
+ assert_equal exp, t(@lucas, Arel.quoted(date).cast(:datetime).send(method, '%G'))
551
+ end
546
552
  end
547
553
  end
548
554
 
549
555
  def test_format_date_with_names
550
556
  skip "#{ENV['DB']} does not support a variety of word-based formatting for month and day names" if %w[mssql sqlite].include?(ENV['DB'])
551
- assert_equal 'Mon, 03 Mar 14', t(@lucas, @updated_at.format('%a, %d %b %y'))
552
- assert_equal 'Monday, 03 March 14', t(@lucas, @updated_at.format('%A, %d %B %y'))
557
+ %i[format format_date].each do |method|
558
+ assert_equal 'Mon, 03 Mar 14', t(@lucas, @updated_at.send(method, '%a, %d %b %y'))
559
+ assert_equal 'Monday, 03 March 14', t(@lucas, @updated_at.send(method, '%A, %d %B %y'))
560
+ end
553
561
 
554
562
  skip "#{ENV['DB']} does not support ALLCAPS month and day names" if ['mysql'].include?(ENV['DB'])
555
- assert_equal 'Mon, 03 MAR 14', t(@lucas, @updated_at.format('%a, %d %^b %y'))
556
- assert_equal 'Monday, 03 MARCH 14', t(@lucas, @updated_at.format('%A, %d %^B %y'))
563
+ %i[format format_date].each do |method|
564
+
565
+ assert_equal 'Mon, 03 MAR 14', t(@lucas, @updated_at.send(method, '%a, %d %^b %y'))
566
+ assert_equal 'Monday, 03 MARCH 14', t(@lucas, @updated_at.send(method, '%A, %d %^B %y'))
567
+ end
557
568
  end
558
569
 
559
570
  def switch_to_lang(lang)
@@ -580,28 +591,30 @@ module ArelExtensions
580
591
  #
581
592
  # Tests should assert one single thing in principle, but until we
582
593
  # refactor this whole thing, we'll have to do tricks of this sort.
583
- begin
584
- switch_to_lang(:en)
585
- case ENV['DB']
586
- when 'mysql', 'postgresql'
587
- assert_equal 'Mon, 03 Mar 14', t(@lucas, @updated_at.format('%a, %d %b %y'))
588
- assert_equal 'Monday, 03 March 14', t(@lucas, @updated_at.format('%A, %d %B %y'))
589
- when 'mssql'
590
- assert_equal 'Monday, 03 March 2014', t(@lucas, @updated_at.format('%A, %d %B %y'))
594
+ %i[format format_date].each do |method|
595
+ begin
596
+ switch_to_lang(:en)
597
+ case ENV['DB']
598
+ when 'mysql', 'postgresql'
599
+ assert_equal 'Mon, 03 Mar 14', t(@lucas, @updated_at.send(method, '%a, %d %b %y'))
600
+ assert_equal 'Monday, 03 March 14', t(@lucas, @updated_at.send(method, '%A, %d %B %y'))
601
+ when 'mssql'
602
+ assert_equal 'Monday, 03 March 2014', t(@lucas, @updated_at.send(method, '%A, %d %B %y'))
603
+ end
604
+ switch_to_lang(:fr)
605
+ case ENV['DB']
606
+ when 'mysql'
607
+ assert_equal 'lun, 03 mar 14', t(@lucas, @updated_at.send(method, '%a, %d %b %y'))
608
+ assert_equal 'lundi, 03 mars 14', t(@lucas, @updated_at.send(method, '%A, %d %B %y'))
609
+ when 'postgresql'
610
+ assert_equal 'Lun., 03 Mars 14', t(@lucas, @updated_at.send(method, '%a, %d %b %y'))
611
+ assert_equal 'Lundi, 03 Mars 14', t(@lucas, @updated_at.send(method, '%A, %d %B %y'))
612
+ when 'mssql'
613
+ assert_equal 'lundi, 03 mars 2014', t(@lucas, @updated_at.send(method, '%A, %d %B %y'))
614
+ end
615
+ ensure
616
+ switch_to_lang(:en)
591
617
  end
592
- switch_to_lang(:fr)
593
- case ENV['DB']
594
- when 'mysql'
595
- assert_equal 'lun, 03 mar 14', t(@lucas, @updated_at.format('%a, %d %b %y'))
596
- assert_equal 'lundi, 03 mars 14', t(@lucas, @updated_at.format('%A, %d %B %y'))
597
- when 'postgresql'
598
- assert_equal 'Lun., 03 Mars 14', t(@lucas, @updated_at.format('%a, %d %b %y'))
599
- assert_equal 'Lundi, 03 Mars 14', t(@lucas, @updated_at.format('%A, %d %B %y'))
600
- when 'mssql'
601
- assert_equal 'lundi, 03 mars 2014', t(@lucas, @updated_at.format('%A, %d %B %y'))
602
- end
603
- ensure
604
- switch_to_lang(:en)
605
618
  end
606
619
  end
607
620
 
@@ -632,6 +645,7 @@ module ArelExtensions
632
645
  assert_equal 'Myung', t(@myung, @comments.coalesce_blank('', ' ', ' ').coalesce_blank('Myung'))
633
646
  assert_equal 'Myung', t(@myung, @comments.coalesce_blank('', ' ', ' ', 'Myung'))
634
647
  assert_equal '2016-05-23', t(@myung, @created_at.coalesce_blank(Date.new(2022, 1, 1)).format('%Y-%m-%d'))
648
+ assert_equal '2016-05-23', t(@myung, @created_at.coalesce_blank(Date.new(2022, 1, 1)).format_date('%Y-%m-%d'))
635
649
  assert_equal 'Laure', t(@laure, @comments.coalesce_blank('Laure'))
636
650
  assert_equal 100, t(@test, @age.coalesce_blank(100))
637
651
  assert_equal 20, t(@test, @age.coalesce_blank(20))
@@ -661,15 +675,22 @@ module ArelExtensions
661
675
  end
662
676
 
663
677
  def test_date_duration
678
+ # When user `nilly` is created, with an explicit `created_at: nil`,
679
+ # activerecord will give it the current date.
680
+ #
681
+ # So depending on the month when we run this test, we will get different
682
+ # results for `User.where(@created_at.month.eq('05'))`.
683
+ count_for_may = Time.now.month == 5 ? 10 : 9
684
+
664
685
  # Year
665
686
  assert_equal 2016, t(@lucas, @created_at.year).to_i
666
687
  assert_equal 0, User.where(@created_at.year.eq('2012')).count
667
688
  # Month
668
689
  assert_equal 5, t(@camille, @created_at.month).to_i
669
- assert_equal 9, User.where(@created_at.month.eq('05')).count
690
+ assert_equal count_for_may, User.where(@created_at.month.eq('05')).count
670
691
  # Week
671
692
  assert_equal(@env_db == 'mssql' ? 22 : 21, t(@arthur, @created_at.week).to_i)
672
- assert_equal 9, User.where(@created_at.month.eq('05')).count
693
+ assert_equal count_for_may, User.where(@created_at.month.eq('05')).count
673
694
  # Day
674
695
  assert_equal 23, t(@laure, @created_at.day).to_i
675
696
  assert_equal 0, User.where(@created_at.day.eq('05')).count
@@ -734,8 +755,10 @@ module ArelExtensions
734
755
  t(@lucas, (@updated_at + Arel.duration('mn', (@updated_at.hour * 60 + @updated_at.minute))))
735
756
 
736
757
  assert_includes ['2024-03-03'], t(@lucas, (@updated_at + durPos).format('%Y-%m-%d'))
758
+ assert_includes ['2024-03-03'], t(@lucas, (@updated_at + durPos).format_date('%Y-%m-%d'))
737
759
  # puts (@updated_at - durPos).to_sql
738
760
  assert_includes ['2004-03-03'], t(@lucas, (@updated_at - durPos).format('%Y-%m-%d'))
761
+ assert_includes ['2004-03-03'], t(@lucas, (@updated_at - durPos).format_date('%Y-%m-%d'))
739
762
 
740
763
 
741
764
  # we test with the ruby object or the string because some adapters don't return an object Date
@@ -775,9 +798,11 @@ module ArelExtensions
775
798
  assert_equal 0, t(@myung, @comments.if_present.count)
776
799
  assert_equal 20.16, t(@myung, @score.if_present)
777
800
  assert_equal '2016-05-23', t(@myung, @created_at.if_present.format('%Y-%m-%d'))
801
+ assert_equal '2016-05-23', t(@myung, @created_at.if_present.format_date('%Y-%m-%d'))
778
802
  assert_nil t(@laure, @comments.if_present)
779
803
 
780
804
  assert_nil t(@nilly, @duration.if_present.format('%Y-%m-%d'))
805
+ assert_nil t(@nilly, @duration.if_present.format_date('%Y-%m-%d'))
781
806
 
782
807
  # NOTE: here we're testing the capacity to format a nil value,
783
808
  # however, @comments is a text field, and not a date/datetime field,
@@ -785,8 +810,10 @@ module ArelExtensions
785
810
  # we need to cast it first.
786
811
  if @env_db == 'postgresql'
787
812
  assert_nil t(@laure, @comments.cast(:date).if_present.format('%Y-%m-%d'))
813
+ assert_nil t(@laure, @comments.cast(:date).if_present.format_date('%Y-%m-%d'))
788
814
  else
789
815
  assert_nil t(@laure, @comments.if_present.format('%Y-%m-%d'))
816
+ assert_nil t(@laure, @comments.if_present.format_date('%Y-%m-%d'))
790
817
  end
791
818
  end
792
819
 
data/version_v1.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ArelExtensions
2
- VERSION = '1.3.6'.freeze
2
+ VERSION = '1.3.8'.freeze
3
3
  end
data/version_v2.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ArelExtensions
2
- VERSION = '2.1.6'.freeze
2
+ VERSION = '2.1.8'.freeze
3
3
  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: 1.3.6
4
+ version: 1.3.8
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: 2022-11-22 00:00:00.000000000 Z
13
+ date: 2023-11-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: arel
@@ -79,6 +79,7 @@ files:
79
79
  - TODO
80
80
  - appveyor.yml
81
81
  - arel_extensions.gemspec
82
+ - dev/compose.yaml
82
83
  - functions.html
83
84
  - gemfiles/rails3.gemfile
84
85
  - gemfiles/rails4_2.gemfile
@@ -88,6 +89,7 @@ files:
88
89
  - gemfiles/rails6.gemfile
89
90
  - gemfiles/rails6_1.gemfile
90
91
  - gemfiles/rails7.gemfile
92
+ - gemfiles/rails7_1.gemfile
91
93
  - gemspecs/arel_extensions-v1.gemspec
92
94
  - gemspecs/arel_extensions-v2.gemspec
93
95
  - generate_gems.sh
@@ -123,6 +125,7 @@ files:
123
125
  - lib/arel_extensions/nodes/find_in_set.rb
124
126
  - lib/arel_extensions/nodes/floor.rb
125
127
  - lib/arel_extensions/nodes/format.rb
128
+ - lib/arel_extensions/nodes/formatted_date.rb
126
129
  - lib/arel_extensions/nodes/formatted_number.rb
127
130
  - lib/arel_extensions/nodes/function.rb
128
131
  - lib/arel_extensions/nodes/is_null.rb
@@ -207,7 +210,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
207
210
  - !ruby/object:Gem::Version
208
211
  version: '0'
209
212
  requirements: []
210
- rubygems_version: 3.2.3
213
+ rubygems_version: 3.3.5
211
214
  signing_key:
212
215
  specification_version: 4
213
216
  summary: Extending Arel