Skip to main content

Cookbook

Ready-to-use recipes for common Ruby extension patterns. Copy, paste, and adapt!

String Manipulationโ€‹

Fast String Reversal with Unicode Supportโ€‹

use magnus::{function, prelude::*, Error, Ruby};
use unicode_segmentation::UnicodeSegmentation;

fn reverse_preserving_graphemes(input: String) -> String {
input.graphemes(true).rev().collect()
}

#[magnus::init]
fn init(ruby: &Ruby) -> Result<(), Error> {
let module = ruby.define_module("StringUtils")?;
module.define_singleton_method(
"reverse_unicode",
function!(reverse_preserving_graphemes, 1)
)?;
Ok(())
}

Usage:

StringUtils.reverse_unicode("Hello ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ!")  # => "!๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ olleH"

Efficient String Search and Replaceโ€‹

use magnus::Error;
use regex::Regex;

fn batch_replace(text: String, patterns: Vec<(String, String)>) -> Result<String, Error> {
let mut result = text;

for (pattern, replacement) in patterns {
let re = Regex::new(&pattern)
.map_err(|e| Error::new(
magnus::exception::arg_error(),
format!("Invalid regex: {}", e)
))?;

result = re.replace_all(&result, replacement.as_str()).to_string();
}

Ok(result)
}

Data Processingโ€‹

Parallel Array Processingโ€‹

use magnus::{RArray, Error, TryConvert};
use rayon::prelude::*;
use rb_sys::rb_thread_call_without_gvl;
use std::os::raw::c_void;

// Structure to hold data for processing
struct ProcessData {
items: Vec<i64>,
result: Vec<i64>,
}

extern "C" fn process_without_gvl(data: *mut c_void) -> *mut c_void {
unsafe {
let data = &mut *(data as *mut ProcessData);
data.result = data.items.par_iter()
.map(|&x| x * x) // Square each number
.collect();
}
std::ptr::null_mut()
}

fn parallel_map(array: RArray) -> Result<RArray, Error> {
// Convert to Rust Vec
let mut items = Vec::with_capacity(array.len());
for item in array.each() {
let val: i64 = TryConvert::try_convert(item?)?;
items.push(val);
}

let mut data = ProcessData {
items,
result: Vec::new(),
};

// Process in parallel (release GVL)
unsafe {
rb_thread_call_without_gvl(
Some(process_without_gvl),
&mut data as *mut _ as *mut c_void,
None,
std::ptr::null_mut(),
);
}

// Convert back to Ruby array
let result = RArray::new();
for val in data.result {
result.push(val)?;
}

Ok(result)
}

CSV Processingโ€‹

use magnus::{RArray, RHash, Symbol, Error};

fn parse_csv_to_hashes(csv_string: String) -> Result<RArray, Error> {
let mut reader = csv::Reader::from_reader(csv_string.as_bytes());
let headers = reader.headers()
.map_err(|e| Error::new(
magnus::exception::runtime_error(),
e.to_string()
))?
.clone();

let result = RArray::new();

for record in reader.records() {
let record = record.map_err(|e| Error::new(
magnus::exception::runtime_error(),
e.to_string()
))?;

let hash = RHash::new();
for (header, value) in headers.iter().zip(record.iter()) {
hash.aset(Symbol::new(header), value)?;
}

result.push(hash)?;
}

Ok(result)
}

JSON Handlingโ€‹

High-Performance JSON Parserโ€‹

use magnus::{RHash, RArray, Value, Error, Ruby, RString, value::ReprValue};

fn parse_json(ruby: &Ruby, json_string: String) -> Result<Value, Error> {
let value: serde_json::Value = serde_json::from_str(&json_string)
.map_err(|e| Error::new(
magnus::exception::runtime_error(),
format!("JSON parse error: {}", e)
))?;

json_to_ruby(ruby, value)
}

