galaaz 2.1.7 → 2.1.9

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.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/Rakefile +20 -2
  4. data/bin/check_gemfile_lock_version +46 -0
  5. data/bin/release_bump +26 -0
  6. data/blogs/README.md +4 -0
  7. data/blogs/galaaz_2_0/galaaz_2_0.Rmd +385 -0
  8. data/blogs/galaaz_2_0/galaaz_2_0.md +409 -0
  9. data/blogs/galaaz_2_0/galaaz_2_0.tex +756 -0
  10. data/blogs/galaaz_2_0/images/galaaz-header.png +0 -0
  11. data/blogs/galaaz_2_0/images/galaaz-lockup-stacked.png +0 -0
  12. data/blogs/galaaz_ggplot/galaaz_ggplot.Rmd +76 -58
  13. data/blogs/galaaz_ggplot/galaaz_ggplot.md +188 -161
  14. data/blogs/galaaz_ggplot/galaaz_ggplot.tex +185 -117
  15. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/midwest_rb.png +0 -0
  16. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/scatter_plot_rb.png +0 -0
  17. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/midwest_rb.png +0 -0
  18. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/scatter_plot_rb.png +0 -0
  19. data/blogs/galaaz_ggplot/images/galaaz-lockup-stacked.png +0 -0
  20. data/blogs/gknit/gknit.Rmd +48 -28
  21. data/blogs/gknit/gknit.md +60 -43
  22. data/blogs/gknit/gknit.tex +1409 -0
  23. data/blogs/gknit/gknit_files/figure-html/bubble-1.png +0 -0
  24. data/blogs/gknit/gknit_files/figure-html/diverging_bar.png +0 -0
  25. data/blogs/gknit/gknit_files/figure-latex/bubble-1.png +0 -0
  26. data/blogs/gknit/images/galaaz-lockup-stacked.png +0 -0
  27. data/blogs/manual/images/galaaz-lockup-stacked.png +0 -0
  28. data/blogs/manual/manual.Rmd +161 -71
  29. data/blogs/manual/manual.md +573 -482
  30. data/blogs/manual/manual.tex +1113 -517
  31. data/blogs/manual/manual_files/figure-html/bubble-1.png +0 -0
  32. data/blogs/manual/manual_files/figure-latex/bubble-1.png +0 -0
  33. data/blogs/manual/manual_files/figure-markdown_github/bubble-1.png +0 -0
  34. data/blogs/manual/manual_files/figure-markdown_github/diverging_bar.png +0 -0
  35. data/blogs/nse_dplyr/images/galaaz-lockup-stacked.png +0 -0
  36. data/blogs/nse_dplyr/nse_dplyr.Rmd +42 -8
  37. data/blogs/nse_dplyr/nse_dplyr.md +69 -50
  38. data/blogs/nse_dplyr/nse_dplyr.tex +1626 -0
  39. data/blogs/oh_my/images/galaaz-lockup-stacked.png +0 -0
  40. data/blogs/oh_my/oh_my.Rmd +206 -55
  41. data/blogs/oh_my/oh_my.md +247 -96
  42. data/blogs/oh_my/oh_my.tex +2038 -93
  43. data/blogs/r_on_rails_ledger/images/00_portfolio_page.png +0 -0
  44. data/blogs/r_on_rails_ledger/images/01_results_panel.png +0 -0
  45. data/blogs/r_on_rails_ledger/images/02_density_tail_risk.png +0 -0
  46. data/blogs/r_on_rails_ledger/images/03_mc_cone.png +0 -0
  47. data/blogs/r_on_rails_ledger/images/04_rolling_var.png +0 -0
  48. data/blogs/r_on_rails_ledger/images/galaaz-lockup-stacked.png +0 -0
  49. data/blogs/r_on_rails_ledger/r_on_rails_ledger.Rmd +354 -0
  50. data/blogs/r_on_rails_ledger/r_on_rails_ledger.md +365 -0
  51. data/blogs/r_on_rails_ledger/r_on_rails_ledger.tex +670 -0
  52. data/blogs/ruby_plot/images/galaaz-lockup-stacked.png +0 -0
  53. data/blogs/ruby_plot/ruby_plot.Rmd +56 -35
  54. data/blogs/ruby_plot/ruby_plot.md +63 -47
  55. data/blogs/ruby_plot/ruby_plot.tex +194 -125
  56. data/blogs/ruby_plot/ruby_plot_files/figure-html/dose_len.png +0 -0
  57. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_delivery.png +0 -0
  58. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_dose.png +0 -0
  59. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color.png +0 -0
  60. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color2.png +0 -0
  61. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_decorations.png +0 -0
  62. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_jitter.png +0 -0
  63. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_points.png +0 -0
  64. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_box_plot.png +0 -0
  65. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_violin_plot.png +0 -0
  66. data/blogs/ruby_plot/ruby_plot_files/figure-html/violin_with_jitter.png +0 -0
  67. data/blogs/ruby_plot/ruby_plot_files/figure-latex/dose_len.png +0 -0
  68. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_delivery.png +0 -0
  69. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_dose.png +0 -0
  70. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color.png +0 -0
  71. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color2.png +0 -0
  72. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_decorations.png +0 -0
  73. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
  74. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_points.png +0 -0
  75. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_box_plot.png +0 -0
  76. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
  77. data/blogs/ruby_plot/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
  78. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/dose_len.png +0 -0
  79. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facet_by_delivery.png +0 -0
  80. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facet_by_dose.png +0 -0
  81. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_by_delivery_color.png +0 -0
  82. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_by_delivery_color2.png +0 -0
  83. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_with_decorations.png +0 -0
  84. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
  85. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_with_points.png +0 -0
  86. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/final_box_plot.png +0 -0
  87. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
  88. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
  89. data/lib/galaaz/cli.rb +98 -13
  90. data/logos/icon-font/README.md +27 -0
  91. data/logos/icon-font/build_font.py +130 -0
  92. data/logos/icon-font/galaaz-mark.svg +34 -0
  93. data/script/omarchy/README.md +9 -2
  94. data/script/omarchy/fonts/galaaz.ttf +0 -0
  95. data/script/omarchy/galaaz-guide.sh +1 -1
  96. data/script/omarchy/install-galaaz.sh +8 -1
  97. data/script/omarchy/omarchy-menu.jsonc +21 -9
  98. data/sty/galaaz-header.png +0 -0
  99. data/sty/galaaz-headers-from-p3.tex +4 -0
  100. data/sty/galaaz.sty +76 -23
  101. data/version.rb +1 -1
  102. metadata +46 -7
  103. data/blogs/galaaz_ggplot/galaaz_ggplot.log +0 -754
  104. data/blogs/manual/manual.log +0 -1786
  105. data/blogs/nse_dplyr/nse_dplyr.log +0 -928
  106. data/blogs/oh_my/oh_my.log +0 -804
  107. data/blogs/ruby_plot/ruby_plot.log +0 -885
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8cee9e5a57aa8b29ef17b8b525511b422cf8c06d5d486addaa025c981045f34e
4
- data.tar.gz: 2e399270c32d42df217b966c5478434f28e9aa5844db4243ef440d07f162b097
3
+ metadata.gz: 3e591ab4464b4ef2d75313c91b9f22de54229f2e83ed9758d66de69758e6828e
4
+ data.tar.gz: a8978965526f22ba7abfc1f643c20516b2f962297e2254d0fc5105a96733c7f7
5
5
  SHA512:
