galaaz 2.1.3 → 2.1.5

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.
data/blogs/gknit/gknit.md CHANGED
@@ -3,7 +3,7 @@ title: "How to do reproducible research in Ruby with gKnit"
3
3
  author:
4
4
  - "Rodrigo Botafogo"
5
5
  - "Daniel Mossé - University of Pittsburgh"
6
- tags: [Tech, Data Science, Ruby, R, JRuby, "GNU R", Galaaz, gKnit, knitr]
6
+ tags: [Tech, Data Science, Ruby, R, JRuby, CRuby, "GNU R", Galaaz, gKnit, knitr]
7
7
  date: "29/04/2019 (narrative updated for Galaaz 2.0, 2026)"
8
8
  bibliography: stats.bib
9
9
  output:
@@ -133,7 +133,7 @@ written both in Ruby and/or R and output it in any of the available formats of _
133
133
  allows ruby developers to do literate programming and reproducible research by allowing them to
134
134
  have in a single document, text and code.
135
135
 
136
- gKnit runs with **JRuby**, **GNU R**, and **Galaaz** (the integration layer between Ruby and R—see below).
136
+ gKnit runs with **JRuby or CRuby**, **GNU R**, and **Galaaz** (the integration layer between Ruby and R—see below).
137
137
  Knitr and **R Markdown** orchestrate the document; Galaaz’s engine keeps **Ruby state across chunks**
138
138
  and talks to R through the **bridge**. Ruby chunks can read and update R variables (`~R[:name]`, `R.*`)
139
139
  without GraalVM-style polyglot interop.
@@ -163,7 +163,7 @@ title: "How to do reproducible research in Ruby with gKnit"
163
163
  author:
164
164
  - "Rodrigo Botafogo"
165
165
  - "Daniel Mossé - University of Pittsburgh"
166
- tags: [Tech, Data Science, Ruby, R, JRuby, "GNU R", Galaaz]
166
+ tags: [Tech, Data Science, Ruby, R, JRuby, CRuby, "GNU R", Galaaz]
167
167
  date: "20/02/2019"
168
168
  output:
169
169
  html_document:
@@ -1166,7 +1166,8 @@ using even a very complex package as 'caret' is trivial with Galaaz.
1166
1166
 
1167
1167
  A word of advice: the 'caret' package has lots of dependencies and installing
1168
1168
  it in a Linux system is a time consuming operation. Method 'R.install_and_loads'
1169
- will install the package if it is not already installed and can take a while.
1169
+ will install the package if it is not already installed (via **`R::Job`**: a child
1170
+ `Rscript`, so the bridge stays free) and can take a while.
1170
1171
 
1171
1172
  ````
1172
1173
  ```{include model}
@@ -1245,7 +1246,7 @@ puts model.test.head
1245
1246
  gKnit also allows developers to document and load files that are not in the same directory
1246
1247
  of the '.Rmd' file.
1247
1248
 
1248
- Here is an example of loading the 'find.rb' file from **JRuby** (via `$LOAD_PATH`). In this example, relative
1249
+ Here is an example of loading the 'find.rb' file from Ruby (via `$LOAD_PATH`). In this example, relative
1249
1250
  is set to FALSE, so Ruby will look for the file in its $LOAD\_PATH, and the user does not
1250
1251
  need to know its directory.
1251
1252
 
@@ -1360,9 +1361,9 @@ the Yaml header to generate this blog in PDF format instead of HTML:
1360
1361
 
1361
1362
  ```
1362
1363
  ---
1363
- title: "gKnit - Ruby and R Knitting with Galaaz (JRuby + GNU R)"
1364
+ title: "gKnit - Ruby and R Knitting with Galaaz (JRuby or CRuby + GNU R)"
1364
1365
  author: "Rodrigo Botafogo"
1365
- tags: [Galaaz, Ruby, R, JRuby, "GNU R", knitr, gknit]
1366
+ tags: [Galaaz, Ruby, R, JRuby, CRuby, "GNU R", knitr, gknit]
1366
1367
  date: "29 October 2018"
1367
1368
  output:
1368
1369
  pdf\_document:
@@ -1385,13 +1386,13 @@ be imported from files in the system. Ideally, in reproducible research, all th
1385
1386
  needed to rebuild a report should be easily packed together (in the same zipped directory)
1386
1387
  and distributed to peers for reexecution.
1387
1388
 
1388
- **Galaaz 2.0** pairs **JRuby** with **GNU R**: you keep the full CRAN/Bioconductor world in R while
1389
+ **Galaaz 2.0** pairs **JRuby or CRuby** with **GNU R**: you keep the full CRAN/Bioconductor world in R while
1389
1390
  writing orchestration, reuse, and application code in Ruby. The effort to wrap Ruby over R (Galaaz)
1390
1391
  and to wrap Knitr as gKnit was tiny compared to reimplementing R’s ecosystem in Ruby—much like
1391
1392
  Python’s investment in NumPy and Pandas, which no Ruby project is likely to duplicate.
1392
1393
 
1393
1394
  An **earlier** prototype used Oracle’s **GraalVM** and Truffle interop; the **current** stack is
1394
- deliberately **standard GNU R** plus the Galaaz **bridge**, documented in the project manual.
1395
+ deliberately **standard GNU R** plus the Galaaz **bridge** on JRuby or CRuby, documented in the project manual.
1395
1396
 
1396
1397
  More interesting than wrapping the R libraries with Ruby, is that Ruby adds
