Migrating Guide: v2.0.0-pre11

This version introduces significant improvements to Familia's feature system, making it easier to organize and use features across complex projects.

Enhanced Feature System

Model-Specific Feature Registration

Previously, all features were registered globally. Now you can register features specific to individual model classes, allowing for better organization and namespace management.

Before

# Global feature registration only
module MyProjectFeature
  # Feature implementation
end
Familia::Base.add_feature MyProjectFeature, :my_project_feature

class Customer < Familia::Horreum
  feature :my_project_feature
end

class Session < Familia::Horreum
  feature :my_project_feature  # Same global feature
end

After

# Model-specific feature registration
module CustomerSpecificFeature
  # Feature implementation
end

# Register feature only for Customer and its subclasses
Customer.add_feature CustomerSpecificFeature, :customer_specific

class Customer < Familia::Horreum
  feature :customer_specific  # Available via Customer's registry
end

class PremiumCustomer < Customer
  feature :customer_specific  # Inherited via ancestry chain
end

class Session < Familia::Horreum
  # feature :customer_specific  # Not available - would raise error
end

Benefits:

  • Features can have the same name across different model hierarchies
  • Standardized naming: deprecated_fields.rb instead of customer_deprecated_fields.rb
  • Natural inheritance through Ruby's class hierarchy

SafeDump DSL Improvements

The new DSL replaces the brittle @safe_dump_fields class instance variable pattern with clean, explicit methods.

Before

class Customer < Familia::Horreum
  feature :safe_dump

  # Brittle - hard to move to feature modules, confusing syntax
  @safe_dump_fields = [
    :custid,
    :email,
    { active: ->(obj) { obj.active? } },
    { display_name: ->(obj) { "#{obj.name} (#{obj.custid})" } }
  ]
end

After

class Customer < Familia::Horreum
  feature :safe_dump

  # Clean DSL - easy to understand and organize
  safe_dump_field :custid
  safe_dump_field :email
  safe_dump_field :active, ->(obj) { obj.active? }
  safe_dump_field :display_name, ->(obj) { "#{obj.name} (#{obj.custid})" }

  # Or define multiple fields at once
  safe_dump_fields :created, :updated, { status: ->(obj) { obj.role } }
end

New methods available:

  • safe_dump_field(name, callable = nil) - Define a single field
  • safe_dump_fields(*fields) - Define multiple fields or get field names
  • safe_dump_field_names - Get array of field names
  • safe_dump_field_map - Get the internal callable map

Backward Compatibility:

  • set_safe_dump_fields(*fields) - Legacy setter method (still works)
  • The old @safe_dump_fields pattern is no longer supported

Auto-loading Features

Before: Manual Loading

# customer/features.rb

# Manual feature loading (copied from Familia)
features_dir = File.join(__dir__, 'features')
if Dir.exist?(features_dir)
  Dir.glob(File.join(features_dir, '*.rb')).each do |feature_file|
    require_relative feature_file
  end
end

class Customer < Familia::Horreum
  # Features now available for use
  feature :deprecated_fields
end

After: Automatic Loading

# customer/features.rb

class Customer < Familia::Horreum
  module Features
    include Familia::Features::Autoloader
    # Automatically discovers and loads all *.rb files from customer/features/
  end
end

class Customer < Familia::Horreum
  # Features automatically loaded and available
  feature :deprecated_fields
end

Directory structure this enables:

models/
├── customer/
│   ├── features/
│   │   ├── deprecated_fields.rb      # Standardized names!
│   │   ├── legacy_support.rb
│   │   └── stripe_integration.rb
│   └── features.rb                   # Include Autoloader here
├── session/
│   ├── features/
│   │   ├── deprecated_fields.rb      # Same name, different implementation
│   │   └── expiration_hooks.rb
│   └── features.rb
└── customer.rb

Field Definitions in Feature Modules

Feature modules can now define fields directly in their ClassMethods modules. When a class extends the module, the field definitions execute in the extending class's context.

Example

# features/common_fields.rb

module CommonFields
  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    # These field calls execute in the extending class's context
    field :created
    field :updated
    field :version

    def touch_updated
      self.updated = Familia.now.to_i
    end
  end

  Familia::Base.add_feature self, :common_fields
end

# Usage
class Customer < Familia::Horreum
  feature :common_fields
  # Now has :created, :updated, :version fields and touch_updated class method
end

Migration Steps

1. Update SafeDump Usage

Replace all @safe_dump_fields definitions with the new DSL:

# Find and replace pattern:
# Old: @safe_dump_fields = [:field1, :field2, { field3: ->(obj) { ... } }]
# New: safe_dump_fields :field1, :field2, { field3: ->(obj) { ... } }

# Or use individual field definitions for better readability:
safe_dump_field :field1
safe_dump_field :field2
safe_dump_field :field3, ->(obj) { ... }

2. UnsortedSet Up Auto-loading (Optional)

If you have project-specific features, set up auto-loading:

# Create: models/[model_name]/features.rb
module YourProject
  class ModelName < Familia::Horreum
    module Features
      include Familia::Features::Autoloader
    end
  end
end

# Require this file before your model definitions
require_relative 'model_name/features'

3. Organize Features by Model (Optional)

Consider reorganizing shared feature names by model:

# Before: features/customer_deprecated_fields.rb
# After: models/customer/features/deprecated_fields.rb

# This allows multiple models to have their own deprecated_fields.rb

4. Test Your Changes

Run your test suite to ensure all SafeDump functionality works correctly:

# Verify SafeDump DSL works
model = YourModel.new(field1: 'value')
result = model.safe_dump
puts result.keys  # Should include your defined fields

Breaking Changes

  1. @safe_dump_fields no longer supported - Must migrate to DSL methods
  2. SafeDump field order - Fields are now returned in definition order via Hash keys (Ruby 1.9+ behavior)

New Capabilities Unlocked

  1. Standardized feature names across different models
  2. Cleaner SafeDump definitions that can be easily moved to feature modules
  3. Automatic feature discovery for better project organization
  4. Model-specific feature registries for better namespace management
  5. Field definitions in feature modules for shared functionality