From cea879bcc32f03b4bed13a4763c02a3e7b8f6306 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Tue, 11 Aug 2026 10:54:13 +0100 Subject: [PATCH] DOC-6957 Add Ruby array data type examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds local_examples/arrays_tutorial/ruby/dt_arrays.rb, covering all 11 steps that content/develop/data-types/arrays.md embeds. The page uses clients-example with no lang_filter, so the Ruby tab appears once examples.json regenerates — arrays.md itself needs no edit. Written against redis-rb master (unreleased): AR* support landed in redis/redis-rb#1371 on 2026-08-10, and the latest gem v6.0.0 (2026-07-31) has no lib/redis/commands/arrays.rb. Hence PARKED. ## How the expected outputs were established No Redis 8.8 with AR* was reachable (local server is 7.2.7, Docker daemon down), so nothing here ran end-to-end. The values come from three separate sources instead, which is worth knowing before trusting them: 1. Wire commands — VERIFIED BY EXECUTION. Replayed all 26 calls against the real redis-rb master (8a06ce6) in a throwaway git worktree with send_command stubbed, so no server was needed. Every call was accepted and serialized to the expected command array. This is what proves the keyword arguments are right (rev:, value:, with_values:, nocase:, logic:) — the error class most likely to be wrong when writing from docstrings. 2. Server reply values — INHERITED, NOT OBSERVED. Taken from the redis-py and predis examples in this same set, which agree with each other on every value. Two independent clients agreeing is good evidence about the wire, but it is still not an observation of Ruby's behaviour. 3. Ruby-side conversions — VERIFIED BY EXECUTION. arseek and arop are the only places Ruby diverges from the Python tab, because arrays.rb passes &Boolify and &Floatify. Fed the raw replies from (2) through the real lambdas: Boolify.call(1) => true, Floatify.call("60") => 60.0, ("30") => 30.0. ## The one expected output I would bet against Floatify.call(60) returns Integer 60, not 60.0 — it only produces a Float from a string. So `# 60.0` on the arop SUM/MAX lines is correct only if the server replies with a bulk string. It does according to both redis-py (asserts "60") and predis (asserts '60'), and both distinguish it from MATCH (integer 1), so the inference is sound — but this is the single line most likely to be wrong at unpark, and it will be wrong silently. Check arop first against a real 8.8. ## Deliberate choices - Mirrors the redis-py file's structure: each step is preceded by a REMOVE block that deletes its keys, rather than doing the reset inside the visible snippet. Chose within-set parallelism over the pattern in local_examples/ruby/dt_hash.rb, so the rendered Ruby and Python tabs read the same. Also means no "recreate the key" comments. - Directory is arrays_tutorial/ruby/, not arrays_tutorial/redis-rb/ — nine existing set dirs use `ruby`, and the language label in examples.json is `Ruby`. - armset takes redis-rb's Hash form, which reads closer to redis-py's dict than a flat pair list would. - arinfo is not covered, matching the other six clients in the set. Learned: verify a preemptive client example by executing the library with its transport stubbed, which separates "does the API accept this" from "does the server return that" — only the second needs a server Constraint: each STEP block is reset by the REMOVE block immediately before it, mirroring the redis-py file, so the rendered Ruby and Python tabs stay structurally parallel Rejected: resetting inside the visible snippet (the local_examples/ruby/dt_hash.rb pattern) | breaks parity with the other six tabs in this set Gaps: nothing ran against a real Redis 8.8 with AR* (local server is 7.2.7, Docker daemon down); every server reply value is inherited from the redis-py and predis examples rather than observed Directive: at unpark check arop SUM/MAX first — Floatify returns an Integer for an integer reply, so "# 60.0" is correct only if the server sends a bulk string, and it fails silently if not Recheck: when a released redis gem contains lib/redis/commands/arrays.rb Ticket: DOC-6957 Co-Authored-By: Claude Opus 5 (1M context) --- .../arrays_tutorial/ruby/dt_arrays.rb | 275 ++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 local_examples/arrays_tutorial/ruby/dt_arrays.rb diff --git a/local_examples/arrays_tutorial/ruby/dt_arrays.rb b/local_examples/arrays_tutorial/ruby/dt_arrays.rb new file mode 100644 index 0000000000..a8e771c29e --- /dev/null +++ b/local_examples/arrays_tutorial/ruby/dt_arrays.rb @@ -0,0 +1,275 @@ +# EXAMPLE: arrays_tutorial +# HIDE_START +require 'redis' + +r = Redis.new +# HIDE_END + +# REMOVE_START +def assert_equal(expected, actual) + raise "Expected #{expected.inspect}, got #{actual.inspect}" unless actual == expected +end + +r.del('events:1') +# REMOVE_END + +# STEP_START arset_arget +res1 = r.arset('events:1', 0, 'login', 'click', 'purchase') +puts res1 # 3 + +res2 = r.arget('events:1', 0) +puts res2 # login + +res3 = r.arget('events:1', 999) +puts res3.inspect # nil +# STEP_END + +# REMOVE_START +assert_equal(3, res1) +assert_equal('login', res2) +assert_equal(nil, res3) +r.del('events:1') +# REMOVE_END + +# REMOVE_START +r.del('metrics') +# REMOVE_END + +# STEP_START armset_armget +res4 = r.armset('metrics', { 0 => '10', 5 => '20', 100 => '30' }) +puts res4 # 3 + +res5 = r.armget('metrics', 0, 5, 100, 999) +puts res5.inspect # ["10", "20", "30", nil] +# STEP_END + +# REMOVE_START +assert_equal(3, res4) +assert_equal(['10', '20', '30', nil], res5) +r.del('metrics') +# REMOVE_END + +# REMOVE_START +r.del('sparse') +# REMOVE_END + +# STEP_START len_count +res6 = r.arset('sparse', 0, 'a') +puts res6 # 1 + +res7 = r.arset('sparse', 1000000, 'b') +puts res7 # 1 + +res8 = r.arlen('sparse') +puts res8 # 1000001 + +res9 = r.arcount('sparse') +puts res9 # 2 +# STEP_END + +# REMOVE_START +assert_equal(1, res6) +assert_equal(1, res7) +assert_equal(1000001, res8) +assert_equal(2, res9) +r.del('sparse') +# REMOVE_END + +# REMOVE_START +r.del('seq') +# REMOVE_END + +# STEP_START argetrange +res10 = r.armset('seq', { 0 => 'a', 1 => 'b', 3 => 'd' }) +puts res10 # 3 + +res11 = r.argetrange('seq', 0, 3) +puts res11.inspect # ["a", "b", nil, "d"] +# STEP_END + +# REMOVE_START +assert_equal(3, res10) +assert_equal(['a', 'b', nil, 'd'], res11) +r.del('seq') +# REMOVE_END + +# REMOVE_START +r.del('seq') +# REMOVE_END + +# STEP_START arscan +res12 = r.armset('seq', { 0 => 'a', 1 => 'b', 3 => 'd' }) +puts res12 # 3 + +res13 = r.arscan('seq', 0, 3) +res13.each { |index, value| puts "#{index} -> #{value}" } +# 0 -> a +# 1 -> b +# 3 -> d +# STEP_END + +# REMOVE_START +assert_equal(3, res12) +assert_equal([[0, 'a'], [1, 'b'], [3, 'd']], res13) +r.del('seq') +# REMOVE_END + +# REMOVE_START +r.del('log') +# REMOVE_END + +# STEP_START arinsert +res14 = r.arinsert('log', 'event1') +puts res14 # 0 + +res15 = r.arinsert('log', 'event2') +puts res15 # 1 + +res16 = r.arnext('log') +puts res16 # 2 + +res17 = r.arseek('log', 10) +puts res17 # true + +res18 = r.arinsert('log', 'event3') +puts res18 # 10 +# STEP_END + +# REMOVE_START +assert_equal(0, res14) +assert_equal(1, res15) +assert_equal(2, res16) +assert_equal(true, res17) +assert_equal(10, res18) +r.del('log') +# REMOVE_END + +# REMOVE_START +r.del('readings') +# REMOVE_END + +# STEP_START arring +res19 = r.arring('readings', 3, 'v0') +puts res19 # 0 + +res20 = r.arring('readings', 3, 'v1') +puts res20 # 1 + +res21 = r.arring('readings', 3, 'v2') +puts res21 # 2 + +res22 = r.arring('readings', 3, 'v3') +puts res22 # 0 + +res23 = r.arget('readings', 0) +puts res23 # v3 +# STEP_END + +# REMOVE_START +assert_equal(0, res19) +assert_equal(1, res20) +assert_equal(2, res21) +assert_equal(0, res22) +assert_equal('v3', res23) +r.del('readings') +# REMOVE_END + +# REMOVE_START +r.del('readings') +# REMOVE_END + +# STEP_START arlastitems +r.arring('readings', 3, 'v0') +r.arring('readings', 3, 'v1') +r.arring('readings', 3, 'v2') +r.arring('readings', 3, 'v3') + +res24 = r.arlastitems('readings', 3) +puts res24.inspect # ["v1", "v2", "v3"] + +res25 = r.arlastitems('readings', 3, rev: true) +puts res25.inspect # ["v3", "v2", "v1"] +# STEP_END + +# REMOVE_START +assert_equal(%w[v1 v2 v3], res24) +assert_equal(%w[v3 v2 v1], res25) +r.del('readings') +# REMOVE_END + +# REMOVE_START +r.del('scores') +# REMOVE_END + +# STEP_START arop +res26 = r.armset('scores', { 0 => '10', 1 => '20', 2 => '30' }) +puts res26 # 3 + +res27 = r.arop('scores', 0, 2, :sum) +puts res27 # 60.0 + +res28 = r.arop('scores', 0, 2, :max) +puts res28 # 30.0 + +res29 = r.arop('scores', 0, 2, :match, value: '10') +puts res29 # 1 +# STEP_END + +# REMOVE_START +assert_equal(3, res26) +assert_equal(60.0, res27) +assert_equal(30.0, res28) +assert_equal(1, res29) +r.del('scores') +# REMOVE_END + +# REMOVE_START +r.del('log') +# REMOVE_END + +# STEP_START argrep +res30 = r.armset('log', { + 0 => 'boot: ok', + 1 => 'warn: disk', + 2 => 'ERROR: cpu', + 3 => 'info: ready', + 4 => 'error: net' +}) +puts res30 # 5 + +res31 = r.argrep('log', 0, 4, match: 'error', nocase: true) +puts res31.inspect # [2, 4] + +res32 = r.argrep('log', 0, 4, glob: ['warn:*', 'error:*'], logic: :or, with_values: true) +puts res32.inspect # [[1, "warn: disk"], [4, "error: net"]] +# STEP_END + +# REMOVE_START +assert_equal(5, res30) +assert_equal([2, 4], res31) +assert_equal([[1, 'warn: disk'], [4, 'error: net']], res32) +r.del('log') +# REMOVE_END + +# REMOVE_START +r.del('scores') +# REMOVE_END + +# STEP_START ardel +res33 = r.armset('scores', { 0 => '10', 1 => '20', 2 => '30' }) +puts res33 # 3 + +res34 = r.ardel('scores', 1) +puts res34 # 1 + +res35 = r.ardelrange('scores', 0, 2) +puts res35 # 2 +# STEP_END + +# REMOVE_START +assert_equal(3, res33) +assert_equal(1, res34) +assert_equal(2, res35) +r.del('scores') +r.close +# REMOVE_END