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.
- π Automatic database schema discovery
- ποΈ Dynamic creation of ActiveRecord models
- π Automatic relationship mapping (
has_many
,belongs_to
,has_one
, andhas_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
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
# 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
To use Dynamic Active Model in a Rails application, follow these steps:
- First, configure Rails to handle the
DB
namespace correctly. Add this toconfig/initializers/inflections.rb
:
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym 'DB'
end
- 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
- 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
- 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 nameduser_profiles.ext.rb
, even if the model is namedUserProfile
.
-
The extension files will be automatically loaded and applied to their respective models. For example,
users.ext.rb
will extend theDB::User
model. -
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
dynamic-db-explorer \
--username root \
--adapter postgresql \
--database your_database \
--password your_password \
--create-class-files /path/to/models
Dynamic Active Model automatically detects and creates four types of relationships:
belongs_to
- Created when a table has a foreign key columnhas_many
- Created when another table has a foreign key pointing to this tablehas_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
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.
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!
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!
db.update_model(:users) do
attr_accessor :temp_password
def full_name
"#{first_name} #{last_name}"
end
end
# 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')
# Apply all .ext.rb files from a directory
db.update_all_models('lib/db')
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
}
- Always use a dedicated namespace for dynamic models to avoid conflicts
- Use table filtering for large databases to improve performance
- Keep model extensions modular and focused
- Follow Ruby naming conventions in your extensions
- Consider using whitelisting in production environments
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
This project is licensed under the MIT License - see the LICENSE.txt file for details.
If you discover any issues or have questions, please create an issue.