1397
1398
  value to R, by allowing developers to use powerful and modern constructs for code reuse that
@@ -1403,7 +1404,7 @@ readability.
1403
1404
 
1404
1405
  ## Prerequisites (Galaaz 2.0)
1405
1406
 
1406
- * **JRuby** and a compatible **JDK**
1407
+ * **JRuby** and a compatible **JDK**, *or* **CRuby 3.3+**
1407
1408
  * **GNU R** on your `PATH`
1408
1409
 
1409
1410
  The following R packages will be automatically installed when necessary, but could be installed prior
@@ -1423,7 +1424,7 @@ the usual build tools are typically enough. On macOS, Xcode command-line tools
1423
1424
  ## Usage
1424
1425
 
1425
1426
  * **`bin/gknit`** \<filename\> (from the Galaaz repo or your install layout); use **`--output_format all`** for HTML and PDF together.
1426
- * Run Ruby with **`bin/galaaz-jruby`** when you need the same JVM flags as the project (see the manual).
1427
+ * Run Ruby with **`bin/galaaz-ruby`** (either engine) or **`bin/galaaz-jruby`** when you need JRuby JVM flags (see the manual).
1427
1428
 
1428
1429
 
1429
1430
  # References
@@ -2,7 +2,7 @@
2
2
  title: "Galaaz Manual"
3
3
  subtitle: "R-on-Rails: GNU R meets Ruby for the web"
4
4
  author: "Rodrigo Botafogo"
5
- tags: [Galaaz, "R-on-Rails", Ruby, Rails, JRuby, R, "GNU R", ggplot2, knitr, dplyr, Bioconductor, Arrow]
5
+ tags: [Galaaz, "R-on-Rails", Ruby, Rails, JRuby, CRuby, R, "GNU R", ggplot2, knitr, dplyr, Bioconductor, Arrow]
6
6
  date: "2026"
7
7
  bibliography: "../../examples/Bibliography/stats.bib"
8
8
  output:
@@ -66,18 +66,20 @@ of libraries for data analysis.
66
66
  Until recently, there was no real perspective for Ruby to bridge this gap.
67
67
  Implementing a complete scientific computing infrastructure would take too long.
68
68
 
69
- **Galaaz 2.0** couples **JRuby** (Ruby on the JVM) with **GNU R**—the same R you use for
70
- CRAN and Bioconductor. Ruby and R run in **separate processes**; the **Galaaz bridge**
71
- sends requests to R and returns results to Ruby. From your point of view you still write
72
- Ruby: `R.c(...)`, `R.library('ggplot2')`, `~R[:mtcars]`, and dplyr-style chains on R objects.
73
- You do not need to learn R syntax to get a lot done, though reading R documentation for
74
- individual packages remains useful.
69
+ **Galaaz 2.0** supports **JRuby** and **CRuby** equally for the same NewBridge protocol.
70
+ Pick the Ruby that fits your app: JRuby when you want real multithreading for web and I/O;
71
+ CRuby when you prefer MRI. R remains the same **GNU R** you use interactively—including
72
+ compiled extensions and Bioconductor. Ruby and R run in **separate processes**; the
73
+ **Galaaz bridge** sends requests to R and returns results to Ruby. From your point of view
74
+ you still write Ruby: `R.c(...)`, `R.library('ggplot2')`, `~R[:mtcars]`, and dplyr-style
75
+ chains on R objects. You do not need to learn R syntax to get a lot done, though reading R
76
+ documentation for individual packages remains useful.
75
77
 
76
78
  Earlier experiments with Galaaz used Oracle’s **GraalVM** with TruffleRuby and FastR so that
77
79
  Ruby and R could share one runtime. That path is no longer the focus: **standard GNU R**
78
80
  gives full compatibility with the R package ecosystem (including compiled extensions and
79
- Bioconductor) while JRuby gives a mature Ruby with **real multithreading** for application
80
- and I/O code.
81
+ Bioconductor) while either Ruby engine talks to R over NewBridge (JRuby for mature
82
+ multithreading on the application side; CRuby when you prefer MRI).
81
83
 
82
84
  The bridge handles **communication and typing** between the two worlds; large tables can
83
85
  also flow through **Apache Arrow** on the R side when you use the optional helpers described
