Ruby SDK
Ruby SDK
Section titled “Ruby SDK”A comprehensive Ruby client library for the Splinterpic API. Compatible with Ruby 2.7+ and works seamlessly with Rails, Sinatra, and plain Ruby applications.
Installation
Section titled “Installation”Using Bundler (Recommended)
Section titled “Using Bundler (Recommended)”Add to your Gemfile:
# If we had a published gem (future)gem 'splinterpic'Manual Installation
Section titled “Manual Installation”Create the SDK class in your project:
require 'net/http'require 'json'require 'uri'
class SplinterpicClient class Error < StandardError; end class RateLimitError < Error; end class InvalidPromptError < Error; end class QuotaExceededError < Error; end
attr_reader :base_url, :default_model
def initialize(config = {}) @base_url = config[:base_url] or raise ArgumentError, 'base_url is required' @default_model = config[:default_model] || '@cf/black-forest-labs/flux-1-schnell' end
def generate(options = {}) data = { prompt: options[:prompt] || raise(ArgumentError, 'prompt is required'), model: options[:model] || @default_model }
data[:template_id] = options[:template_id] if options[:template_id] data[:collection_id] = options[:collection_id] if options[:collection_id]
request(:post, '/api/generate', data) end
def images(filters = {}) request(:get, '/api/images', nil, filters) end
def image(image_id) request(:get, "/api/images/#{image_id}") end
def delete_image(image_id) request(:delete, "/api/images/#{image_id}") end
def models request(:get, '/api/models') end
def create_collection(name, description = nil) data = { name: name } data[:description] = description if description request(:post, '/api/collections', data) end
def collections request(:get, '/api/collections') end
private
def request(method, path, body = nil, query = nil) uri = URI.join(@base_url, path) uri.query = URI.encode_www_form(query) if query
http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == 'https'
request = case method when :get then Net::HTTP::Get.new(uri) when :post then Net::HTTP::Post.new(uri) when :delete then Net::HTTP::Delete.new(uri) else raise ArgumentError, "Unsupported method: #{method}" end
request['Content-Type'] = 'application/json' request.body = body.to_json if body
response = http.request(request)
handle_response(response) end
def handle_response(response) body = JSON.parse(response.body, symbolize_names: true)
case response.code.to_i when 200..299 body when 400 raise InvalidPromptError, body[:error] || 'Bad request' when 402 raise QuotaExceededError, body[:error] || 'Quota exceeded' when 429 raise RateLimitError, body[:error] || 'Rate limit exceeded' else raise Error, body[:error] || "HTTP #{response.code}" end rescue JSON::ParserError raise Error, "Invalid JSON response: #{response.body}" endendQuick Start
Section titled “Quick Start”require_relative 'splinterpic_client'
# Initialize the clientclient = SplinterpicClient.new( base_url: 'https://splinterpic-worker.your-name.workers.dev', default_model: '@cf/black-forest-labs/flux-1-schnell')
# Generate an imagebegin image = client.generate( prompt: 'A serene mountain landscape at sunset' )
puts "Image generated successfully!" puts "ID: #{image[:id]}" puts "URL: #{image[:r2_url]}"
rescue SplinterpicClient::Error => e puts "Error: #{e.message}"endRails Integration
Section titled “Rails Integration”Initializer (config/initializers/splinterpic.rb)
Section titled “Initializer (config/initializers/splinterpic.rb)”require 'splinterpic_client'
Rails.configuration.splinterpic = SplinterpicClient.new( base_url: ENV['SPLINTERPIC_BASE_URL'], default_model: ENV.fetch('SPLINTERPIC_DEFAULT_MODEL', '@cf/black-forest-labs/flux-1-schnell'))Environment Variables (.env)
Section titled “Environment Variables (.env)”SPLINTERPIC_BASE_URL=https://splinterpic-worker.your-name.workers.devSPLINTERPIC_DEFAULT_MODEL=@cf/black-forest-labs/flux-1-schnellController
Section titled “Controller”class ImagesController < ApplicationController before_action :set_client
def create @image = @client.generate( prompt: params[:prompt], model: params[:model] )
render json: { success: true, image: @image }
rescue SplinterpicClient::Error => e render json: { success: false, error: e.message }, status: :unprocessable_entity end
def index @images = @client.images( limit: params[:limit] || 20, offset: params[:offset] || 0 )
render json: @images end
def show @image = @client.image(params[:id]) render json: @image
rescue SplinterpicClient::Error => e render json: { error: 'Image not found' }, status: :not_found end
def destroy @client.delete_image(params[:id]) head :no_content
rescue SplinterpicClient::Error => e render json: { error: e.message }, status: :unprocessable_entity end
private
def set_client @client = Rails.configuration.splinterpic endendActive Job for Async Generation
Section titled “Active Job for Async Generation”class GenerateImageJob < ApplicationJob queue_as :default
retry_on SplinterpicClient::RateLimitError, wait: 5.seconds, attempts: 3 retry_on SplinterpicClient::Error, wait: 10.seconds, attempts: 2
def perform(prompt, user_id, options = {}) client = Rails.configuration.splinterpic
image = client.generate( prompt: prompt, model: options[:model] )
# Store in database Image.create!( user_id: user_id, external_id: image[:id], prompt: prompt, url: image[:r2_url], model: image[:model], cost: image[:cost] )
# Notify user ImageGeneratedNotification.with(image: image).deliver_later(User.find(user_id)) endend
# UsageGenerateImageJob.perform_later('A sunset over mountains', current_user.id)Model Integration
Section titled “Model Integration”class Image < ApplicationRecord belongs_to :user
validates :prompt, presence: true, length: { maximum: 500 } validates :external_id, presence: true, uniqueness: true
scope :recent, -> { order(created_at: :desc) } scope :by_model, ->(model) { where(model: model) }
def regenerate client = Rails.configuration.splinterpic
new_image = client.generate( prompt: self.prompt, model: self.model )
update!( external_id: new_image[:id], url: new_image[:r2_url], cost: new_image[:cost] ) end
def self.batch_generate(prompts, user) prompts.map do |prompt| GenerateImageJob.perform_later(prompt, user.id) end endendSinatra Integration
Section titled “Sinatra Integration”require 'sinatra'require 'sinatra/json'require_relative 'splinterpic_client'
# Initialize clientconfigure do set :splinterpic, SplinterpicClient.new( base_url: ENV['SPLINTERPIC_BASE_URL'] )end
# Routespost '/api/generate' do begin payload = JSON.parse(request.body.read, symbolize_names: true)
image = settings.splinterpic.generate( prompt: payload[:prompt], model: payload[:model] )
json success: true, image: image
rescue SplinterpicClient::Error => e status 422 json success: false, error: e.message endend
get '/api/images' do begin images = settings.splinterpic.images( limit: params[:limit], offset: params[:offset] )
json images
rescue SplinterpicClient::Error => e status 500 json error: e.message endend
get '/api/images/:id' do begin image = settings.splinterpic.image(params[:id]) json image
rescue SplinterpicClient::Error => e status 404 json error: 'Image not found' endendAdvanced Usage
Section titled “Advanced Usage”Batch Generation with Concurrent Requests
Section titled “Batch Generation with Concurrent Requests”require 'concurrent'
class BatchImageGenerator def initialize(client) @client = client end
def generate_batch(prompts, max_threads: 5) pool = Concurrent::FixedThreadPool.new(max_threads) futures = []
prompts.each do |prompt| futures << Concurrent::Future.execute(executor: pool) do begin @client.generate(prompt: prompt) rescue StandardError => e { error: e.message, prompt: prompt } end end end
# Wait for all to complete results = futures.map(&:value)
pool.shutdown pool.wait_for_termination
results endend
# Usageclient = SplinterpicClient.new(base_url: ENV['SPLINTERPIC_BASE_URL'])generator = BatchImageGenerator.new(client)
prompts = [ 'Mountain landscape at sunset', 'Ocean waves on beach', 'City skyline at night']
results = generator.generate_batch(prompts)
results.each do |result| if result[:error] puts "Failed: #{result[:prompt]} - #{result[:error]}" else puts "Generated: #{result[:id]}" endendCollection Manager
Section titled “Collection Manager”class CollectionManager def initialize(client) @client = client end
def create_campaign(name, prompts) # Create collection collection = @client.create_collection( name, "Marketing campaign images" )
# Generate images in collection images = prompts.map do |prompt| @client.generate( prompt: prompt, collection_id: collection[:id] ) end
{ collection: collection, images: images } end
def export_collection(collection_id, format: :json) images = @client.images(collection_id: collection_id)
case format when :json images.to_json when :csv require 'csv' CSV.generate do |csv| csv << ['ID', 'Prompt', 'Model', 'URL', 'Created At'] images[:images].each do |img| csv << [img[:id], img[:prompt], img[:model], img[:r2_url], img[:created_at]] end end end endend
# Usagemanager = CollectionManager.new(client)
campaign = manager.create_campaign('Q1 Social Media', [ 'Product showcase on white background', 'Lifestyle shot with product in use'])
puts "Created campaign with #{campaign[:images].size} images"
# Export to CSVcsv_data = manager.export_collection(campaign[:collection][:id], format: :csv)File.write('campaign_export.csv', csv_data)Rate Limiting Helper
Section titled “Rate Limiting Helper”class RateLimitedClient def initialize(client, requests_per_minute: 60) @client = client @requests_per_minute = requests_per_minute @request_times = [] @mutex = Mutex.new end
def generate(options) wait_if_rate_limited
@client.generate(options) end
private
def wait_if_rate_limited @mutex.synchronize do now = Time.now
# Remove timestamps older than 1 minute @request_times.reject! { |t| now - t > 60 }
if @request_times.size >= @requests_per_minute sleep_time = 60 - (now - @request_times.first) sleep(sleep_time) if sleep_time > 0
@request_times.clear end
@request_times << now end endend
# Usageclient = SplinterpicClient.new(base_url: ENV['SPLINTERPIC_BASE_URL'])rate_limited_client = RateLimitedClient.new(client, requests_per_minute: 30)
# This will automatically rate limit requests100.times do |i| image = rate_limited_client.generate(prompt: "Test image #{i}") puts "Generated: #{image[:id]}"endRetry Logic with Exponential Backoff
Section titled “Retry Logic with Exponential Backoff”class RetryableClient def initialize(client, max_retries: 3) @client = client @max_retries = max_retries end
def generate(options) retries = 0
begin @client.generate(options)
rescue SplinterpicClient::RateLimitError => e retries += 1 if retries <= @max_retries wait_time = 2 ** retries # Exponential backoff: 2, 4, 8 seconds puts "Rate limited, retrying in #{wait_time} seconds..." sleep(wait_time) retry else raise end
rescue SplinterpicClient::Error => e retries += 1 if retries <= @max_retries && e.message.include?('timeout') wait_time = 2 ** retries puts "Request timeout, retrying in #{wait_time} seconds..." sleep(wait_time) retry else raise end end endend
# Usageclient = SplinterpicClient.new(base_url: ENV['SPLINTERPIC_BASE_URL'])retryable = RetryableClient.new(client, max_retries: 3)
image = retryable.generate(prompt: 'Test')Caching with Redis
Section titled “Caching with Redis”require 'redis'require 'json'
class CachedSplinterpicClient def initialize(client, redis_url: ENV['REDIS_URL']) @client = client @redis = Redis.new(url: redis_url) end
def models cached = @redis.get('splinterpic:models')
if cached JSON.parse(cached, symbolize_names: true) else models = @client.models @redis.setex('splinterpic:models', 3600, models.to_json) # Cache for 1 hour models end end
def image(image_id) cache_key = "splinterpic:image:#{image_id}" cached = @redis.get(cache_key)
if cached JSON.parse(cached, symbolize_names: true) else image = @client.image(image_id) @redis.setex(cache_key, 300, image.to_json) # Cache for 5 minutes image end end
def generate(options) # Don't cache generation requests @client.generate(options) endend
# Usageclient = SplinterpicClient.new(base_url: ENV['SPLINTERPIC_BASE_URL'])cached_client = CachedSplinterpicClient.new(client)
# First call hits APImodels = cached_client.models
# Subsequent calls use cachemodels = cached_client.models # Fast!Testing
Section titled “Testing”RSpec Example
Section titled “RSpec Example”require 'rspec'require 'webmock/rspec'
RSpec.describe SplinterpicClient do let(:base_url) { 'https://splinterpic-worker.test.workers.dev' } let(:client) { described_class.new(base_url: base_url) }
describe '#generate' do it 'generates an image successfully' do stub_request(:post, "#{base_url}/api/generate") .with( body: { prompt: 'Test prompt', model: '@cf/black-forest-labs/flux-1-schnell' }.to_json ) .to_return( status: 200, body: { id: 'img_123', prompt: 'Test prompt', r2_url: 'https://example.com/image.png' }.to_json )
result = client.generate(prompt: 'Test prompt')
expect(result[:id]).to eq('img_123') expect(result[:prompt]).to eq('Test prompt') end
it 'raises error on invalid prompt' do stub_request(:post, "#{base_url}/api/generate") .to_return( status: 400, body: { error: 'Invalid prompt' }.to_json )
expect { client.generate(prompt: '') }.to raise_error(SplinterpicClient::InvalidPromptError) end end
describe '#models' do it 'fetches available models' do stub_request(:get, "#{base_url}/api/models") .to_return( status: 200, body: { models: [ { id: 'model1', name: 'Model 1' } ] }.to_json )
result = client.models
expect(result[:models]).to be_an(Array) expect(result[:models].size).to eq(1) end endendMinitest Example
Section titled “Minitest Example”require 'minitest/autorun'require 'webmock/minitest'
class SplinterpicClientTest < Minitest::Test def setup @base_url = 'https://splinterpic-worker.test.workers.dev' @client = SplinterpicClient.new(base_url: @base_url) end
def test_generate_success stub_request(:post, "#{@base_url}/api/generate") .to_return( status: 200, body: { id: 'img_123', prompt: 'Test' }.to_json )
result = @client.generate(prompt: 'Test')
assert_equal 'img_123', result[:id] end
def test_rate_limit_error stub_request(:post, "#{@base_url}/api/generate") .to_return( status: 429, body: { error: 'Rate limit exceeded' }.to_json )
assert_raises(SplinterpicClient::RateLimitError) do @client.generate(prompt: 'Test') end endendBest Practices
Section titled “Best Practices”1. Use Environment Variables
Section titled “1. Use Environment Variables”# Goodclient = SplinterpicClient.new( base_url: ENV.fetch('SPLINTERPIC_BASE_URL'))
# Bad - Never hardcode URLsclient = SplinterpicClient.new( base_url: 'https://splinterpic-worker.workers.dev')2. Handle All Error Types
Section titled “2. Handle All Error Types”begin image = client.generate(prompt: prompt)
rescue SplinterpicClient::RateLimitError => e # Wait and retry sleep 5 retry
rescue SplinterpicClient::QuotaExceededError => e # Notify admin AdminMailer.quota_exceeded.deliver_now raise
rescue SplinterpicClient::InvalidPromptError => e # Show user-friendly message flash[:error] = "Please provide a valid prompt"
rescue SplinterpicClient::Error => e # Log and notify Rails.logger.error "Splinterpic error: #{e.message}" raiseend3. Use Background Jobs for Generation
Section titled “3. Use Background Jobs for Generation”# Good - non-blockingGenerateImageJob.perform_later(prompt, user_id)
# Bad - blocks requestimage = client.generate(prompt: prompt)4. Implement Logging
Section titled “4. Implement Logging”class SplinterpicClient def initialize(config = {}) @logger = config[:logger] || Logger.new(STDOUT) # ... rest of initialization end
private
def request(method, path, body = nil, query = nil) @logger.info "Splinterpic API: #{method.upcase} #{path}" start_time = Time.now
response = # ... make request
duration = ((Time.now - start_time) * 1000).round @logger.info "Splinterpic API: #{response.code} (#{duration}ms)"
handle_response(response) endendSupport
Section titled “Support”- Documentation: API Reference
- Email: support@splinterpic.com