fn json_to_ruby(ruby: &Ruby, value: serde_json::Value) -> Result<Value, Error> {
use serde_json::Value as J;

match value {
J::Null => Ok(ruby.qnil().as_value()),
J::Bool(b) => Ok(if b { ruby.qtrue().as_value() } else { ruby.qfalse().as_value() }),
J::Number(n) => {
if let Some(i) = n.as_i64() {
Ok(ruby.integer_from_i64(i).as_value())
} else if let Some(f) = n.as_f64() {
Ok(ruby.float_from_f64(f).as_value())
} else {
Err(Error::new(
magnus::exception::runtime_error(),
"Number out of range"
))
}
}
J::String(s) => Ok(RString::new(&s).as_value()),
J::Array(arr) => {
let ruby_array = RArray::with_capacity(arr.len());
for item in arr {
ruby_array.push(json_to_ruby(ruby, item)?)?;
}
Ok(ruby_array.as_value())
}
J::Object(obj) => {
let ruby_hash = RHash::new();
for (key, val) in obj {
ruby_hash.aset(key, json_to_ruby(ruby, val)?)?;
}
Ok(ruby_hash.as_value())
}
}
}

File Operationsโ€‹

Fast File Processingโ€‹

use magnus::{Error, RString};
use std::fs;
use std::io::{BufRead, BufReader};

fn process_large_file(path: String, pattern: String) -> Result<i64, Error> {
let file = fs::File::open(&path)
.map_err(|e| Error::new(
magnus::exception::runtime_error(),
format!("Cannot open file: {}", e)
))?;

let reader = BufReader::new(file);
let mut count = 0i64;

for line in reader.lines() {
let line = line.map_err(|e| Error::new(
magnus::exception::runtime_error(),
format!("Read error: {}", e)
))?;

if line.contains(&pattern) {
count += 1;
}
}

Ok(count)
}

fn read_binary_file(path: String) -> Result<RString, Error> {
let data = fs::read(&path)
.map_err(|e| Error::new(
magnus::exception::runtime_error(),
format!("Cannot read file: {}", e)
))?;

// Return as binary string
Ok(RString::from_slice(&data))
}

Cryptographyโ€‹

Hash Functionsโ€‹

use magnus::{RString, Error};
use sha2::{Sha256, Digest};

fn sha256_hexdigest(input: RString) -> Result<String, Error> {
let data = unsafe { input.as_slice() };

let mut hasher = Sha256::new();
hasher.update(data);
let result = hasher.finalize();

Ok(hex::encode(result))
}

fn sha256_raw(input: RString) -> Result<RString, Error> {
let data = unsafe { input.as_slice() };

let mut hasher = Sha256::new();
hasher.update(data);
let result = hasher.finalize();

Ok(RString::from_slice(&result))
}

Database Operationsโ€‹

Connection Pool Patternโ€‹

use magnus::{Error};
use std::sync::{Arc, Mutex};
use once_cell::sync::Lazy;

// Global connection pool
static POOL: Lazy<Arc<Mutex<Vec<Connection>>>> = Lazy::new(|| {
Arc::new(Mutex::new(Vec::new()))
});

struct Connection {
id: usize,
// Your actual connection here
}

#[magnus::wrap(class = "DatabasePool", free_immediately)]
struct DatabasePool;

impl DatabasePool {
fn acquire() -> Result<Connection, Error> {
let mut pool = POOL.lock()
.map_err(|_| Error::new(magnus::exception::runtime_error(), "Failed to acquire pool lock"))?;

if let Some(conn) = pool.pop() {
Ok(conn)
} else {
// Create new connection
Ok(Connection { id: pool.len() })
}
}

fn release(conn: Connection) -> Result<(), Error> {
let mut pool = POOL.lock()
.map_err(|_| Error::new(magnus::exception::runtime_error(), "Failed to acquire pool lock"))?;
pool.push(conn);
Ok(())
}

fn with_connection<F, R>(f: F) -> Result<R, Error>
where
F: FnOnce(&Connection) -> Result<R, Error>,
{
let conn = Self::acquire()?;
let result = f(&conn);
Self::release(conn)?;
result
}
}

HTTP Clientโ€‹

Simple URL Operationsโ€‹

use magnus::{RHash, Error};
use url::Url;