6
- metadata.gz: 5d48ebe0a5e37459dd44237a9c0ceae8809718eb2f3396999ec6bb8123e524bf34f06e6da67467d8a0ef7b2955e99e5fc4099d14a46e56ef08e69cf25ca37c4e
7
- data.tar.gz: 5e461b9065b98c347ef94665ecc809717e715da2145ebaab3410c4897c7dbc42df04492a932ada657b2b5767f3074a88c687dea2530875642f69803541b83a6b
6
+ metadata.gz: 3dfe41a6abb29b76ff48d9282dbce82d450975fb96b6f84eb3d564b9fda75f4220e5a773f64f59a7b416749bdcb58ab58cd63b8928ce878160d6983134ae6c80
7
+ data.tar.gz: 3bfcbf82a7dacbfa2357532d6fb0db4c651acd8b98d8b231da1d07cb36c7e47d73c2947773752afe80a78dc0b1b8f5c660d310887cddffba98235de984a85491
data/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 2.1.9
6
+
7
+ ### Added
8
+
9
+ - `rake release:bump VERSION=x.y.z` (or `bin/release_bump x.y.z`) — rewrite
10
+ `version.rb` and refresh `Gemfile.lock` so CI frozen `bundle install` stays in
11
+ sync; `rake release:check` / CI job `Gemfile.lock matches version.rb` fail early
12
+ with that message instead of Bundler exit 16. `make_gem` / `publish_gem` depend
13
+ on `release:check`.
14
+ - Blogs: `galaaz_2_0` and `r_on_rails_ledger` (sources + rendered outputs); listed
15
+ in `galaaz blogs init` / `BLOG_NAMES`.
16
+ - Brand kit under `logos/` (incl. transparent masters). PDF headers via
17
+ `sty/galaaz.sty`: small logo left, upright page number + italic uppercase
18
+ section title right; optional `\galaazheaderfrompage` (shared
19
+ `sty/galaaz-headers-from-p3.tex` for title/TOC + splash). Main blogs share
20
+ HTML `before_body` lockup + PDF splash; `galaaz blogs init` / `add tex`
21
+ install sty header PNG and the from-p3 override.
22
+ - Omarchy menu brand mark: monochrome icon font `galaaz` (`U+E900`, source
23
+ `logos/icon-font/`) installed to `~/.local/share/fonts/galaaz/` by
24
+ `galaaz omarchy` / core installer — replaces the Ruby-on-Rails Nerd Font gem.
25
+
26
+ ### Fixed
27
+
28
+ - `galaaz add ledger`: run `rails tailwindcss:build` after seed so Propshaft finds
29
+ `tailwind.css` (builds dir is gitignored).
30
+ - Sync `Gemfile.lock` path gem with `version.rb` on every release bump (CI frozen
31
+ install).
32
+
33
+ ## 2.1.8
34
+
35
+ ### Fixed
36
+
37
+ - `galaaz add ledger`: always `bundle install` under `mise x` (honours app `.ruby-version`),
38
+ ensure the lockfile's bundler is installed, then `bundle update galaaz` and
39
+ `bundle exec` for setup/rails — avoids Omarchy PATH Ruby 4.x mixing with ledger
40
+ Ruby 3.3 and "gems not found" / wrong bundler errors.
41
+
3
42
  ## 2.1.7
