Project

activeblob

0.0
No release in over 3 years
ActiveBlob provides a Blob model with SHA1-based deduplication, polymorphic attachments, and support for multiple storage backends (filesystem, S3). Includes automatic metadata extraction for images, videos, and PDFs.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

Runtime

~> 1.0
>= 6.1
 Project Readme

ActiveBlob

ActiveBlob is a content-addressable blob storage system for Rails applications. It provides SHA1-based deduplication, polymorphic attachments, and support for multiple storage backends (filesystem and S3).

Features

  • Content-addressable storage: Blobs are identified by SHA1 hash, preventing duplicate storage
  • Polymorphic attachments: Attach blobs to any model with has_one_blob or has_many_blobs
  • Multiple storage backends: Filesystem or S3-compatible storage
  • Type-specific models: Built-in support for images, videos, and PDFs with automatic metadata extraction
  • Flexible input: Create blobs from files, URLs, base64 data, or raw data
  • URL generation: Generate signed URLs for cloud storage or local paths

Installation

Add this line to your application's Gemfile:

gem 'activeblob'

And then execute:

bundle install

Run the installer to generate migrations and configuration:

rails generate activeblob:install
rails db:migrate

Configuration

Configure your storage backend in config/initializers/activeblob.rb:

Filesystem Storage (Default)

ActiveBlob.configure do |config|
  config.storage_config = {
    storage: 'filesystem',
    path: Rails.root.join('storage', 'blobs')
  }
end

S3 Storage

ActiveBlob.configure do |config|
  config.storage_config = {
    storage: 's3',
    bucket: ENV['S3_BUCKET'],
    access_key_id: ENV['S3_ACCESS_KEY_ID'],
    secret_access_key: ENV['S3_SECRET_ACCESS_KEY'],
    region: ENV['S3_REGION'] || 'us-east-1',
    endpoint: ENV['S3_ENDPOINT'] # Optional, for S3-compatible services
  }
end

Usage

Adding Attachments to Models

Single Attachment

class User < ApplicationRecord
  has_one_blob :avatar
end

# Usage
user = User.new
user.avatar = params[:avatar]  # File upload
user.save

Multiple Attachments

class Post < ApplicationRecord
  has_many_blobs :images
end

# Usage
post = Post.new
post.images = [file1, file2, file3]
post.save

Creating Blobs

From File Upload

blob = ActiveBlob::Blob.new(file: params[:file])
blob.save

From URL

blob = ActiveBlob::Blob.new(url: "https://example.com/image.jpg")
blob.save

From Base64

blob = ActiveBlob::Blob.new(
  base64: base64_string,
  content_type: "image/png",
  filename: "image.png"
)
blob.save

From Raw Data

blob = ActiveBlob::Blob.new(data: binary_data)
blob.content_type = "image/jpeg"
blob.save

Accessing Blobs

# Get blob URL
user.avatar.url

# Get blob URL with options
blob.url(disposition: 'inline', filename: 'custom.jpg', expires_in: 3600)

# Get blob metadata
blob.size          # File size in bytes
blob.content_type  # MIME type
blob.sha1          # Binary SHA1 hash
blob.extension     # File extension based on content type

# Open blob for processing
blob.open do |file|
  # Process the file
  puts file.read
end

Blob Types

ActiveBlob automatically selects the appropriate blob type based on content type:

Images (ActiveBlob::Blob::Image)

Requires ruby-vips gem:

gem 'ruby-vips'

Automatically extracts:

  • Width and height
  • Dominant color
  • Background color
  • Aspect ratio
image = ActiveBlob::Blob.new(file: image_file)
image.save

image.width          # => 1920
image.height         # => 1080
image.aspect_ratio   # => 1.777...
image.dominant_color # => "255,128,0"

Videos (ActiveBlob::Blob::Video)

Requires streamio-ffmpeg gem:

gem 'streamio-ffmpeg'

Automatically extracts:

  • Duration
  • Bitrate
  • Codec
  • Width and height
  • Frame rate
video = ActiveBlob::Blob.new(file: video_file)
video.save

video.duration    # => 120.5 (seconds)
video.width       # => 1920
video.height      # => 1080
video.codec       # => "h264"
video.frame_rate  # => 30.0

