Ruby & Rails Integration
Ruby & Rails Integration
WeftKit Standalone speaks standard wire protocols, so every existing Ruby database gem and Rails integration works without modification. This guide covers bare Ruby drivers and full Rails integration for each WeftKit engine.
Prerequisites
- WeftKit Standalone running and reachable (see Deployment)
- Ruby 3.2 or later
- Bundler
Gemfile
ruby# Gemfile # WeftKitRel — PostgreSQL wire protocol gem 'pg', '~> 1.5' # bare Ruby gem 'activerecord', '~> 7.1' # Rails ORM (included in Rails) # WeftKitDoc — MongoDB wire protocol gem 'mongo', '~> 2.20' # WeftKitMem — Redis RESP3 gem 'redis', '~> 5.2' gem 'hiredis-client', '~> 0.22' # optional C extension for better performance # WeftKitGraph — Bolt (Neo4j) gem 'neo4j-ruby-driver', '~> 4.4'
bashbundle install
1. WeftKitRel via pg Gem (Bare Ruby)
WeftKitRel speaks the PostgreSQL v3 wire protocol. The pg gem connects to it identically to a PostgreSQL server.
Connecting
rubyrequire 'pg' conn = PG.connect( host: 'localhost', port: 5432, dbname: 'mydb', user: 'app_user', password: ENV.fetch('WEFTKIT_PASS'), sslmode: 'prefer' ) puts "Connected to WeftKitRel: #{conn.server_version}"
Parameterised Queries
ruby# Always use parameterised queries — prevents SQL injection result = conn.exec_params( 'SELECT id, name, email FROM users WHERE age > $1 ORDER BY name', [18] ) result.each do |row| puts "#{row['name']} — #{row['email']}" end result.clear
Inserting and Returning
rubyresult = conn.exec_params( 'INSERT INTO users (name, email, age) VALUES ($1, $2, $3) RETURNING id', ['Alice', 'alice@example.com', 30] ) id = result[0]['id'].to_i puts "Created user with ID: #{id}" result.clear
Transaction Block
rubyconn.transaction do |tx| tx.exec_params( 'UPDATE accounts SET balance = balance - $1 WHERE id = $2', [100.00, 1] ) tx.exec_params( 'UPDATE accounts SET balance = balance + $1 WHERE id = $2', [100.00, 2] ) # Automatically commits on block exit; rolls back on exception end
Iterating Large Result Sets
ruby# Use exec_params with a block to avoid loading all rows into memory at once conn.send_query_params('SELECT * FROM large_table WHERE active = $1', [true]) conn.set_single_row_mode loop do result = conn.get_result break if result.nil? result.check result.each { |row| process(row) } result.clear end
2. WeftKitRel via ActiveRecord + Rails
config/database.yml
yamldefault: &default adapter: postgresql encoding: unicode pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> timeout: 5000 host: <%= ENV.fetch("WEFTKIT_HOST", "localhost") %> port: <%= ENV.fetch("WEFTKIT_PORT", 5432) %> username: <%= ENV.fetch("WEFTKIT_USER", "app_user") %> password: <%= ENV.fetch("WEFTKIT_PASS") { Rails.application.credentials.weftkit_pass } %> sslmode: <%= ENV.fetch("WEFTKIT_SSLMODE", "prefer") %> development: <<: *default database: myapp_development test: <<: *default database: myapp_test production: <<: *default database: myapp_production sslmode: require pool: <%= ENV.fetch("RAILS_MAX_THREADS", 10) %>
Rails Credentials (Recommended for Production)
bash# Edit credentials — stores encrypted in config/credentials.yml.enc rails credentials:edit
yaml# config/credentials.yml.enc (decrypted view) weftkit_pass: "my-production-secret"
ApplicationRecord Model
ruby# app/models/application_record.rb class ApplicationRecord < ActiveRecord::Base primary_abstract_class end
ruby# app/models/user.rb class User < ApplicationRecord has_many :orders, dependent: :destroy validates :name, presence: true validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP } validates :age, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true scope :adults, -> { where('age >= ?', 18) } scope :by_name, -> { order(:name) } end
ruby# app/models/order.rb class Order < ApplicationRecord belongs_to :user validates :total, numericality: { greater_than: 0 } validates :status, inclusion: { in: %w[pending processing shipped delivered cancelled] } scope :active, -> { where.not(status: 'cancelled') } end
Rails Migration
ruby# db/migrate/20240101000001_create_users.rb class CreateUsers < ActiveRecord::Migration[7.1] def change create_table :users do |t| t.string :name, null: false t.string :email, null: false t.integer :age t.timestamps end add_index :users, :email, unique: true add_index :users, :age end end
ruby# db/migrate/20240101000002_create_orders.rb class CreateOrders < ActiveRecord::Migration[7.1] def change create_table :orders do |t| t.references :user, null: false, foreign_key: true t.decimal :total, null: false, precision: 10, scale: 2 t.string :status, null: false, default: 'pending' t.timestamps end add_index :orders, [:user_id, :status] end end
bashrails db:migrate
ActiveRecord Queries
ruby# Create user = User.create!(name: 'Alice', email: 'alice@example.com', age: 30) # Find user = User.find(1) user = User.find_by!(email: 'alice@example.com') # Find with conditions adults = User.where('age > ?', 18).order(:name).limit(50) # Chained scopes top_users = User.adults.by_name.includes(:orders).where(orders: { status: 'delivered' }) # Eager loading (avoids N+1 queries) users = User.includes(:orders).where('age > ?', 18) users.each do |u| puts "#{u.name} has #{u.orders.size} orders" end # Update user.update!(age: 31) User.where('age < ?', 18).update_all(status: 'minor') # Destroy user.destroy User.where(status: 'cancelled').destroy_all
ActiveRecord Transactions
rubyActiveRecord::Base.transaction do user = User.create!(name: 'Bob', email: 'bob@example.com') Order.create!(user: user, total: 49.99, status: 'pending') # Both records committed together; any exception triggers rollback end
3. WeftKitDoc via mongo Gem
WeftKitDoc speaks the MongoDB Wire protocol. The mongo gem connects to it directly.
Connecting
rubyrequire 'mongo' Mongo::Logger.logger.level = Logger::WARN client = Mongo::Client.new( 'mongodb://app_user:secret@localhost:27017/mydb', server_selection_timeout: 5 ) collection = client[:articles]
Inserting Documents
ruby# Insert one result = collection.insert_one( title: 'Getting Started with WeftKit', author: 'Alice', tags: ['database', 'weftkit'], views: 0, created: Time.now ) puts "Inserted ID: #{result.inserted_id}" # Insert many collection.insert_many([ { title: 'MVCC Deep Dive', author: 'Bob', views: 0 }, { title: 'Vector Search', author: 'Carol', views: 0 }, ])
Finding Documents
ruby# Find one article = collection.find(author: 'Alice').first puts article[:title] if article # Find many with filter and options cursor = collection.find({ tags: 'database' }) .sort(views: -1) .limit(10) cursor.each { |doc| puts "#{doc[:title]} — #{doc[:views]} views" }
Updating Documents
ruby# Update one collection.update_one( { title: 'Getting Started with WeftKit' }, { '$set' => { views: 1500, featured: true } } ) # Update many collection.update_many( { author: 'Bob' }, { '$inc' => { views: 10 } } )
Aggregation Pipeline
rubypipeline = [ { '$match' => { views: { '$gt' => 100 } } }, { '$group' => { '_id' => '$author', 'total_views' => { '$sum' => '$views' }, 'count' => { '$sum' => 1 } }}, { '$sort' => { 'total_views' => -1 } }, { '$limit' => 5 } ] collection.aggregate(pipeline).each do |result| puts "#{result['_id']}: #{result['total_views']} total views" end
4. WeftKitMem via redis Gem
WeftKitMem speaks Redis RESP3. The redis gem connects to it using the standard Redis protocol.
Connecting
rubyrequire 'redis' redis = Redis.new( host: 'localhost', port: 6379, password: ENV.fetch('REDIS_PASSWORD', nil), db: 0, timeout: 5.0 )
String Commands with Expiry
ruby# Set with TTL in seconds redis.setex('session:abc123', 1800, 'user_42') # Get value = redis.get('session:abc123') if value.nil? puts 'Key not found or expired' else puts "Session user: #{value}" end # Atomic increment views = redis.incr('article:42:views')
Hash Commands
ruby# Set multiple hash fields redis.hmset('user:42', 'name', 'Alice', 'email', 'alice@example.com', 'plan', 'pro') # Get all fields fields = redis.hgetall('user:42') puts "#{fields['name']} — #{fields['plan']}" # Get one field email = redis.hget('user:42', 'email')
Pub/Sub
ruby# Publisher (in a separate thread or process) Thread.new do publisher = Redis.new(host: 'localhost', port: 6379, password: ENV.fetch('REDIS_PASSWORD', nil)) loop do publisher.publish('events:orders', JSON.generate({ order_id: 42, status: 'shipped' })) sleep 1 end end # Subscriber (blocks current thread) redis.subscribe('events:orders') do |on| on.message do |channel, message| data = JSON.parse(message) puts "Order #{data['order_id']} is now #{data['status']}" end end
5. WeftKitGraph via Neo4j Ruby Driver
WeftKitGraph speaks the Bolt protocol. Use the neo4j-ruby-driver gem.
Connecting
rubyrequire 'neo4j/driver' driver = Neo4j::Driver::GraphDatabase.driver( 'bolt://localhost:7687', Neo4j::Driver::AuthTokens.basic('app_user', 'secret') )
Read Session
rubyfriends = driver.session(default_access_mode: Neo4j::Driver::AccessMode::READ) do |session| session.read_transaction do |tx| result = tx.run( 'MATCH (p:Person {name: $name})-[:FRIEND]->(f:Person) RETURN f.name AS name, f.age AS age', name: 'Alice' ) result.map { |record| { name: record[:name], age: record[:age] } } end end friends.each { |f| puts "#{f[:name]} (age #{f[:age]})" }
Write Session
rubydriver.session do |session| session.write_transaction do |tx| tx.run( 'MERGE (a:Person {name: $name1}) MERGE (b:Person {name: $name2}) MERGE (a)-[:FRIEND]->(b)', name1: 'Alice', name2: 'Carol' ) end end
Cleanup
rubyat_exit { driver.close }
6. Rails Integration — Redis Caching and Sessions
Cache Store in config/environments/production.rb
ruby# config/environments/production.rb Rails.application.configure do config.cache_store = :redis_cache_store, { url: ENV.fetch('REDIS_URL', 'redis://:secret@localhost:6379/1'), connect_timeout: 30, read_timeout: 0.2, write_timeout: 0.2, reconnect_attempts: 1, error_handler: ->(method:, returning:, exception:) { Rails.logger.error "Redis error in #{method}: #{exception.message}" } } config.session_store :cache_store, key: '_myapp_session', expire_after: 2.hours end
Action Cable Redis Adapter
yaml# config/cable.yml development: adapter: redis url: <%= ENV.fetch("REDIS_URL", "redis://localhost:6379/0") %> channel_prefix: myapp_development production: adapter: redis url: <%= ENV.fetch("REDIS_URL") %> channel_prefix: myapp_production
Using the Cache in Controllers and Models
ruby# In a controller def show @product = Rails.cache.fetch("product:#{params[:id]}", expires_in: 1.hour) do Product.find(params[:id]) end end # Fragment caching in a view (works automatically with redis cache_store) # <%= cache @product do %> # <%= render @product %> # <% end %>
Low-Level Redis Access in Rails
ruby# config/initializers/redis.rb REDIS = Redis.new( url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0'), password: ENV.fetch('REDIS_PASSWORD', nil) )
ruby# Anywhere in your Rails app REDIS.set('rate_limit:user:42', 0, ex: 3600) count = REDIS.incr('rate_limit:user:42') raise TooManyRequestsError if count > 100
Connection String Quick Reference
| Engine | Gem | Connection |
|---|---|---|
| WeftKitRel | pg | PG.connect(host: 'localhost', port: 5432, dbname: 'mydb', user: 'app_user', password: '...') |
| WeftKitRel | ActiveRecord | adapter: postgresql in database.yml |
| WeftKitDoc | mongo | Mongo::Client.new('mongodb://app_user:secret@localhost:27017/mydb') |
| WeftKitMem | redis | Redis.new(host: 'localhost', port: 6379, password: '...') |
| WeftKitGraph | neo4j-ruby-driver | GraphDatabase.driver('bolt://localhost:7687', AuthTokens.basic(...)) |
Next Steps
- Deployment — Start and configure WeftKit Standalone
- Security — Configure TLS, JWT, and mTLS for production
- Integration Guides — Other language guides
On this page