4
43
 
5
44
  ### Added
data/Rakefile CHANGED
@@ -220,12 +220,30 @@ blogs.each do |dir|
220
220
 
221
221
  end
222
222
 
223
+ #===========================================================================================
224
+ # Release helpers: keep version.rb and Gemfile.lock in sync (CI frozen install)
225
+ #===========================================================================================
226
+
227
+ namespace :release do
228
+ desc 'Fail if version.rb does not match the PATH galaaz version in Gemfile.lock'
229
+ task :check do
230
+ ruby 'bin/check_gemfile_lock_version'
231
+ end
232
+
233
+ desc 'Set version.rb to VERSION=x.y.z and refresh Gemfile.lock (bundle lock)'
234
+ task :bump do
235
+ new_ver = ENV['VERSION'].to_s.strip
236
+ abort 'Usage: rake release:bump VERSION=x.y.z' if new_ver.empty?
237
+ ruby 'bin/release_bump', new_ver
238
+ end
239
+ end
240
+
223
241
  #===========================================================================================
224
242
  # Makes a gem for publishing in RubyGems
225
243
  #===========================================================================================
226
244
 
227
245
  desc 'Makes a Gem'
228
- task :make_gem do
246
+ task :make_gem => 'release:check' do
229
247
  (sh %{ gem build #{$gem_name}.gemspec })
230
248
  end
231
249
 
@@ -234,7 +252,7 @@ end
234
252
  #===========================================================================================
235
253
 
236
254
  desc 'Publish gem to rubygems'
237
- task :publish_gem do
255
+ task :publish_gem => 'release:check' do
238
256
  (sh %{ gem push #{$gem_name}-#{$version}.gem })
239
257
  end
240
258
 
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Fail if version.rb and the PATH galaaz entry in Gemfile.lock disagree.
5
+ # CI runs this before frozen bundle install so the failure is obvious
6
+ # (instead of Bundler exit 16 from ruby/setup-ruby).
7
+
8
+ root = File.expand_path('..', __dir__)
9
+ version_rb = File.join(root, 'version.rb')
10
+ lock_path = File.join(root, 'Gemfile.lock')
11
+
12
+ abort "missing #{version_rb}" unless File.file?(version_rb)
13
+ abort "missing #{lock_path}" unless File.file?(lock_path)
14
+
15
+ version_src = File.read(version_rb)
16
+ declared = version_src[/\$version\s*=\s*["']([^"']+)["']/, 1]
17
+ abort "could not parse $version from version.rb" if declared.nil? || declared.empty?
18
+
19
+ lock = File.read(lock_path)
20
+ # Prefer the PATH gemspec block (first galaaz version under PATH … specs:).
21
+ path_block = lock[/\APATH\n.*?^GEM\n/m]
22
+ locked =
23
+ if path_block
24
+ path_block[/^\s+galaaz \(([^)]+)\)/, 1]
25
+ end
26
+ locked ||= lock[/^\s+galaaz \(([^)]+)\)/, 1]
27
+ abort 'could not parse galaaz version from Gemfile.lock PATH specs' if locked.nil? || locked.empty?
28
+
29
+ if declared == locked
30
+ puts "OK: version.rb (#{declared}) matches Gemfile.lock path gem (#{locked})"
31
+ exit 0
32
+ end
33
+
34
+ warn <<~MSG
35
+ version.rb and Gemfile.lock disagree.
36
+
37
+ version.rb $version = #{declared}
38
+ Gemfile.lock PATH galaaz (#{locked})
39
+
40
+ After bumping the gem version, refresh the lockfile in the same commit:
41
+
42
+ rake release:bump VERSION=#{declared}
43
+
44
+ Or: bundle lock && git add Gemfile.lock
45
+ MSG
46
+ exit 1
data/bin/release_bump ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Bump version.rb and refresh Gemfile.lock so CI frozen bundle install stays valid.
5
+ # Usage: bin/release_bump x.y.z
6
+
7
+ root = File.expand_path('..', __dir__)
8
+ new_ver = ARGV[0].to_s.strip
9
+ abort 'Usage: bin/release_bump x.y.z' unless new_ver.match?(/\A\d+\.\d+\.\d+(?:[.-][\w.]+)?\z/)
10
+
11
+ path = File.join(root, 'version.rb')
12
+ src = File.read(path)
13
+ unless src.sub!(/\$version\s*=\s*["'][^"']+["']/, "$version=\"#{new_ver}\"")
14
+ abort "could not rewrite $version in #{path}"
15
+ end
16
+ File.write(path, src)
17
+ puts "Wrote version.rb $version=#{new_ver}"
18
+
19
+ Dir.chdir(root) do
20
+ ok = system('bundle', 'lock')
21
+ abort 'bundle lock failed' unless ok
22
+ ok = system(RbConfig.ruby, File.join(root, 'bin/check_gemfile_lock_version'))
23
+ abort 'version check failed after bump' unless ok
24
+ end
25
+
26
+ puts 'Next: update CHANGELOG.md, commit version.rb + Gemfile.lock, then rake make_gem && rake publish_gem'
data/blogs/README.md CHANGED
@@ -21,6 +21,8 @@ Examples:
21
21
  bin/gknit blogs/oh_my/oh_my.Rmd
22
22
  bin/gknit blogs/gknit/gknit.Rmd
23
23
  bin/gknit blogs/galaaz_ggplot/galaaz_ggplot.Rmd
24
+ bin/gknit blogs/galaaz_2_0/galaaz_2_0.Rmd
25
+ bin/gknit blogs/r_on_rails_ledger/r_on_rails_ledger.Rmd
24
26
  bin/gknit blogs/manual/manual.Rmd
25
27
  bin/gknit blogs/nse_dplyr/nse_dplyr.Rmd
26
28
  bin/gknit blogs/ruby_plot/ruby_plot.Rmd
@@ -36,6 +38,8 @@ From the project root:
36
38
  rake blog:oh_my
37
39
  rake blog:gknit
38
40
  rake blog:galaaz_ggplot
41
+ rake blog:galaaz_2_0
42
+ rake blog:r_on_rails_ledger
39
43
  rake blog:manual
40
44
  rake blog:nse_dplyr
41
45
  rake blog:ruby_plot
@@ -0,0 +1,385 @@
1
+ ---
2
+ title: "Galaaz 2.0: Ruby meets real GNU R (again)"
3
+ subtitle: "NewBridge, JRuby or CRuby, CRAN/Bioconductor, and Arrow-shaped data paths"
4
+ author: "Rodrigo Botafogo"
5
+ tags: [Galaaz, "Galaaz 2.0", Ruby, R, JRuby, CRuby, "GNU R", NewBridge, Arrow, knitr, gknit]
6
+ date: "2026"
7
+ output:
8
+ html_document:
9
+ self_contained: true
10
+ keep_md: true
11
+ toc: true
12
+ toc_float: true
13
+ toc_depth: 2
14
+ number_sections: true
15
+ includes:
16
+ before_body: _logo_before_body.html
17
+ pdf_document:
18
+ includes:
19
+ in_header:
20
+ - "../../sty/galaaz.sty"
21
+ - "../../sty/galaaz-headers-from-p3.tex"
22
+ keep_tex: yes
23
+ number_sections: yes
24
+ toc: true
25
+ toc_depth: 2
26
+ fontsize: 11pt
27
+ ---
28
+
29
+ ```{r setup, echo=FALSE}
30
+ options(width = 70)
31
+ ```
32
+
33
+ ```{ruby setup_ruby, echo=FALSE}
34
+ ENV['GALAAZ_BRIDGE_TIMEOUT_SEC'] ||= '300'
35
+ ENV['GALAAZ_JOBS_DIR'] ||=
36
+ File.join(Dir.tmpdir, 'galaaz_jobs_galaaz_2_0')
37
+ R.options(crayon__enabled: false, width: 70)
38
+ ```
39
+
40
+ ```{r brand_logo, echo=FALSE, fig.align='center', out.width='45%', eval=knitr::is_latex_output()}
41
+ # PDF only — HTML uses _logo_before_body.html (above the TOC).
42
+ knitr::include_graphics("images/galaaz-lockup-stacked.png")
43
+ ```
44
+
45
+ # Introduction
46
+
47
+ Ruby is excellent for web apps, orchestration, and expressive object models.
48
+ It has never matched **R** for statistics, Bioconductor pipelines, or the
49
+ depth of CRAN. **Python** closed a similar gap with NumPy, pandas, and a
50
+ huge scientific ecosystem. For Ruby, the practical answer is not to
51
+ reimplement that world — it is to **drive real R** from idiomatic Ruby.
52
+
53
+ **Galaaz** is that coupling. An earlier line of work ran on Oracle’s
54
+ **GraalVM** with **TruffleRuby** and **FastR** in one JVM. **Galaaz 2.0**
55
+ is a different architecture: **JRuby or CRuby** talk to **standard GNU R**
56
+ over a process bridge we call **NewBridge**. You keep CRAN and Bioconductor
57
+ (including compiled packages), and you keep a normal Ruby toolchain.
58
+
59
+ This post is the 2.0 story: what changed, how the bridge works, how to pin
60
+ R versions in containers, how data moves (including **Apache Arrow**), and
61
+ how to keep the bridge free for long R jobs. Deep API detail lives in the
62
+ [Galaaz Manual](https://rbotafogo.github.io/galaaz/); here we stay at
63
+ blog length with a few runnable sketches.
64
+
65
+ # What changed
66
+
67
+ | | Historical stack | Galaaz 2.0 |
68
+ |---|---|---|
69
+ | Ruby | TruffleRuby (Graal) | **JRuby** or **CRuby** |
70
+ | R | FastR (subset of GNU R) | **GNU R** (CRAN / Bioconductor) |
71
+ | Coupling | Same-JVM interop | **NewBridge** (separate processes) |
72
+ | Install | Special Graal distribution | `gem install` + compile gatekeeper |
73
+ | Legacy tools | `grun`, polyglot `gknit-draft` | `bin/galaaz-ruby`, `bin/gknit` |
74
+
75
+ Ideas from the older articles still matter — ggplot layers, dplyr pipes,
76
+ gKnit literate docs — but the **engine** underneath is different. If you
77
+ land on a 2018 “Ruby + R on GraalVM” post, treat the plots and narrative
78
+ as inspiration and this post (plus the manual) as the current stack.
79
+
80
+ # NewBridge in one picture
81
+
82
+ Galaaz 2.0 does **not** embed Renjin or FastR. Roughly:
83
+
84
+ 1. Your **Ruby** process (JRuby or CRuby) loads Galaaz.
85
+ 2. A native **gatekeeper** (`ext/new_bridge`, Rcpp / C++) mediates the protocol.
86
+ 3. A **GNU R** process evaluates calls, holds large objects, and runs packages.
87
+
88
+ Ruby stays Ruby: `R.mean`, `E.aes`, method chaining, classes and modules.
89
+ R stays R: the same `lm`, `DESeq2`, or `ggplot2` you would run in RStudio.
90
+ Large panels should usually **stay in R**; Ruby orchestrates and pulls
91
+ small results (Remote Control). Opt in to unbox scalars when you need a
92
+ plain Ruby number (`>> 0` for length-1 vectors is the common 2.0 idiom).
93
+
94
+ A minimal session looks like this:
95
+
96
+ ```{ruby hello_bridge}
97
+ require 'galaaz'
98
+
99
+ puts R.sum(R.c(1, 2, 3, 4, 5))
100
+ mtcars = ~R[:mtcars]
101
+ puts mtcars.dim
102
+ puts R.summary(mtcars.mpg)
103
+ ```
104
+
105
+ Callbacks still exist: Ruby procs can be registered so R code can call
106
+ back into Ruby when a package expects an R function. That path is
107
+ documented in the manual; the important product point is that **real
108
+ GNU R** is on the other end of the wire.
109
+
110
+ # Either Ruby engine
111
+
112
+ NewBridge is tested on both:
113
+
114
+ * **JRuby** (e.g. 10.x + JDK 21) — real OS threads, JVM ecosystem, natural
115
+ fit for Rails under load.
116
+ * **CRuby** (e.g. 3.3+) — MRI gem ecosystem, familiar for many Ruby shops.
117
+
118
+ The public API is the same. Launchers honor **`GALAAZ_RUBY`**.
119
+ Use **`bin/galaaz-ruby`**, or **`bin/galaaz-jruby`** to force JRuby.
120
+ Prefer **JRuby** when you want parallel Ruby threads feeding R;
121
+ prefer **CRuby** when your app and gems already live there. Arrow
122
+ helpers need engine-specific native bits (Java Arrow + `JAVA_OPTS`
123
+ nio opens on JRuby; Arrow GLib + `red-arrow` on CRuby) — see the
124
+ manual’s Arrow section.
125
+
126
+ ```bash
127
+ # From a checkout or installed layout:
128
+ bin/galaaz-ruby -e \
129
+ 'require "galaaz"; puts R.R__version[["version.string"]]'
130
+ GALAAZ_RUBY=jruby bin/galaaz-ruby -e \
131
+ 'require "galaaz"; puts R.pi'
132
+ ```
133
+
134
+ # Multiple R versions via containers
135
+
136
+ Because R is a **separate process**, the Ruby gem does not hard-wire one
137
+ R build. Whatever `R` / `Rscript` is on `PATH` (and can load the
138
+ gatekeeper) is the R you drive. That makes **containers** a clean way to
139
+ pin versions:
140
+
141
+ * Image A: Ubuntu + **R 4.3** + system libs for your Bioconductor set.
142
+ * Image B: same app code + **R 4.4** (or a rocker image) for comparison.
143
+ * Your Galaaz gem and Ruby code stay the same; only the image’s R changes.
144
+
145
+ Sketch (conceptual):
146
+
147
+ ```bash
148
+ # Dev against the R that the image provides
149
+ docker run --rm -it myorg/galaaz-r43 \
150
+ bin/galaaz-ruby script/analysis.rb
151
+
152
+ docker run --rm -it myorg/galaaz-r44 \
153
+ bin/galaaz-ruby script/analysis.rb
154
+ ```
155
+
156
+ Inside each image, `R --version` differs; NewBridge still speaks the same
157
+ protocol. For a first taste without building your own Dockerfile, the
158
+ repo ships **try** images and runners:
159
+
160
+ ```bash
161
+ # From a Galaaz checkout
162
+ ./docker/try-gstudio/run.sh # JRuby + gstudio-oriented image
163
+ ./docker/try-cruby/run.sh # CRuby try image
164
+ ```
165
+
166
+ **Cold-install** proofs (`docker/cold-install`,
167
+ `docker/cold-install-cruby`) install the gem on a throwaway Ubuntu and
168
+ compile the gatekeeper — useful when you want “empty machine → working
169
+ bridge” confidence. Multi-runtime Docker R matrices are still evolving
170
+ (slow integration coverage); the sketch above is the pattern to build on.
171
+
172
+ # Moving data: Apache Arrow
173
+
174
+ Crossing a process boundary means you must be honest about **copies**.
175
+ Galaaz’s Arrow roadmap has three stages:
176
+
177
+ | Stage | Idea | Status |
178
+ |---|---|---|
179
+ | **A** | Copy columnar data into R; return an R-side Arrow/proxy handle | Available (`from_ruby_batches`, `table_from`, …) |
180
+ | **B** | Same-machine **IPC file**: bulk bytes stay off the MsgPack bridge; only a **file path** crosses NewBridge | Shipped (two directions: B1 and B2) |
181
+ | **C** | Shared-memory bus (true shared RAM) | Future |
182
+
183
+ Do **not** read Stage A as “zero-copy shared RAM.” Stage A is “build the
184
+ table in R efficiently, then Remote-Control it.” Stage B is still a
185
+ file/`mmap` handoff on one machine—not Stage C—but it keeps large
186
+ columns out of the control plane.
187
+
188
+ ## Stage B in plain words
189
+
190
+ **IPC** here means Apache Arrow’s **Inter-Process Communication** file
191
+ format: a portable columnar dump on disk (often under `/dev/shm` so it
192
+ lives in RAM-backed tmpfs). Ruby and R never shove megabytes through
193
+ NewBridge; they agree on a path, then each side’s Arrow stack
194
+ reads or writes that file.
195
+
196
+ Stage B splits by **direction**:
197
+
198
+ * **B1 — Ruby → R (ingest).** Ruby writes an IPC file
199
+ (`Galaaz::ArrowIpc.write` / `write_batches`). R opens it with
200
+ `R::Arrow.open_ipc(path)` and gets a table proxy for dplyr or
201
+ modeling. After R has materialised the table, Ruby can
202
+ `Galaaz::ArrowIpc.release(path)` (unlink the scratch file).
203
+ * **B2 — R → Ruby (export).** After analytics in R, `R::Arrow.write_ipc`
204
+ writes another IPC file and returns its path over the bridge. Ruby
205
+ reads columns or row hashes with `Galaaz::ArrowIpc.read` /
206
+ `read_batches`—handy when the next step is a DB write or an API
207
+ payload in Ruby.
208
+
209
+ Needs: R package `arrow`, plus a Ruby writer/reader (CRuby:
210
+ **red-arrow**; JRuby: Arrow Java JARs). Specs skip when those are
211
+ missing; the sketch below does the same.
212
+
213
+ ## Stage B sketch (B1 ingest + B2 export)
214
+
215
+ This is the path to prefer for large same-machine handoffs. Ruby
216
+ threads build row batches; **B1** writes one IPC file and R opens it by
217
+ path; dplyr summarises in R; **B2** writes the result IPC and Ruby
218
+ reads row hashes. NewBridge only carries paths.
219
+
220
+ ```{ruby arrow_ipc_b12, message=FALSE, warning=FALSE}
221
+ ipc_ok = Galaaz::ArrowIpc.available? &&
222
+ (R::Support.eval(
223
+ "requireNamespace('arrow', quietly=TRUE) && " +
224
+ "requireNamespace('dplyr', quietly=TRUE)") == true)
225
+ unless ipc_ok
226
+ puts '(Skip: need Arrow IPC backend + R arrow/dplyr.)'
227
+ else
228
+ thread_count = 2
229
+ rows_per_thread = 250
230
+ group_count = 5
231
+ batches = []
232
+ mutex = Mutex.new
233
+ threads = []
234
+
235
+ thread_count.times do |tid|
236
+ threads << Thread.new do
237
+ start = tid * rows_per_thread
238
+ local = (start...(start + rows_per_thread)).map do |i|
239
+ {
240
+ id: i,
241
+ grp: "g#{i % group_count}",
242
+ value: (i % 17) + 1,
243
+ weight: ((i % 5) + 1) * 0.5
244
+ }
245
+ end
246
+ mutex.synchronize { batches << local }
247
+ end
248
+ end
249
+ threads.each(&:join)
250
+
251
+ paths = []
252
+ begin
253
+ # B1: Ruby → IPC file → R table proxy
254
+ # write_batches wants flat row Hashes (not nested batches)
255
+ in_path = Galaaz::ArrowIpc.write_batches(batches.flatten)
256
+ paths << in_path
257
+ tbl = R::Arrow.open_ipc(in_path)
258
+ puts "R class: #{tbl.rclass}"
259
+ puts "IPC in: #{File.basename(in_path)}"
260
+
261
+ grouped = R.dplyr___group_by(tbl, :grp)
262
+ summed = R.dplyr___summarise(
263
+ grouped,
264
+ n: E.n(),
265
+ total: E.sum(:value),
266
+ wsum: E.sum(R[:value] * R[:weight])
267
+ )
268
+
269
+ # B2: R → IPC file → Ruby row hashes
270
+ out_path = R::Arrow.write_ipc(summed)
271
+ paths << out_path
272
+ rows = Galaaz::ArrowIpc.read_batches(out_path)
273
+ puts "IPC out: #{File.basename(out_path)}"
274
+ rows.first(5).each do |r|
275
+ puts "#{r[:grp]} n=#{r[:n]} " +
276
+ "total=#{r[:total]} wsum=#{r[:wsum]}"
277
+ end
278
+
279
+ total_n = rows.map { |r| r[:n].to_i }.sum
280
+ expect_n = thread_count * rows_per_thread
281
+ puts "Sum of n (expect #{expect_n}): #{total_n}"
282
+ ensure
283
+ paths.each { |p| Galaaz::ArrowIpc.release(p) }
284
+ end
285
+ end
286
+ ```
287
+
288
+ What to notice: `write_batches` / `open_ipc` is **B1**; after dplyr,
289
+ `write_ipc` / `read_batches` is **B2**. Specs:
290
+ `arrow_ipc_handoff_spec.rb`, `arrow_ipc_export_spec.rb`. Deeper notes:
291
+ `Documentation/ROADMAP_ARROW_RUBY_R.md`.
292
+
293
+ ## Stage A (still useful)
294
+
295
+ Stage A copies batches straight into R with
296
+ `R::Arrow.from_ruby_batches`—no IPC file. Fine for modest tables or
297
+ when you do not have a Ruby Arrow writer installed. Same threaded
298
+ shape as above, one call instead of B1:
299
+
300
+ ```{ruby arrow_pipeline, message=FALSE, warning=FALSE}
301
+ arrow_ok = R::Support.eval(
302
+ "requireNamespace('arrow', quietly=TRUE) && " +
303
+ "requireNamespace('dplyr', quietly=TRUE)")
304
+ unless arrow_ok == true
305
+ puts '(Skip: need arrow + dplyr in R.)'
306
+ else
307
+ batches = [
308
+ [{ id: 1, grp: 'a', value: 10, weight: 1.0 },
309
+ { id: 2, grp: 'b', value: 20, weight: 0.5 }],
310
+ [{ id: 3, grp: 'a', value: 30, weight: 1.5 }]
311
+ ]
312
+ tbl = R::Arrow.from_ruby_batches(batches)
313
+ puts "R class: #{tbl.rclass}"
314
+ grouped = R.dplyr___group_by(tbl, :grp)
315
+ summarised = R.dplyr___summarise(
316
+ grouped, n: E.n(), total: E.sum(:value))
317
+ out = R.dplyr___collect(summarised)
318
+ puts R.as__data__frame(out)
319
+ end
320
+ ```
321
+
322
+ Larger Stage A demos live in `arrow_from_ruby_batches_spec.rb` and
323
+ `arrow_large_pipeline_spec.rb` (under `specs/` / `slow-specs/`).
324
+
325
+ # Keeping the bridge free
326
+
327
+ Two different “don’t block” stories:
328
+
329
+ * **`R::Async` / `R.eval_r_async`** — free a **Ruby** thread while the
330
+ **same** bridge R process works. Good for short/medium calls when you
331
+ still want one R session.
332
+ * **`R::Job`** — run heavy work in a **child `Rscript`**. The bridge
333
+ stays free for other traffic. Package installs
334
+ (`R.install_and_loads`) use this path so `make`/`gcc` do not wedge
335
+ the gatekeeper.
336
+
337
+ ```{ruby job_lm, message=FALSE, warning=FALSE}
338
+ begin
339
+ coef = R::Job.eval(<<~R) { |job| job.load_rds }
340
+ fit <- lm(mpg ~ wt, data = mtcars)
341
+ saveRDS(unname(coef(fit)), result_path)
342
+ R
343
+ puts coef
344
+ rescue => e
345
+ puts e.class.to_s
346
+ e.message.to_s.scan(/.{1,68}/).each { |line| puts line }
347
+ end
348
+ ```
349
+
350
+ Rule of thumb: short interactive analytics on the bridge; installs and
351
+ multi-minute fits in **`R::Job`**. Details and timeouts
352
+ (`GALAAZ_INSTALL_TIMEOUT_SEC`, `GALAAZ_JOBS_DIR`) are in the manual.
353
+
354
+ # What stayed the same
355
+
356
+ Galaaz 2.0 is a new **runtime**, not a new plotting language. These still
357
+ apply:
358
+
359
+ * **ggplot2** from Ruby — see *Ruby Plotting with Galaaz* /
360
+ *Beautiful Ruby Plots*.
361
+ * **dplyr / NSE** — see *Non Standard Evaluation in dplyr with Galaaz*.
362
+ * **Literate docs** — **gKnit** still wraps knitr so Ruby chunks render
363
+ next to R (HTML/PDF).
364
+ * **Classes, modules, procs** — the *oh my* S4 comparison still teaches
365
+ structure; unwrap and bridge details follow 2.0 rules.
366
+
367
+ R-on-Rails (Ruby web + R science) is the product framing: Rails (or any
368
+ Ruby app) orchestrates; GNU R computes.
369
+
370
+ # Conclusion
371
+
372
+ **Galaaz 2.0** bets on **boring, powerful defaults**: standard Ruby
373
+ engines, standard GNU R, a clear process bridge, containers to pin R,
374
+ and Arrow-shaped paths for larger tables — with zero-copy shared memory
375
+ still on the roadmap. The Graal-era prototype proved the *idea*; 2.0
376
+ makes the *ecosystem* reachable.
377
+
378
+ ## Further reading
379
+
380
+ * Manual (GitHub Pages): https://rbotafogo.github.io/galaaz/
381
+ * Arrow roadmap: `Documentation/ROADMAP_ARROW_RUBY_R.md`
382
+ * Repo blogs under `blogs/` (ggplot, dplyr, gKnit, oh my, manual)
383
+ * Try images: `docker/try-gstudio`, `docker/try-cruby`
384
+ * Older Graal-era plot narrative (historical): search
385
+ “Ruby Plotting with Galaaz in GraalVM” on Towards Data Science