PDFs (ActiveBlob::Blob::PDF)

Requires pdf-reader gem:

gem 'pdf-reader'

Automatically extracts:

  • Page count
  • Width and height per page
  • Aspect ratio
pdf = ActiveBlob::Blob.new(file: pdf_file)
pdf.save

pdf.page_count    # => 10
pdf.aspect_ratio  # => 0.707 (based on first page)

Deduplication

ActiveBlob automatically deduplicates blobs based on SHA1 hash:

# Upload the same file twice
blob1 = ActiveBlob::Blob.new(file: file)
blob1.save

blob2 = ActiveBlob::Blob.new(file: file)
blob2.save

# blob1 and blob2 reference the same record
blob1.id == blob2.id  # => true

Attachments

Attachments link blobs to your records:

# Create attachment explicitly
attachment = ActiveBlob::Attachment.create(
  record: user,
  blob: blob,
  filename: "avatar.jpg",
  type: "avatar"
)

# Access attachment properties
attachment.filename      # => "avatar.jpg"
attachment.content_type  # => "image/jpeg"
attachment.size          # => 102400
attachment.url           # Delegates to blob.url

Nested Attributes

You can use nested attributes with attachments:

class Post < ApplicationRecord
  has_many_blobs :images
  accepts_nested_attributes_for :images
end

# In your controller
post.update(
  images_attributes: [
    { blob_id: blob.id, filename: "image1.jpg" },
    { blob_id: blob2.id, filename: "image2.jpg" }
  ]
)

Architecture

Models

  • ActiveBlob::Blob: Base model for all blobs

    • ActiveBlob::Blob::Image: Image blobs with metadata extraction
    • ActiveBlob::Blob::Video: Video blobs with metadata extraction
    • ActiveBlob::Blob::PDF: PDF blobs with metadata extraction
  • ActiveBlob::Attachment: Polymorphic join model linking blobs to records

Storage Backends

  • ActiveBlob::Storage::Filesystem: Store blobs on local filesystem
  • ActiveBlob::Storage::S3: Store blobs on S3 or S3-compatible services

Database Schema

Blobs Table

create_table :blobs, id: :uuid do |t|
  t.string :type                           # STI type
  t.bigint :size, null: false              # File size in bytes
  t.string :content_type, null: false      # MIME type
  t.jsonb :metadata, default: {}           # Type-specific metadata
  t.binary :sha1, limit: 20, null: false   # SHA1 hash for deduplication
  t.timestamps
end

add_index :blobs, :sha1
add_index :blobs, [:type, :sha1]

Attachments Table

create_table :attachments, id: :uuid do |t|
  t.string :type                    # Attachment type (e.g., "avatar", "photo")
  t.integer :order, default: 0      # Order for has_many_blobs
  t.string :filename                # Original filename
  t.string :record_type             # Polymorphic record type
  t.uuid :record_id                 # Polymorphic record ID
  t.uuid :blob_id, null: false      # Reference to blob
  t.timestamps
end

add_index :attachments, [:record_type, :record_id]
add_index :attachments, :blob_id
add_index :attachments, [:blob_id, :record_id, :record_type, :type, :filename],
          unique: true

Advanced Usage

Custom Blob Types

Create custom blob types by subclassing ActiveBlob::Blob:

class ActiveBlob::Blob::Audio < ActiveBlob::Blob
  validates :content_type, format: /\Aaudio\/.*\Z/

  def duration
    metadata['duration']
  end

  def self.process(record, path)
    # Extract audio metadata
    # record.metadata = { 'duration' => ..., 'bitrate' => ... }
  end
end

Storage Adapter Interface

Create custom storage adapters by implementing:

class MyStorage
  def local?
    # Return true if storage is local, false otherwise
  end

  def write(id, file, options = {})
    # Write file to storage
  end

  def read(id)
    # Read file from storage
  end

  def delete(id)
    # Delete file from storage
  end

  def exists?(id)
    # Check if file exists
  end

  def url(id, **options)
    # Generate URL for file
  end

  def copy_to_tempfile(id, basename: nil, &block)
    # Copy file to temporary location and yield to block
  end
end

Development

After checking out the repo, run:

bundle install

To run tests:

rails test

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/bemky/activeblob.

License

The gem is available as open source under the terms of the MIT License.