fn parse_url_components(url_string: String) -> Result<RHash, Error> {
let parsed = Url::parse(&url_string)
.map_err(|e| Error::new(
magnus::exception::arg_error(),
format!("Invalid URL: {}", e)
))?;

let result = RHash::new();
result.aset("scheme", parsed.scheme())?;
if let Some(host) = parsed.host_str() {
result.aset("host", host)?;
}
result.aset("path", parsed.path())?;
if let Some(port) = parsed.port() {
result.aset("port", port)?;
}

Ok(result)
}

Image Processingโ€‹

Basic Image Operationsโ€‹

use magnus::{RString, Error};
use image::ImageFormat;

fn resize_image(
image_data: RString,
width: u32,
height: u32
) -> Result<RString, Error> {
let bytes = unsafe { image_data.as_slice() };

let img = image::load_from_memory(bytes)
.map_err(|e| Error::new(
magnus::exception::runtime_error(),
format!("Invalid image: {}", e)
))?;

let resized = img.resize_exact(width, height, image::imageops::FilterType::Lanczos3);

let mut output = Vec::new();
use std::io::Cursor;
resized.write_to(&mut Cursor::new(&mut output), ImageFormat::Png)
.map_err(|e| Error::new(
magnus::exception::runtime_error(),
format!("Failed to encode image: {}", e)
))?;

Ok(RString::from_slice(&output))
}

Performance Monitoringโ€‹

Benchmarking Helperโ€‹

use magnus::{block::Proc, Value, Error};
use std::time::Instant;

fn benchmark_block(iterations: usize, block: Proc) -> Result<f64, Error> {
let start = Instant::now();

for _ in 0..iterations {
block.call::<_, Value>(())?;
}

let duration = start.elapsed();
Ok(duration.as_secs_f64())
}

fn measure_memory<F, R>(f: F) -> Result<(R, usize), Error>
where
F: FnOnce() -> Result<R, Error>,
{
let before = get_memory_usage();
let result = f()?;
let after = get_memory_usage();

Ok((result, after - before))
}

fn get_memory_usage() -> usize {
// Platform-specific memory measurement
#[cfg(target_os = "linux")]
{
use std::fs;
let status = fs::read_to_string("/proc/self/status")
.unwrap_or_else(|_| String::from("Unable to read status"));
// Parse VmRSS from status
0 // Placeholder
}

#[cfg(not(target_os = "linux"))]
{
0 // Placeholder
}
}

Thread Safetyโ€‹

Thread-Safe Counterโ€‹

use magnus::Error;
use std::sync::{Arc, Mutex};

#[magnus::wrap(class = "ThreadSafeCounter", free_immediately)]
struct ThreadSafeCounter {
value: Arc<Mutex<i64>>,
}

impl ThreadSafeCounter {
fn new() -> Self {
ThreadSafeCounter {
value: Arc::new(Mutex::new(0)),
}
}

fn increment(&self) -> Result<i64, Error> {
let mut val = self.value.lock()
.map_err(|_| Error::new(magnus::exception::runtime_error(), "Failed to acquire lock"))?;
*val += 1;
Ok(*val)
}

fn value(&self) -> Result<i64, Error> {
let val = self.value.lock()
.map_err(|_| Error::new(magnus::exception::runtime_error(), "Failed to acquire lock"))?;
Ok(*val)
}
}

Custom Iteratorsโ€‹

Ruby-Style Enumeratorโ€‹

use magnus::{block::Proc, RArray, Value, Error};

#[magnus::wrap(class = "RangeIterator", free_immediately)]
struct RangeIterator {
current: i64,
end: i64,
step: i64,
}

impl RangeIterator {
fn new(start: i64, end: i64, step: Option<i64>) -> Self {
RangeIterator {
current: start,
end,
step: step.unwrap_or(1),
}
}

fn each(&mut self, block: Proc) -> Result<(), Error> {
while self.current < self.end {
block.call::<_, Value>((self.current,))?;
self.current += self.step;
}
Ok(())
}

fn to_a(&mut self) -> Result<RArray, Error> {
let array = RArray::new();
while self.current < self.end {
array.push(self.current)?;
self.current += self.step;
}
Ok(array)
}
}

More Recipes Comingโ€‹

Have a pattern you'd like to see? Open an issue or contribute your own.