diff --git a/2023/ruby/.ruby-version b/2023/ruby/.ruby-version
new file mode 100644
index 0000000..a3ec5a4
--- /dev/null
+++ b/2023/ruby/.ruby-version
@@ -0,0 +1 @@
+3.2
diff --git a/2023/ruby/Gemfile b/2023/ruby/Gemfile
new file mode 100644
index 0000000..4e7be04
--- /dev/null
+++ b/2023/ruby/Gemfile
@@ -0,0 +1,4 @@
+source "https://rubygems.org"
+
+gem "minitest"
+gem "rake"
diff --git a/2023/ruby/Gemfile.lock b/2023/ruby/Gemfile.lock
new file mode 100644
index 0000000..2e5a344
--- /dev/null
+++ b/2023/ruby/Gemfile.lock
@@ -0,0 +1,15 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ minitest (5.20.0)
+ rake (13.1.0)
+
+PLATFORMS
+ arm64-darwin-22
+
+DEPENDENCIES
+ minitest
+ rake
+
+BUNDLED WITH
+ 2.4.1
diff --git a/2023/ruby/day_01.rb b/2023/ruby/day_01.rb
new file mode 100644
index 0000000..4bdc43e
--- /dev/null
+++ b/2023/ruby/day_01.rb
@@ -0,0 +1,24 @@
+lines = ARGF.readlines(chomp: true)
+
+# part one
+p lines
+ .map { _1.chars.select {|c| c =~ /\d/ }}
+ .map { _1.values_at(0, -1) }
+ .sum { _1.join.to_i }
+
+# part two
+spellings = %w[
+ one two three four five six seven eight nine
+].each.with_index.to_h { [_1, _2 + 1] }
+digits = (spellings.keys + spellings.values).join(?|)
+first_re = /(#{digits})(?~#{digits})/
+last_re = /.*(#{digits})/
+
+p lines
+ .sum { |line|
+ [first_re, last_re]
+ .map { _1.match(line)[1] }
+ .map { spellings.fetch(_1, &:to_i) }
+ .join
+ .to_i
+ }