activerecord-refined 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/sandbox.yml +162 -26
- data/.github/workflows/test.yml +15 -2
- data/README.md +486 -52
- data/activerecord-refined.gemspec +3 -2
- data/examples/ctes.rb +41 -10
- data/examples/expressions.rb +71 -5
- data/examples/json.rb +94 -0
- data/examples/postgresql.rb +89 -6
- data/examples/predicates.rb +32 -5
- data/examples/windows.rb +96 -0
- data/examples/writes.rb +84 -0
- data/lib/active_record/refined/ast.rb +831 -94
- data/lib/active_record/refined.rb +351 -26
- data/lib/activerecord-refined/version.rb +1 -1
- data/lib/activerecord-refined.rb +7 -0
- data/test/test_block_syntax.rb +1100 -51
- data/test/test_helper.rb +82 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d179cda5b9633e349569aa64a129909938a377302ec7091d2dc342f1e809dc9
|
|
4
|
+
data.tar.gz: 94cd9271298ee1b67e53ebe53c1f9a7b3ab5548e796f18d3bd2ec3e8ec24f320
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d4a032b8a00105c9f1d655e1cd745e2b2c66e4c10a1f0ca737451bd1f4dabf519227cf4c86f79d17f47340f7f26071f386fd5e88bf7488bb4de31513380178c5
|
|
7
|
+
data.tar.gz: 42448bbad15330e96a8cb56760a463b17880256360d8f8f05684942ae758db148c24a3fb32c81c35bda840281265a21a120481aa9c5eaf3369d7b082ffc10e48
|
|
@@ -33,13 +33,75 @@ jobs:
|
|
|
33
33
|
with:
|
|
34
34
|
node-version: '24'
|
|
35
35
|
|
|
36
|
+
# What the binary is made of: the gems with C extensions, the scripts
|
|
37
|
+
# that drive the build -- RUBY_REV and the toolchain versions are pinned
|
|
38
|
+
# in them -- the staged extension, and the host Ruby. Not the gem's own
|
|
39
|
+
# lib and not the page, neither of which the binary contains; those are
|
|
40
|
+
# files the page fetches at startup.
|
|
41
|
+
#
|
|
42
|
+
# Naming the build after this rather than after the binary is what makes
|
|
43
|
+
# the URL stable. The build is not reproducible to the byte -- the same
|
|
44
|
+
# tree twice over came out 1 KB apart, mtimes riding along in the packed
|
|
45
|
+
# filesystem -- so a hash of the output would change on every deploy and
|
|
46
|
+
# give every visitor 12 MB to fetch again for nothing.
|
|
47
|
+
- name: Identify the build
|
|
48
|
+
id: build
|
|
49
|
+
run: |
|
|
50
|
+
hash="${{ hashFiles('sandbox/.ruby-version', 'sandbox/Gemfile.lock', 'sandbox/bin/build-wasm', 'sandbox/bin/rbwasm-build.rb', 'sandbox/bin/install-wasi-sdk.rb', 'sandbox/ext/**') }}"
|
|
51
|
+
|
|
52
|
+
# hashFiles gives back an empty string when it matches nothing, and
|
|
53
|
+
# an empty hash would quietly name every build alike -- and hand out
|
|
54
|
+
# whichever binary happened to be uploaded first, for good.
|
|
55
|
+
if [ -z "$hash" ]; then
|
|
56
|
+
echo "::error::hashFiles matched none of the build inputs" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
echo "hash=$hash" >> "$GITHUB_OUTPUT"
|
|
61
|
+
echo "key=ruby-${hash:0:16}.wasm" >> "$GITHUB_OUTPUT"
|
|
62
|
+
echo "this build is ruby-${hash:0:16}.wasm"
|
|
63
|
+
|
|
64
|
+
# The binary for these inputs may already be in the bucket from an
|
|
65
|
+
# earlier deploy, in which case there is nothing to build: 12 MB comes
|
|
66
|
+
# down in a few seconds where a build takes two minutes warm and sixteen
|
|
67
|
+
# cold. The examples are still checked against it below, which is what
|
|
68
|
+
# the deploy is really for.
|
|
69
|
+
#
|
|
70
|
+
# Anything at all going wrong here -- no credentials, as on a fork, a
|
|
71
|
+
# network failure, a half-written file -- falls through to building.
|
|
72
|
+
- name: Fetch this build from R2 if it is there
|
|
73
|
+
id: prebuilt
|
|
74
|
+
if: vars.R2_PUBLIC_URL != ''
|
|
75
|
+
env:
|
|
76
|
+
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
77
|
+
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
78
|
+
AWS_DEFAULT_REGION: auto
|
|
79
|
+
AWS_REQUEST_CHECKSUM_CALCULATION: when_required
|
|
80
|
+
AWS_RESPONSE_CHECKSUM_VALIDATION: when_required
|
|
81
|
+
ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
|
82
|
+
BUCKET: ${{ vars.R2_BUCKET }}
|
|
83
|
+
KEY: ${{ steps.build.outputs.key }}
|
|
84
|
+
run: |
|
|
85
|
+
if aws s3api get-object \
|
|
86
|
+
--endpoint-url "$ENDPOINT" \
|
|
87
|
+
--bucket "$BUCKET" \
|
|
88
|
+
--key "$KEY" \
|
|
89
|
+
/tmp/ruby.wasm.gz \
|
|
90
|
+
--no-cli-pager > /dev/null 2>&1 &&
|
|
91
|
+
gunzip -c /tmp/ruby.wasm.gz > ruby.wasm
|
|
92
|
+
then
|
|
93
|
+
echo "found=true" >> "$GITHUB_OUTPUT"
|
|
94
|
+
echo "took $KEY from the bucket; not building"
|
|
95
|
+
ls -l ruby.wasm
|
|
96
|
+
else
|
|
97
|
+
rm -f ruby.wasm
|
|
98
|
+
echo "$KEY is not in the bucket; building it"
|
|
99
|
+
fi
|
|
100
|
+
|
|
36
101
|
# The Ruby checkout and the build tree have to be cached together. make
|
|
37
102
|
# decides by mtime, so restoring objects next to a freshly cloned Ruby
|
|
38
|
-
# would rebuild everything.
|
|
39
|
-
#
|
|
40
|
-
# The build directory is keyed on the gems that have C extensions, so
|
|
41
|
-
# Gemfile.lock covers it; RUBY_REV is pinned in bin/build-wasm, and the
|
|
42
|
-
# staged extension comes from ext/ and the sqlite3 gem.
|
|
103
|
+
# would rebuild everything. Keyed on the same inputs as the binary, so
|
|
104
|
+
# the two cannot drift apart.
|
|
43
105
|
#
|
|
44
106
|
# restore-keys is what keeps this worth caching -- a key change would
|
|
45
107
|
# otherwise mean a cold 16-minute build rather than two minutes -- but it
|
|
@@ -48,16 +110,18 @@ jobs:
|
|
|
48
110
|
# deleting the cache does. bin/build-wasm rejects a ruby.wasm built from
|
|
49
111
|
# such a tree, so it fails the run rather than shipping.
|
|
50
112
|
- name: Restore the WASM build tree
|
|
113
|
+
if: steps.prebuilt.outputs.found != 'true'
|
|
51
114
|
uses: actions/cache@v6
|
|
52
115
|
with:
|
|
53
116
|
path: sandbox/build
|
|
54
|
-
key: wasm-${{ runner.os }}-${{
|
|
117
|
+
key: wasm-${{ runner.os }}-${{ steps.build.outputs.hash }}
|
|
55
118
|
restore-keys: wasm-${{ runner.os }}-
|
|
56
119
|
|
|
57
120
|
- run: bundle install --jobs 4
|
|
58
121
|
- run: npm ci
|
|
59
122
|
|
|
60
123
|
- name: Build ruby.wasm
|
|
124
|
+
if: steps.prebuilt.outputs.found != 'true'
|
|
61
125
|
run: ./bin/build-wasm
|
|
62
126
|
|
|
63
127
|
- name: Assemble the Ruby files served to the page
|
|
@@ -69,12 +133,13 @@ jobs:
|
|
|
69
133
|
- name: Check the examples
|
|
70
134
|
run: node --stack-size=4000 check-examples.mjs
|
|
71
135
|
|
|
72
|
-
# ruby.wasm is 40 MB
|
|
73
|
-
# is
|
|
74
|
-
#
|
|
136
|
+
# ruby.wasm is 40 MB and nearly all of the bandwidth this page uses, so
|
|
137
|
+
# it is served from R2 -- egress there is free -- and only the page
|
|
138
|
+
# itself from Pages.
|
|
75
139
|
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
140
|
+
# Only when it was built here: found in the bucket, it is already up.
|
|
141
|
+
# The object is therefore written once and never rewritten, which is what
|
|
142
|
+
# the immutable caching claims. R2 serves what it is given and does not
|
|
78
143
|
# compress, hence gzip with the encoding recorded on the object: 40 MB
|
|
79
144
|
# becomes 12 MB on the wire.
|
|
80
145
|
#
|
|
@@ -88,36 +153,108 @@ jobs:
|
|
|
88
153
|
# account, which is not what a public repository's CI should hold.
|
|
89
154
|
- name: Upload ruby.wasm to R2
|
|
90
155
|
id: r2
|
|
91
|
-
if:
|
|
156
|
+
if: >-
|
|
157
|
+
github.ref == 'refs/heads/master' && vars.R2_PUBLIC_URL != ''
|
|
158
|
+
&& steps.prebuilt.outputs.found != 'true'
|
|
92
159
|
env:
|
|
160
|
+
KEY: ${{ steps.build.outputs.key }}
|
|
93
161
|
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
94
162
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
95
163
|
AWS_DEFAULT_REGION: auto
|
|
96
164
|
# R2 sends no checksum headers back, which newer AWS CLIs insist on.
|
|
97
165
|
AWS_REQUEST_CHECKSUM_CALCULATION: when_required
|
|
98
166
|
AWS_RESPONSE_CHECKSUM_VALIDATION: when_required
|
|
167
|
+
ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
|
168
|
+
BUCKET: ${{ vars.R2_BUCKET }}
|
|
169
|
+
# How many builds to keep, the newest included. A page loaded just
|
|
170
|
+
# before a deploy goes on finding its binary, and a bad build can be
|
|
171
|
+
# rolled back by hand; further back is 12 MB apiece for nothing.
|
|
172
|
+
KEEP: 3
|
|
99
173
|
run: |
|
|
100
174
|
gzip -9 -c ruby.wasm > /tmp/ruby.wasm.gz
|
|
101
175
|
|
|
102
|
-
# One key, overwritten each time, since only the newest build is of
|
|
103
|
-
# any use. That rules out caching it as immutable: the URL no longer
|
|
104
|
-
# changes with the contents, so a long max-age would go on serving
|
|
105
|
-
# the previous binary. Five minutes bounds how stale a visitor's
|
|
106
|
-
# copy can be just after a deploy, and revalidation costs a 304
|
|
107
|
-
# rather than 12 MB.
|
|
108
176
|
aws s3api put-object \
|
|
109
|
-
--endpoint-url "
|
|
110
|
-
--bucket "$
|
|
111
|
-
--key
|
|
177
|
+
--endpoint-url "$ENDPOINT" \
|
|
178
|
+
--bucket "$BUCKET" \
|
|
179
|
+
--key "$KEY" \
|
|
112
180
|
--body /tmp/ruby.wasm.gz \
|
|
113
181
|
--content-type application/wasm \
|
|
114
182
|
--content-encoding gzip \
|
|
115
|
-
--cache-control "public, max-age=
|
|
183
|
+
--cache-control "public, max-age=31536000, immutable" \
|
|
116
184
|
--no-cli-pager
|
|
117
185
|
|
|
118
|
-
echo '{"rubyWasmUrl":"${{ vars.R2_PUBLIC_URL }}/ruby-head.wasm"}' > config.json
|
|
119
186
|
echo "uploaded=true" >> "$GITHUB_OUTPUT"
|
|
120
187
|
|
|
188
|
+
# Distinct keys have nothing to remove them, so the oldest go here.
|
|
189
|
+
# Ordered by age rather than by name, since a key says which inputs
|
|
190
|
+
# built it but not which came first; the timestamps are UTC ISO 8601,
|
|
191
|
+
# so sorting them as text sorts them by time. `head -n -KEEP` is
|
|
192
|
+
# every line but the last KEEP, and prints nothing until there are
|
|
193
|
+
# more.
|
|
194
|
+
#
|
|
195
|
+
# The listing is written out before it is sorted, so that what the if
|
|
196
|
+
# tests is the listing: a pipeline would report the sort, which
|
|
197
|
+
# succeeds on no input either way. A failure is said rather than
|
|
198
|
+
# raised, since the binary is already up and a stale object left
|
|
199
|
+
# behind is no reason to hold back the deploy that needs the new one.
|
|
200
|
+
if aws s3api list-objects-v2 \
|
|
201
|
+
--endpoint-url "$ENDPOINT" \
|
|
202
|
+
--bucket "$BUCKET" \
|
|
203
|
+
--prefix ruby- \
|
|
204
|
+
--query "Contents[].[LastModified,Key]" \
|
|
205
|
+
--output text --no-cli-pager > /tmp/builds
|
|
206
|
+
then
|
|
207
|
+
sort /tmp/builds | head -n "-$KEEP" | cut -f2 | while read -r stale; do
|
|
208
|
+
# The one just uploaded is never stale, whatever the listing says
|
|
209
|
+
# of its age: it is the binary the page about to be deployed asks
|
|
210
|
+
# for. Whatever else is under the prefix is not ours to delete.
|
|
211
|
+
case "$stale" in
|
|
212
|
+
"$KEY") echo "keeping $stale, which is this build"; continue ;;
|
|
213
|
+
ruby-*.wasm) ;;
|
|
214
|
+
*) echo "leaving $stale alone"; continue ;;
|
|
215
|
+
esac
|
|
216
|
+
echo "removing $stale"
|
|
217
|
+
aws s3api delete-object \
|
|
218
|
+
--endpoint-url "$ENDPOINT" \
|
|
219
|
+
--bucket "$BUCKET" \
|
|
220
|
+
--key "$stale" \
|
|
221
|
+
--no-cli-pager ||
|
|
222
|
+
echo "::warning::could not remove $stale"
|
|
223
|
+
done
|
|
224
|
+
else
|
|
225
|
+
echo "::warning::could not list $BUCKET; older builds were left in place"
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
# The preload in the head is what the page fetches the binary by, and it
|
|
229
|
+
# ships pointing at the local build. Naming R2 there rather than in a
|
|
230
|
+
# file the page has to read first is worth a round trip on every visit:
|
|
231
|
+
# the browser can start on the 12 MB while it is still parsing the head,
|
|
232
|
+
# instead of after the scripts have loaded and asked where to look.
|
|
233
|
+
#
|
|
234
|
+
# Done whether the binary went up in this run or was already there. A
|
|
235
|
+
# checkout with no deployment behind it keeps the line as written, and
|
|
236
|
+
# the artifact carries the ruby.wasm beside it.
|
|
237
|
+
- name: Point the page at R2
|
|
238
|
+
id: r2url
|
|
239
|
+
if: steps.prebuilt.outputs.found == 'true' || steps.r2.outputs.uploaded == 'true'
|
|
240
|
+
env:
|
|
241
|
+
URL: ${{ vars.R2_PUBLIC_URL }}/${{ steps.build.outputs.key }}
|
|
242
|
+
run: |
|
|
243
|
+
before=$(grep -c 'id="ruby-wasm"' index.html)
|
|
244
|
+
if [ "$before" != 1 ]; then
|
|
245
|
+
echo "::error::expected one preload link in index.html, found $before" >&2
|
|
246
|
+
exit 1
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
sed -i "s|\(<link id=\"ruby-wasm\"[^>]*href=\)\"[^\"]*\"|\1\"$URL\"|" index.html
|
|
250
|
+
grep '<link id="ruby-wasm"' index.html
|
|
251
|
+
|
|
252
|
+
if ! grep -q "href=\"$URL\"" index.html; then
|
|
253
|
+
echo "::error::the preload link was not rewritten" >&2
|
|
254
|
+
exit 1
|
|
255
|
+
fi
|
|
256
|
+
echo "served=true" >> "$GITHUB_OUTPUT"
|
|
257
|
+
|
|
121
258
|
- uses: actions/upload-artifact@v7
|
|
122
259
|
with:
|
|
123
260
|
name: sandbox
|
|
@@ -126,10 +263,9 @@ jobs:
|
|
|
126
263
|
sandbox/boot.rb
|
|
127
264
|
sandbox/examples.js
|
|
128
265
|
sandbox/manifest.json
|
|
129
|
-
sandbox/
|
|
130
|
-
${{ steps.r2.outputs.uploaded != 'true' && 'sandbox/ruby.wasm' || '' }}
|
|
266
|
+
${{ steps.r2url.outputs.served != 'true' && 'sandbox/ruby.wasm' || '' }}
|
|
131
267
|
sandbox/rb
|
|
132
|
-
sandbox/
|
|
268
|
+
sandbox/assets
|
|
133
269
|
if-no-files-found: ignore
|
|
134
270
|
retention-days: 7
|
|
135
271
|
|
data/.github/workflows/test.yml
CHANGED
|
@@ -48,8 +48,21 @@ jobs:
|
|
|
48
48
|
bundler-cache: true
|
|
49
49
|
- run: bundle exec rake test
|
|
50
50
|
|
|
51
|
+
# Both answer to the mysql2 adapter and they do not agree: MariaDB's json
|
|
52
|
+
# column is a checked longtext where MySQL's is a type of its own, and
|
|
53
|
+
# MariaDB casts to integer where MySQL knows only signed. Each has passed
|
|
54
|
+
# while the other failed, so both run.
|
|
51
55
|
mysql:
|
|
56
|
+
name: mysql (${{ matrix.image }})
|
|
52
57
|
runs-on: ubuntu-latest
|
|
58
|
+
strategy:
|
|
59
|
+
fail-fast: false
|
|
60
|
+
matrix:
|
|
61
|
+
include:
|
|
62
|
+
- image: mysql:8
|
|
63
|
+
health: mysqladmin ping -proot
|
|
64
|
+
- image: mariadb:11
|
|
65
|
+
health: healthcheck.sh --connect --innodb_initialized
|
|
53
66
|
env:
|
|
54
67
|
ADAPTER: mysql2
|
|
55
68
|
# The suite creates its own database, which MYSQL_USER would not be
|
|
@@ -58,13 +71,13 @@ jobs:
|
|
|
58
71
|
DB_PASSWORD: root
|
|
59
72
|
services:
|
|
60
73
|
mysql:
|
|
61
|
-
image:
|
|
74
|
+
image: ${{ matrix.image }}
|
|
62
75
|
env:
|
|
63
76
|
MYSQL_ROOT_PASSWORD: root
|
|
64
77
|
ports:
|
|
65
78
|
- 3306:3306
|
|
66
79
|
options: >-
|
|
67
|
-
--health-cmd "
|
|
80
|
+
--health-cmd "${{ matrix.health }}"
|
|
68
81
|
--health-interval 10s
|
|
69
82
|
--health-timeout 5s
|
|
70
83
|
--health-retries 5
|