@@ -241,14 +243,16 @@ For day-to-day **2.0** use, rely on **`bin/galaaz-ruby`** (or **`bin/galaaz-jrub
241
243
  Typical development and CI targets:
242
244
 
243
245
  * **Linux** — recent Ubuntu LTS or comparable distributions (x86_64).
244
- * **macOS** — recent releases with JRuby and GNU R available.
246
+ * **macOS** — recent releases with JRuby or CRuby and GNU R available.
245
247
  * **Windows** — use **WSL2** (same Linux stack as above); native Windows is not the primary target.
246
248
 
247
249
  The native **gatekeeper** component under `ext/new_bridge` is built with `make` and a C++ toolchain; see the project `README` if compilation fails on your platform.
248
250
 
249
251
  # Dependencies
250
252
 
251
- * **JRuby** — Galaaz 2.0 requires JRuby (tested with **10.1.1.0**) and a matching **JDK** (tested with **Java 21**). MRI Ruby is not supported.
253
+ * **Ruby** — **JRuby** or **CRuby** (both supported for NewBridge). Tested with **JRuby 10.1.1.0**
254
+ (+ **JDK 21**) and **CRuby 3.3.12**. Use `bin/galaaz-ruby` (honors `GALAAZ_RUBY`) or plain
255
+ `gem install galaaz` under the Ruby you choose.
252
256
  * **GNU R** — `R` and `Rscript` on your `PATH` (tested with **4.3.3**), plus a C++ toolchain (`g++`, `make`) and the **Rcpp** package to compile the gatekeeper.
253
257
  * **galaaz gem** — runtime dependency `msgpack` is pulled in by `gem install`.
254
258
  * Optional: **Docker** — if you run R in a container (common on WSL2); see bootstrap below.
@@ -258,29 +262,40 @@ The native **gatekeeper** component under `ext/new_bridge` is built with `make`
258
262
 
259
263
  The supported install is **`gem install` + compile the gatekeeper**. You do not need a git clone.
260
264
 
261
- 1. Install **JRuby**, a compatible **JDK**, and **GNU R** (with `Rscript` and a C++ compiler).
265
+ 1. Install **JRuby** (and a compatible **JDK**) **or CRuby 3.3+**, plus **GNU R** (with `Rscript` and a C++ compiler).
262
266
  2. In R, install **Rcpp**: `install.packages("Rcpp")`.
263
- 3. Install the gem: `jruby -S gem install galaaz`
267
+ 3. Install the gem: `jruby -S gem install galaaz` (or `gem install galaaz` under CRuby).
264
268
  4. Compile the native gatekeeper from the installed gem:
265
269
 
266
270
  ```
267
- gem_dir="$(jruby -e "puts Gem::Specification.find_by_name('galaaz').full_gem_path")"
271
+ gem_dir="$(ruby -e "puts Gem::Specification.find_by_name('galaaz').full_gem_path")"
272
+ # under JRuby: gem_dir="$(jruby -e "puts Gem::Specification.find_by_name('galaaz').full_gem_path")"
268
273
  make -C "${gem_dir}/ext/new_bridge" all
269
274
  ```
270
275
 
271
- 5. Ensure **`R`** starts GNU R and can install packages (network access to CRAN when you first call `R.install_and_loads`). For **Apache Arrow** on Java 9+, pass `-J--add-opens=java.base/java.nio=ALL-UNNAMED` to JRuby (from a checkout, `bin/galaaz-jruby` does this).
276
+ 5. Ensure **`R`** starts GNU R and can install packages (network access to CRAN when you first call `R.install_and_loads`). For **Apache Arrow** on **JRuby** (Java 9+), the child JVM needs `--add-opens=java.base/java.nio=ALL-UNNAMED` via **`JAVA_OPTS`** (from a checkout, `bin/galaaz-jruby` and `mise.toml` set this; a leading `jruby -J... -S bundle exec` does **not** pass `-J` to rspec). On **CRuby**, install Apache Arrow GLib (`libarrow-glib-dev` from the [Apache Arrow APT](https://arrow.apache.org/install/) repo) and `gem install red-arrow` matching `pkg-config --modversion arrow-glib`. Do not install the unrelated Rubygems package named `arrow`.
272
277
 
273
278
  For **gKnit**, **knitr**, **rmarkdown**, and LaTeX (PDF output), install the corresponding R packages, **Pandoc**, and a TeX distribution if you need PDF; the repository includes helpers such as **`bin/install-tinytex`** where appropriate.
274
279
 
275
- A **table of all `bin/` scripts** (bootstrap, JRuby wrapper, gstudio, gknit, test runners, and which ones are legacy) is in the section **Command-line tools (`bin/`)** earlier in this manual.
280
+ A **table of all `bin/` scripts** (bootstrap, Ruby launcher, gstudio, gknit, test runners, and which ones are legacy) is in the section **Command-line tools (`bin/`)** earlier in this manual.
276
281
 
277
282
  ### From a repository checkout (contributors)
278
283
 
279
- 1. Install **bundler** if needed, then run **`jruby -S bundle install`** in the repository root.
284
+ 1. Install **bundler** if needed, then run **`bundle install`** with your chosen Ruby
285
+ (`jruby -S bundle install` or CRuby `bundle install`) in the repository root.
280
286
  2. Build the bridge native code: **`make -C ext/new_bridge all`** (or **`rake compile_gatekeeper`**).
281
- 3. Run scripts with **`bin/galaaz-jruby`** (sources **`bin/galaaz_jruby_env.inc.sh`** and adds **`-I lib`**).
282
-
283
- A **gstudio** try image (JRuby + R + Galaaz already installed) is **`docker run --rm -it ghcr.io/rbotafogo/galaaz-try:gstudio`** (or **`./docker/try-gstudio/run.sh`** from a checkout). Maintainers can prove a RubyGems install on a throwaway Ubuntu machine (no repo inside the container) with **`./docker/cold-install/run.sh published-specs`**.
287
+ 3. Run scripts with **`bin/galaaz-ruby`** (uses `ruby` on `PATH`; set **`GALAAZ_RUBY=jruby`** or
288
+ **`GALAAZ_RUBY=ruby`** to force an engine). Spec runners: **`bin/run_rspec`** /
289
+ **`bin/run_all_rspec`** (same `GALAAZ_RUBY` rule). **`bin/galaaz-jruby`** remains a thin
290
+ wrapper that forces JRuby.
291
+
292
+ A **gstudio** try image with Galaaz already installed is available for both engines:
293
+ **JRuby** — `docker run --rm -it ghcr.io/rbotafogo/galaaz-try:gstudio` (or
294
+ `./docker/try-gstudio/run.sh` from a checkout); **CRuby** —
295
+ `docker run --rm -it ghcr.io/rbotafogo/galaaz-try:cruby` (or `./docker/try-cruby/run.sh`).
296
+ Maintainers can prove a RubyGems install on a throwaway Ubuntu machine (no repo inside
297
+ the container) with `./docker/cold-install/run.sh published-specs` (JRuby) or
298
+ `./docker/cold-install-cruby/run.sh published-specs` (CRuby).
284
299
 
285
300
  ## Windows + WSL2 (optional: Docker / R in a container)
286
301
 
@@ -315,8 +330,15 @@ WSL integration is enabled for the distro where Galaaz is installed.
315
330
  ```{ruby simple_example}
316
331
  vec = R.c(1, 2, 3, 4)
317
332
  puts vec
333
+
334
+ # R.foo(...) calls an R *function*. Datasets are objects — fetch with ~:
335
+ df = ~R[:mtcars]
336
+ puts R.summary(df)
318
337
  ```
319
-
338
+
339
+ (`R.mtcars` is wrong: it becomes `mtcars()` in R and fails. With
340
+ `using Galaaz::SymbolDSL`, the short form `~:mtcars` also works.)
341
+
320
342
  * Run all specs
321
343
 
322
344
  > galaaz specs:all
@@ -371,8 +393,13 @@ driver). For concurrency tests on the bridge itself, see `specs/bridge_concurren
371
393
 
372
394
  ## Long-running R calls and a completion block
373
395
 
374
- For R work that can take a long time, the bridge can avoid a Ruby-side **wait timeout** by
375
- scheduling the call and resuming in a **block** when the `RET` arrives.
396
+ For R work that can take a long time **on the bridge**, the bridge can avoid a Ruby-side
397
+ **wait timeout** by scheduling the call and resuming in a **block** when the `RET` arrives.
398
+
399
+ **Important distinction:** this keeps the **same** GNU R process busy. Other sync
400
+ `eval_r` / gknit chunks still wait on that R. For CRAN installs and other work that must
401
+ **not** monopolize the bridge (or that can OOM a small VM if abandoned mid-compile), use
402
+ **`R::Job`** in the next section instead.
376
403
 
377
404
  - **`R.eval_r_async(code, timeout: nil) { |result| ... }`** — string eval; on success, `result.value`
378
405
  is the same formatted string as **`R.eval_r`** (use `timeout: nil` for no Ruby-side limit).
@@ -420,6 +447,63 @@ the outcome to storage, and notify the client (poll, WebSocket, Turbo Stream, et
420
447
  Ruby pattern above is only to show **when** the result exists (inside the block, or after data
421
448
  written there is observed elsewhere). Runnable specs live in **`new_bridge_specs/eval_r_async_spec.rb`**.
422
449
 
450
+ ## Background R jobs (`R::Job`)
451
+
452
+ `R::Async` / `R.eval_r_async` free the **Ruby** thread while the **same** bridge R process
453
+ runs your code. That is enough for Rails-style “don’t block the request thread,” but not
454
+ enough for heavy `install.packages` or multi-minute model fits: the gatekeeper R is still
455
+ busy, other chunks time out, and abandoning the wait can leave compile work burning RAM.
456
+
457
+ **`R::Job`** runs that work in a **child `Rscript` process**. The bridge stays free. Logs and
458
+ metadata live under `~/.local/share/galaaz/jobs/` (override with `GALAAZ_JOBS_DIR`).
459
+
460
+ ### Package installs
461
+
462
+ `R.install_and_loads` / `R.install_rlibs` use `R::Job.install` and **await until the child
463
+ finishes** (default: no wall-clock limit). Optional limit: `GALAAZ_INSTALL_TIMEOUT_SEC` or
464
+ `install_timeout_sec:`. On timeout the child process group is killed so leftover
465
+ `make`/`gcc` cannot OOM the shell. Stale `00LOCK-*` dirs are cleared before the next install.
466
+ Only one install runs at a time (`install.lock`).
467
+
468
+ ```{ruby r_job_install_note, eval=FALSE}
469
+ # May take a long time the first time (e.g. caret); the bridge is not used for compile.
470
+ R.install_and_loads 'caret'
471
+ ```
472
+
473
+ ### Long arbitrary R (`eval` / `script`)
474
+
475
+ Prefer the **block** form (like `File.open`): await the child, yield the job, return the
476
+ block’s value. Without a block, the methods still await by default and return the `Job`.
477
+
478
+ ```{ruby r_job_eval_block}
479
+ coef = R::Job.eval(<<~R) { |job| job.load_rds }
480
+ fit <- lm(mpg ~ wt, data = mtcars)
481
+ saveRDS(unname(coef(fit)), result_path)
482
+ R
483
+ puts coef
484
+ ```
485
+
486
+ ```{ruby r_job_eval_no_block, eval=FALSE}
487
+ # Without a block: awaits (wait: true is the default) and returns the Job
488
+ job = R::Job.eval(code)
489
+ job = R::Job.eval(code, wait: false) # start only; call job.wait later
490
+
491
+ # Script file; trailing args → commandArgs(trailingOnly=TRUE) in the child
492
+ res = R::Job.script('train.R', '5') { |job| job.load_rds }
493
+ ```
494
+
495
+ In the child: `setwd(job.dir)`, `.libPaths` includes the Galaaz user library,
496
+ `GALAAZ_JOB_DIR` is set, and **`result_path`** defaults to
497
+ `file.path(GALAAZ_JOB_DIR, "result.rds")`. Persist with `saveRDS(..., result_path)`, then
498
+ load on the bridge with **`job.load_rds`** (short sync `readRDS` → a normal Galaaz R object).
499
+
500
+ ### Choosing async vs Job
501
+
502
+ | Need | Use |
503
+ |------|-----|
504
+ | Don’t freeze a Ruby thread; short/medium R on the bridge is OK | `R::Async` / `R.eval_r_async` |
505
+ | Install CRAN packages, or long R that must not block the bridge | `R::Job` / `R.install_and_loads` |
506
+
423
507
  ## Galaaz + Rails (R-on-Rails) integration baseline
424
508
 
425
509
  This is the practical **R-on-Rails** starter: an R scientist’s analysis behind a small Rails
@@ -633,9 +717,9 @@ puts vec.map { |x| x + 2 }
633
717
 
634
718
  This manual has been formatted using gKnit. gKnit uses knitr and R Markdown to knit
635
719
  a document in Ruby or R and output it in any of the available formats for R Markdown.
636
- gKnit runs with **JRuby**, **GNU R**, and Galaaz. In gKnit, Ruby variables are persisted between
637
- chunks, making it an ideal solution for literate programming. Also, since it is based
638
- on Galaaz, Ruby chunks can have access to R variables and combining Ruby with R in one
720
+ gKnit runs with **JRuby or CRuby**, **GNU R**, and Galaaz. In gKnit, Ruby variables are persisted between
721
+ chunks, making it an ideal solution for literate programming. Also, since it is based
722
+ on Galaaz, Ruby chunks can have access to R variables and combining Ruby with R in one
639
723
  document is natural.
640
724
 
641
725
  The idea of "literate programming" was first introduced by Donald Knuth in the
@@ -1163,7 +1247,8 @@ using even a very complex package as 'caret' is trivial with Galaaz.
1163
1247
 
1164
1248
  A word of advice: the 'caret' package has lots of dependencies and installing
1165
1249
  it in a Linux system is a time consuming operation. Method 'R.install_and_loads'
1166
- will install the package if it is not already installed and can take a while.
1250
+ will install the package if it is not already installed (via **`R::Job`**: a child
1251
+ `Rscript`, so the bridge stays free) and can take a while.
1167
1252
 
1168
1253
  ````
1169
1254
  ```{include model}`r ''`
@@ -2008,7 +2093,8 @@ a real **Arrow `Table` inside GNU R**. From there you use R’s **`arrow`** and
2008
2093
  as usual: **`group_by`** on the Arrow table, **`summarise`** for aggregates, then **`collect()`** to
2009
2094
  materialize a tibble when you need in-memory R rows.
2010
2095
 
2011
- That pattern matches production use: **JRuby threads** (or sequential code) assemble many rows in
2096
+ That pattern matches production use: **JRuby threads** or a **multi-process CRuby** app
2097
+ (or sequential code) assemble many rows in
2012
2098
  Ruby; you pay **one** bridge-heavy handoff to R; **dplyr** runs vectorised work on the Arrow table
2013
2099
  in R.
2014
2100
 
@@ -2112,10 +2198,11 @@ pulls in several dependencies; the first install can take several minutes.
2112
2198
  ## Example: DESeq2 on the airway dataset
2113
2199
 
2114
2200
  The script **`examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb`** is the canonical
2115
- version in the repository. Run it from the **Galaaz repository root** with JRuby, for example:
2201
+ version in the repository. Run it from the **Galaaz repository root** with either engine, for example:
2116
2202
 
2117
2203
  ```text
2118
- bin/galaaz-jruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
2204
+ bin/galaaz-ruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
2205
+ # or: bin/galaaz-jruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
2119
2206
  ```
2120
2207
 
2121
2208
  The workflow in Ruby mirrors a standard DESeq2 vignette:
@@ -2143,7 +2230,7 @@ manual is knitted, because **DESeq2** is heavy and may be absent on the build ma
2143
2230
 
2144
2231
  ```{ruby deseq2_airway_full_listing, eval=FALSE}
2145
2232
  # Canonical script: examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
2146
- # Run: bin/galaaz-jruby examples/.../deseq2_airway_galaaz.rb (repo root).
2233
+ # Run: bin/galaaz-ruby examples/.../deseq2_airway_galaaz.rb (repo root).
2147
2234
 
2148
2235
  require 'galaaz'
2149
2236
 
@@ -2665,8 +2752,8 @@ arguments.
2665
2752
 
2666
2753
  * Fork it
2667
2754
  * Create your feature branch (`git checkout -b my-new-feature`)
2668
- * Write tests — use **`bin/run_rspec`** or **`bin/run_all_rspec`** with **JRuby** so JVM flags and
2669
- the load path match **`docs/testing.md`**
2755
+ * Write tests — use **`bin/run_rspec`** or **`bin/run_all_rspec`** (JRuby or CRuby via
2756
+ **`GALAAZ_RUBY`**) so the load path matches **`docs/testing.md`**
2670
2757
  * Commit your changes (`git commit -am 'Add some feature'`)
2671
2758
  * Push to the branch (`git push origin my-new-feature`)
2672
2759
  * Open a pull request
@@ -2,7 +2,7 @@
2
2
  title: "Galaaz Manual"
3
3
  subtitle: "R-on-Rails: GNU R meets Ruby for the web"
4
4
  author: "Rodrigo Botafogo"
5
- tags: [Galaaz, "R-on-Rails", Ruby, Rails, JRuby, R, "GNU R", ggplot2, knitr, dplyr, Bioconductor, Arrow]
5
+ tags: [Galaaz, "R-on-Rails", Ruby, Rails, JRuby, CRuby, R, "GNU R", ggplot2, knitr, dplyr, Bioconductor, Arrow]
6
6
  date: "2026"
7
7
  bibliography: "../../examples/Bibliography/stats.bib"
8
8
  output:
@@ -73,7 +73,7 @@ rewrite in another stack. **R-on-Rails** means:
73
73
  1. **Keep your science in R** — packages, formulas, plots, Bioconductor, the same engine as RStudio.
74
74
  2. **Learn enough Ruby/Rails** — routes, controllers, views, jobs, auth—not a second statistics career.
75
75
  3. **Call R from the app** — Galaaz loads R behind the scenes; long jobs can complete asynchronously
76
- while Rails stays responsive (see later sections on the bridge and `R::Async`).
76
+ while Rails stays responsive (see later sections on the bridge, `R::Async`, and `R::Job`).
77
77
  4. **Ship alone when you need to** — one developer can own both the analysis and the product UI,
78
78
  without waiting for a separate “stats engineer” and “Rails engineer.”
79
79
 
@@ -358,7 +358,13 @@ A **table of all `bin/` scripts** (bootstrap, Ruby launcher, gstudio, gknit, tes
358
358
  **`bin/run_all_rspec`** (same `GALAAZ_RUBY` rule). **`bin/galaaz-jruby`** remains a thin
359
359
  wrapper that forces JRuby.
360
360
 
361
- A **gstudio** try image (JRuby + R + Galaaz already installed) is **`docker run --rm -it ghcr.io/rbotafogo/galaaz-try:gstudio`** (or **`./docker/try-gstudio/run.sh`** from a checkout). Maintainers can prove a RubyGems install on a throwaway Ubuntu machine (no repo inside the container) with **`./docker/cold-install/run.sh published-specs`** (JRuby) or **`./docker/cold-install-cruby/run.sh published-specs`** (CRuby).
361
+ A **gstudio** try image with Galaaz already installed is available for both engines:
362
+ **JRuby** — `docker run --rm -it ghcr.io/rbotafogo/galaaz-try:gstudio` (or
363
+ `./docker/try-gstudio/run.sh` from a checkout); **CRuby** —
364
+ `docker run --rm -it ghcr.io/rbotafogo/galaaz-try:cruby` (or `./docker/try-cruby/run.sh`).
365
+ Maintainers can prove a RubyGems install on a throwaway Ubuntu machine (no repo inside
366
+ the container) with `./docker/cold-install/run.sh published-specs` (JRuby) or
367
+ `./docker/cold-install-cruby/run.sh published-specs` (CRuby).
362
368
 
363
369
  ## Windows + WSL2 (optional: Docker / R in a container)
364
370
 
@@ -394,11 +400,18 @@ WSL integration is enabled for the distro where Galaaz is installed.
394
400
  ``` ruby
395
401
  vec = R.c(1, 2, 3, 4)
396
402
  puts vec
403
+
404
+ # R.foo(...) calls an R *function*. Datasets are objects — fetch with ~:
405
+ df = ~R[:mtcars]
406
+ puts R.summary(df)
397
407
  ```
398
408
 
399
409
  ```
400
410
  ## [1] 1 2 3 4
401
411
  ```
412
+
413
+ (`R.mtcars` is wrong: it becomes `mtcars()` in R and fails. With
414
+ `using Galaaz::SymbolDSL`, the short form `~:mtcars` also works.)
402
415
 
403
416
  * Run all specs
404
417
 
@@ -456,8 +469,13 @@ driver). For concurrency tests on the bridge itself, see `specs/bridge_concurren
456
469
 
457
470
  ## Long-running R calls and a completion block
458
471
 
459
- For R work that can take a long time, the bridge can avoid a Ruby-side **wait timeout** by
460
- scheduling the call and resuming in a **block** when the `RET` arrives.
472
+ For R work that can take a long time **on the bridge**, the bridge can avoid a Ruby-side
473
+ **wait timeout** by scheduling the call and resuming in a **block** when the `RET` arrives.
474
+
475
+ **Important distinction:** this keeps the **same** GNU R process busy. Other sync
476
+ `eval_r` / gknit chunks still wait on that R. For CRAN installs and other work that must
477
+ **not** monopolize the bridge (or that can OOM a small VM if abandoned mid-compile), use
478
+ **`R::Job`** in the next section instead.
461
479
 
462
480
  - **`R.eval_r_async(code, timeout: nil) { |result| ... }`** — string eval; on success, `result.value`
463
481
  is the same formatted string as **`R.eval_r`** (use `timeout: nil` for no Ruby-side limit).
@@ -514,6 +532,67 @@ the outcome to storage, and notify the client (poll, WebSocket, Turbo Stream, et
514
532
  Ruby pattern above is only to show **when** the result exists (inside the block, or after data
515
533
  written there is observed elsewhere). Runnable specs live in **`new_bridge_specs/eval_r_async_spec.rb`**.
516
534
 
535
+ ## Background R jobs (`R::Job`)
536
+
537
+ `R::Async` / `R.eval_r_async` free the **Ruby** thread while the **same** bridge R process
538
+ runs your code. That is enough for Rails-style “don’t block the request thread,” but not
539
+ enough for heavy `install.packages` or multi-minute model fits: the gatekeeper R is still
540
+ busy, other chunks time out, and abandoning the wait can leave compile work burning RAM.
541
+
542
+ **`R::Job`** runs that work in a **child `Rscript` process**. The bridge stays free. Logs and
543
+ metadata live under `~/.local/share/galaaz/jobs/` (override with `GALAAZ_JOBS_DIR`).
544
+
545
+ ### Package installs
546
+
547
+ `R.install_and_loads` / `R.install_rlibs` use `R::Job.install` and **await until the child
548
+ finishes** (default: no wall-clock limit). Optional limit: `GALAAZ_INSTALL_TIMEOUT_SEC` or
549
+ `install_timeout_sec:`. On timeout the child process group is killed so leftover
550
+ `make`/`gcc` cannot OOM the shell. Stale `00LOCK-*` dirs are cleared before the next install.
551
+ Only one install runs at a time (`install.lock`).
552
+
553
+ ``` ruby
554
+ # May take a long time the first time (e.g. caret); the bridge is not used for compile.
555
+ R.install_and_loads 'caret'
556
+ ```
557
+
558
+ ### Long arbitrary R (`eval` / `script`)
559
+
560
+ Prefer the **block** form (like `File.open`): await the child, yield the job, return the
561
+ block’s value. Without a block, the methods still await by default and return the `Job`.
562
+
563
+ ``` ruby
564
+ coef = R::Job.eval(<<~R) { |job| job.load_rds }
565
+ fit <- lm(mpg ~ wt, data = mtcars)
566
+ saveRDS(unname(coef(fit)), result_path)
567
+ R
568
+ puts coef
569
+ ```
570
+
571
+ ```
572
+ ## [1] 37.285126 -5.344472
573
+ ```
574
+
575
+ ``` ruby
576
+ # Without a block: awaits (wait: true is the default) and returns the Job
577
+ job = R::Job.eval(code)
578
+ job = R::Job.eval(code, wait: false) # start only; call job.wait later
579
+
580
+ # Script file; trailing args → commandArgs(trailingOnly=TRUE) in the child
581
+ res = R::Job.script('train.R', '5') { |job| job.load_rds }
582
+ ```
583
+
584
+ In the child: `setwd(job.dir)`, `.libPaths` includes the Galaaz user library,
585
+ `GALAAZ_JOB_DIR` is set, and **`result_path`** defaults to
586
+ `file.path(GALAAZ_JOB_DIR, "result.rds")`. Persist with `saveRDS(..., result_path)`, then
587
+ load on the bridge with **`job.load_rds`** (short sync `readRDS` → a normal Galaaz R object).
588
+
589
+ ### Choosing async vs Job
590
+
591
+ | Need | Use |
592
+ |------|-----|
593
+ | Don’t freeze a Ruby thread; short/medium R on the bridge is OK | `R::Async` / `R.eval_r_async` |
594
+ | Install CRAN packages, or long R that must not block the bridge | `R::Job` / `R.install_and_loads` |
595
+
517
596
  ## Galaaz + Rails (R-on-Rails) integration baseline
518
597
 
519
598
  This is the practical **R-on-Rails** starter: an R scientist’s analysis behind a small Rails
@@ -1873,7 +1952,8 @@ using even a very complex package as 'caret' is trivial with Galaaz.
1873
1952
 
1874
1953
  A word of advice: the 'caret' package has lots of dependencies and installing
1875
1954
  it in a Linux system is a time consuming operation. Method 'R.install_and_loads'
1876
- will install the package if it is not already installed and can take a while.
1955
+ will install the package if it is not already installed (via **`R::Job`**: a child
1956
+ `Rscript`, so the bridge stays free) and can take a while.
1877
1957
 
1878
1958
  ````
1879
1959
  ```{include model}
@@ -4194,10 +4274,11 @@ pulls in several dependencies; the first install can take several minutes.
4194
4274
  ## Example: DESeq2 on the airway dataset
4195
4275
 
4196
4276
  The script **`examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb`** is the canonical
4197
- version in the repository. Run it from the **Galaaz repository root** with JRuby, for example:
4277
+ version in the repository. Run it from the **Galaaz repository root** with either engine, for example:
4198
4278
 
4199
4279
  ```text
4200
- bin/galaaz-jruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
4280
+ bin/galaaz-ruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
4281
+ # or: bin/galaaz-jruby examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
4201
4282
  ```
4202
4283
 
4203
4284
  The workflow in Ruby mirrors a standard DESeq2 vignette:
@@ -4226,7 +4307,7 @@ manual is knitted, because **DESeq2** is heavy and may be absent on the build ma
4226
4307
 
4227
4308
  ``` ruby
4228
4309
  # Canonical script: examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb
4229
- # Run: bin/galaaz-jruby examples/.../deseq2_airway_galaaz.rb (repo root).
4310
+ # Run: bin/galaaz-ruby examples/.../deseq2_airway_galaaz.rb (repo root).
4230
4311
 
4231
4312
  require 'galaaz'
4232
4313
 
@@ -3,7 +3,7 @@ title: "Extending R with classes, modules, procs, lambdas, oh my!"
3
3
  author:
4
4
  - "Rodrigo Botafogo"
5
5
  - "Daniel Mossé - University of Pittsburgh"
6
- tags: [Tech, Data Science, Ruby, R, JRuby, "GNU R", Galaaz]
6
+ tags: [Tech, Data Science, Ruby, R, JRuby, CRuby, "GNU R", Galaaz]
7
7
  date: "November 19th, 2018 (narrative updated for Galaaz 2.0, 2026)"
8
8
  output:
9
9
  html_document:
@@ -34,10 +34,10 @@ or similar solutions in Python,
34
34
  such as [PypeR](https://pypi.python.org/pypi/PypeR/1.1.0), [rpy2](http://rpy2.bitbucket.org/)
35
35
  and other similar solutions.
36
36
 
37
- **Galaaz 2.0** runs on **[JRuby](https://www.jruby.org/)** and drives **GNU R** through a **bridge**,
38
- so Ruby code can create and manipulate R objects and call R functions while staying idiomatic Ruby.
39
- An earlier prototype used Oracle’s **GraalVM** with **TruffleRuby** and **FastR**; that stack is
40
- historical and is **not** what current Galaaz targets.
37
+ **Galaaz 2.0** supports **[JRuby](https://www.jruby.org/)** or **CRuby** equally and drives
38
+ **GNU R** through a **bridge**, so Ruby code can create and manipulate R objects and call R
39
+ functions while staying idiomatic Ruby. An earlier prototype used Oracle’s **GraalVM** with
40
+ **TruffleRuby** and **FastR**; that stack is historical and is **not** what current Galaaz targets.
41
41
 
42
42
 
43
43
  # Bases of Object Programming
@@ -1673,7 +1673,7 @@ For those interested in Ruby and science, we recommend:
1673
1673
 
1674
1674
  * http://sciruby.com/
1675
1675
 
1676
- **Galaaz 2.0** runs on **JRuby** and talks to **GNU R** through the bridge described in this
1676
+ **Galaaz 2.0** runs on **JRuby or CRuby** and talks to **GNU R** through the bridge described in this
1677
1677
  series — the same integration model used throughout the examples above.
1678
1678
 
1679
1679
  # Callbacks and R calling into Ruby
@@ -1695,7 +1695,7 @@ path in Galaaz 2.0.
1695
1695
 
1696
1696
  # Conclusions II
1697
1697
 
1698
- **JRuby + GNU R + Galaaz** gives a practical polyglot stack: idiomatic Ruby for structure and
1698
+ **JRuby or CRuby + GNU R + Galaaz** gives a practical polyglot stack: idiomatic Ruby for structure and
1699
1699
  libraries, GNU R for statistics and the CRAN/Bioconductor ecosystem, and Galaaz as the bridge
1700
1700
  between them. As always, choose the right tools for the job at hand — and when the job sits
1701
1701
  between an R-only workflow and a broader polyglot application, Galaaz is designed to connect those
data/blogs/oh_my/oh_my.md CHANGED
@@ -3,7 +3,7 @@ title: "Extending R with classes, modules, procs, lambdas, oh my!"
3
3
  author:
4
4
  - "Rodrigo Botafogo"
5
5
  - "Daniel Mossé - University of Pittsburgh"
6
- tags: [Tech, Data Science, Ruby, R, JRuby, "GNU R", Galaaz]
6
+ tags: [Tech, Data Science, Ruby, R, JRuby, CRuby, "GNU R", Galaaz]
7
7
  date: "November 19th, 2018 (narrative updated for Galaaz 2.0, 2026)"
8
8
  output:
9
9
  html_document:
@@ -32,10 +32,10 @@ or similar solutions in Python,
32
32
  such as [PypeR](https://pypi.python.org/pypi/PypeR/1.1.0), [rpy2](http://rpy2.bitbucket.org/)
33
33
  and other similar solutions.
34
34
 
35
- **Galaaz 2.0** runs on **[JRuby](https://www.jruby.org/)** and drives **GNU R** through a **bridge**,
36
- so Ruby code can create and manipulate R objects and call R functions while staying idiomatic Ruby.
37
- An earlier prototype used Oracle’s **GraalVM** with **TruffleRuby** and **FastR**; that stack is
38
- historical and is **not** what current Galaaz targets.
35
+ **Galaaz 2.0** supports **[JRuby](https://www.jruby.org/)** or **CRuby** equally and drives
36
+ **GNU R** through a **bridge**, so Ruby code can create and manipulate R objects and call R
37
+ functions while staying idiomatic Ruby. An earlier prototype used Oracle’s **GraalVM** with
38
+ **TruffleRuby** and **FastR**; that stack is historical and is **not** what current Galaaz targets.
39
39
 
40
40
 
41
41
  # Bases of Object Programming
@@ -2148,7 +2148,7 @@ For those interested in Ruby and science, we recommend:
2148
2148
 
2149
2149
  * http://sciruby.com/
2150
2150
 
2151
- **Galaaz 2.0** runs on **JRuby** and talks to **GNU R** through the bridge described in this
2151
+ **Galaaz 2.0** runs on **JRuby or CRuby** and talks to **GNU R** through the bridge described in this
2152
2152
  series — the same integration model used throughout the examples above.
2153
2153
 
2154
2154
  # Callbacks and R calling into Ruby
@@ -2170,7 +2170,7 @@ path in Galaaz 2.0.
2170
2170
 
2171
2171
  # Conclusions II
2172
2172
 
2173
- **JRuby + GNU R + Galaaz** gives a practical polyglot stack: idiomatic Ruby for structure and
2173
+ **JRuby or CRuby + GNU R + Galaaz** gives a practical polyglot stack: idiomatic Ruby for structure and
2174
2174
  libraries, GNU R for statistics and the CRAN/Bioconductor ecosystem, and Galaaz as the bridge
2175
2175
  between them. As always, choose the right tools for the job at hand — and when the job sits
2176
2176
  between an R-only workflow and a broader polyglot application, Galaaz is designed to connect those