Skip to content

dougyouch/dynamic-active-model

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Build Status Code Coverage Maintainability

Dynamic Active Model

Dynamic Active Model is a powerful Ruby gem that automatically discovers your database schema and creates corresponding ActiveRecord models with proper relationships. It's perfect for rapid prototyping, database exploration, and working with legacy databases.

Features

  • πŸ” Automatic database schema discovery
  • πŸ—οΈ Dynamic creation of ActiveRecord models
  • πŸ”— Automatic relationship mapping (has_many, belongs_to, has_one, and has_and_belongs_to_many)
  • ⚑ In-memory model creation for quick exploration
  • πŸ“ Physical model file generation
  • πŸ› οΈ Customizable model extensions
  • βš™οΈ Flexible table filtering (blacklist/whitelist)
  • πŸ”’ Safe handling of dangerous attribute names
  • πŸ”‘ Automatic has_one detection based on unique constraints
  • 🀝 Automatic has_and_belongs_to_many detection for join tables

Installation

Add this line to your application's Gemfile:

gem 'dynamic-active-model'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install dynamic-active-model

Quick Start

In-Memory Model Creation

# Define your database model namespace
module DB; end

# Create models and relationships in one step
DynamicActiveModel::Explorer.explore(DB,
  username: 'root',
  adapter: 'postgresql',
  database: 'your_database',
  password: 'your_password'
)

# Start using your models
movie = DB::Movies.first
movie.name
movie.actors  # Automatically mapped relationship

Using in a Rails Application

To use Dynamic Active Model in a Rails application, follow these steps:

  1. First, configure Rails to handle the DB namespace correctly. Add this to config/initializers/inflections.rb:
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
  inflect.acronym 'DB'
end
  1. To avoid eager loading issues, add this to config/application.rb:
# config/application.rb
module YourApp
  class Application < Rails::Application
    # ... other configuration ...

    # Ignore the DB namespace for eager loading
    Rails.autoloaders.main.ignore(
      "#{config.root}/app/models/db"
    )
  end
end
  1. Create a base module file in app/models/db.rb:
# app/models/db.rb
module DB
  include DynamicActiveModel::Setup

  # Use the primary database connection from database.yml
  connection_options 'primary'

  # Set the path for auto-loading extension files
  extensions_path 'app/models/db'

  # Optionally skip tables you don't want to model
  skip_tables ['schema_migrations', 'ar_internal_metadata']

  # Create all models
  create_models!
end
  1. To extend specific models, create extension files in app/models/db/ with the .ext.rb suffix:
# app/models/db/users.ext.rb
update_model do
  def full_name
    "#{first_name} #{last_name}"
  end

  def active?
    status == 'active'
  end
end

Note: Extension files are based on the table name, not the model name. For example, if you have a table named user_profiles, the extension file should be named user_profiles.ext.rb, even if the model is named UserProfile.

  1. The extension files will be automatically loaded and applied to their respective models. For example, users.ext.rb will extend the DB::User model.

  2. You can now use your models throughout your Rails application:

# In a controller
class UsersController < ApplicationController
  def index
    @users = DB::User.all
  end

  def show
    @user = DB::User.find(params[:id])
    @full_name = @user.full_name  # Using the extended method
  end
end

Generate Model Files

dynamic-db-explorer \
  --username root \
  --adapter postgresql \
  --database your_database \
  --password your_password \
  --create-class-files /path/to/models

Advanced Usage

Relationship Types

Dynamic Active Model automatically detects and creates four types of relationships:

  1. belongs_to - Created when a table has a foreign key column
  2. has_many - Created when another table has a foreign key pointing to this table
  3. has_one - Automatically detected when:
    • A table has a foreign key with a unique constraint
    • A table has a unique key constraint that another table references
  4. has_and_belongs_to_many - Automatically detected when:
    • A join table exists with exactly two columns
    • Both columns are foreign keys to other tables
    • The join table has no primary key
    • The table name follows Rails conventions (alphabetically ordered plural model names)

Example of automatic has_and_belongs_to_many detection:

# Table: actors
#   - id (primary key)
#   - name

# Table: movies
#   - id (primary key)
#   - title

# Table: actors_movies (join table)
#   - actor_id (foreign key to actors.id)
#   - movie_id (foreign key to movies.id)
#   - No primary key

# Results in:
class Actor < ActiveRecord::Base
  has_and_belongs_to_many :movies
end

class Movie < ActiveRecord::Base
  has_and_belongs_to_many :actors
end

Note: If a join table has additional columns or a primary key, it will be treated as a regular model with has_many/belongs_to relationships instead.

Table Filtering

Blacklist (Skip) Tables

db = DynamicActiveModel::Database.new(DB, database_config)

# Skip specific tables
db.skip_table 'temporary_data'
db.skip_table /^temp_/
db.skip_tables ['old_data', /^backup_/]

db.create_models!

Whitelist Tables

db = DynamicActiveModel::Database.new(DB, database_config)

# Include only specific tables
db.include_table 'users'
db.include_table /^customer_/
db.include_tables ['orders', 'products']

db.create_models!

Extending Models

Inline Extensions

db.update_model(:users) do
  attr_accessor :temp_password

  def full_name
    "#{first_name} #{last_name}"
  end
end

File-based Extensions

# lib/db/users.ext.rb
update_model do
  attr_accessor :temp_password

  def full_name
    "#{first_name} #{last_name}"
  end
end

# Apply the extension
db.update_model(:users, 'lib/db/users.ext.rb')

Mass Update All Models

# Apply all .ext.rb files from a directory
db.update_all_models('lib/db')

Configuration

Database Connection

The gem supports all ActiveRecord database adapters. Here's a typical configuration:

{
  adapter: 'postgresql',  # or 'mysql2', 'sqlite3', etc.
  host: 'localhost',
  database: 'your_database',
  username: 'your_username',
  password: 'your_password',
  port: 5432            # optional
}

Best Practices

  1. Always use a dedicated namespace for dynamic models to avoid conflicts
  2. Use table filtering for large databases to improve performance
  3. Keep model extensions modular and focused
  4. Follow Ruby naming conventions in your extensions
  5. Consider using whitelisting in production environments

Documentation

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

License

This project is licensed under the MIT License - see the LICENSE.txt file for details.

Support

If you discover any issues or have questions, please create an issue.

About

Dynamically create ActiveRecord models for tables

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published