arel_extensions 2.1.6 → 2.1.8
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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +96 -65
- data/NEWS.md +20 -3
- data/README.md +46 -9
- data/Rakefile +7 -0
- data/dev/compose.yaml +29 -0
- data/gemfiles/rails6_1.gemfile +1 -1
- data/gemfiles/rails7.gemfile +1 -1
- data/gemfiles/rails7_1.gemfile +22 -0
- data/lib/arel_extensions/date_duration.rb +5 -0
- data/lib/arel_extensions/helpers.rb +11 -5
- data/lib/arel_extensions/nodes/concat.rb +1 -1
- data/lib/arel_extensions/nodes/formatted_date.rb +42 -0
- data/lib/arel_extensions/nodes/json.rb +6 -2
- data/lib/arel_extensions/version.rb +1 -1
- data/lib/arel_extensions/visitors/mssql.rb +4 -0
- data/lib/arel_extensions/visitors/mysql.rb +28 -22
- data/lib/arel_extensions/visitors/oracle.rb +4 -0
- data/lib/arel_extensions/visitors/postgresql.rb +4 -0
- data/lib/arel_extensions/visitors/to_sql.rb +27 -1
- data/lib/arel_extensions.rb +7 -0
- data/test/arelx_test_helper.rb +0 -4
- data/test/with_ar/all_agnostic_test.rb +150 -123
- data/version_v1.rb +1 -1
- data/version_v2.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a3d245de4fca76ff0c5c0806b3b1ed04f47afd7bab31e104c9366870308b638
|
|
4
|
+
data.tar.gz: 95d67960f12cdd7f6bda282e6258d069276e2e24146c26e70b868e4828ed10ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4cdf102bc01abe1b41c19f129d86a28b8e8e43e702e9240a8aa286d9bcc71a04dba47965667f185379d1b69ee69490e40bd7dc3e7f1b974f5e95a2ce59424964
|
|
7
|
+
data.tar.gz: d93e6301b77655ac3ff34164f7f1794846d408be2751705b3cf467808a7919e31be6b5bb43a3996f61ddd89d97f681cb34793d93d98e1433c16a3dba96f7795b
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -13,19 +13,25 @@ jobs:
|
|
|
13
13
|
fail-fast: false
|
|
14
14
|
matrix:
|
|
15
15
|
versions: [
|
|
16
|
-
{ruby: 3.
|
|
17
|
-
{ruby: 3.
|
|
18
|
-
{ruby: 3.
|
|
19
|
-
{ruby: 3.
|
|
20
|
-
{ruby:
|
|
21
|
-
{ruby:
|
|
22
|
-
{ruby:
|
|
23
|
-
{ruby:
|
|
24
|
-
{ruby:
|
|
25
|
-
{ruby: 2.
|
|
26
|
-
{ruby: 2.
|
|
27
|
-
{ruby: 2.
|
|
28
|
-
{ruby: 2.
|
|
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.
|
|
69
|
-
{ruby: 3.
|
|
70
|
-
{ruby: 3.
|
|
71
|
-
{ruby: 3.
|
|
72
|
-
{ruby:
|
|
73
|
-
{ruby:
|
|
74
|
-
{ruby:
|
|
75
|
-
{ruby:
|
|
76
|
-
{ruby:
|
|
77
|
-
{ruby: 2.
|
|
78
|
-
{ruby: 2.
|
|
79
|
-
{ruby: 2.
|
|
80
|
-
{ruby: 2.
|
|
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.
|
|
122
|
-
{ruby: 3.
|
|
123
|
-
{ruby: 3.
|
|
124
|
-
{ruby: 3.
|
|
125
|
-
{ruby:
|
|
126
|
-
{ruby:
|
|
127
|
-
{ruby:
|
|
128
|
-
{ruby:
|
|
129
|
-
{ruby:
|
|
130
|
-
{ruby: 2.
|
|
131
|
-
{ruby: 2.
|
|
132
|
-
{ruby: 2.
|
|
133
|
-
{ruby: 2.
|
|
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.
|
|
200
|
-
{ruby: 3.
|
|
201
|
-
{ruby: 3.
|
|
202
|
-
{ruby: 3.
|
|
203
|
-
{ruby:
|
|
204
|
-
{ruby:
|
|
205
|
-
{ruby:
|
|
206
|
-
{ruby:
|
|
207
|
-
{ruby:
|
|
208
|
-
{ruby: 2.
|
|
209
|
-
{ruby: 2.
|
|
210
|
-
{ruby: 2.
|
|
211
|
-
{ruby: 2.
|
|
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.
|
|
273
|
-
{ruby: 3.
|
|
274
|
-
# {ruby: 3.
|
|
275
|
-
{ruby: 3.
|
|
276
|
-
# {ruby:
|
|
277
|
-
{ruby:
|
|
278
|
-
{ruby: 2.7,
|
|
279
|
-
{ruby: 2.7,
|
|
280
|
-
{ruby: 2.7,
|
|
281
|
-
{ruby: 2.
|
|
282
|
-
{ruby: 2.
|
|
283
|
-
{ruby: 2.5,
|
|
284
|
-
{ruby: 2.5,
|
|
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
|
-
#
|
|
1
|
+
# News
|
|
2
2
|
|
|
3
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 '
|
|
98
|
+
# => my_table.name REGEXP '^[a-d_]+'
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
|
|
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
|
-
|
|
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
|
data/gemfiles/rails6_1.gemfile
CHANGED
|
@@ -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
|
|
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]
|
data/gemfiles/rails7.gemfile
CHANGED
|
@@ -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
|
|
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}
|
|
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.
|
|
37
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
|
@@ -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?
|
|
263
|
-
|
|
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
|
-
|
|
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('\\', '\\\\')
|
|
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
|
data/lib/arel_extensions.rb
CHANGED
|
@@ -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)
|
data/test/arelx_test_helper.rb
CHANGED
|
@@ -437,123 +437,134 @@ module ArelExtensions
|
|
|
437
437
|
end
|
|
438
438
|
|
|
439
439
|
def test_format
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
assert_equal '
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
assert_equal '
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
-
|
|
552
|
-
|
|
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
|
-
|
|
556
|
-
|
|
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
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
|
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
|
|
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
data/version_v2.rb
CHANGED
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.1.
|
|
4
|
+
version: 2.1.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:
|
|
13
|
+
date: 2023-11-14 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: activerecord
|
|
@@ -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.
|
|
213
|
+
rubygems_version: 3.3.5
|
|
211
214
|
signing_key:
|
|
212
215
|
specification_version: 4
|
|
213
216
|
summary: Extending Arel
|