arel_extensions 2.1.6 → 2.1.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|