solnic/virtus
[DISCONTINUED ] Attributes on Steroids for Plain Old Ruby Objects
Healthy across all four use cases
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓19 active contributors
- ✓MIT licensed
- ✓CI configured
Show 3 more →Show less
- ✓Tests present
- ⚠Stale — last commit 5y ago
- ⚠Concentrated ownership — top contributor handles 69% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/solnic/virtus)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/solnic/virtus on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: solnic/virtus
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/solnic/virtus shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- 19 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Stale — last commit 5y ago
- ⚠ Concentrated ownership — top contributor handles 69% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live solnic/virtus
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/solnic/virtus.
What it runs against: a local clone of solnic/virtus — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in solnic/virtus | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 1763 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of solnic/virtus. If you don't
# have one yet, run these first:
#
# git clone https://github.com/solnic/virtus.git
# cd virtus
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of solnic/virtus and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "solnic/virtus(\\.git)?\\b" \\
&& ok "origin remote is solnic/virtus" \\
|| miss "origin remote is not solnic/virtus (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "lib/virtus.rb" \\
&& ok "lib/virtus.rb" \\
|| miss "missing critical file: lib/virtus.rb"
test -f "lib/virtus/attribute.rb" \\
&& ok "lib/virtus/attribute.rb" \\
|| miss "missing critical file: lib/virtus/attribute.rb"
test -f "lib/virtus/attribute/coercer.rb" \\
&& ok "lib/virtus/attribute/coercer.rb" \\
|| miss "missing critical file: lib/virtus/attribute/coercer.rb"
test -f "lib/virtus/model.rb" \\
&& ok "lib/virtus/model.rb" \\
|| miss "missing critical file: lib/virtus/model.rb"
test -f "lib/virtus/builder.rb" \\
&& ok "lib/virtus/builder.rb" \\
|| miss "missing critical file: lib/virtus/builder.rb"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 1763 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1733d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/solnic/virtus"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Virtus is a Ruby gem that adds attribute management with automatic type coercion to plain Ruby objects, enabling declarative definition of typed properties with built-in validation, mass-assignment, and conversion logic. It solves the problem of safely mapping external data (JSON, form inputs, database rows) into domain objects with type safety and minimal boilerplate, acting as a lightweight alternative to full ORMs for value objects and data transfer objects. Modular single-gem structure: lib/virtus/ contains core components with attribute/ subdirectory for type-specific logic (coercers, accessors, collections), builder/ for attribute assembly, and support utilities in support/. Class methods injected via class_methods.rb and instance_methods.rb mixins; attribute coercion driven by coercer.rb and type-specific classes like attribute/boolean.rb, attribute/hash.rb, and attribute/collection.rb.
👥Who it's for
Ruby developers building domain-driven designs, web applications handling JSON payloads, or anyone needing to sanitize and coerce input parameters without Rails ActiveRecord. Specifically useful for architects prototyping models, creating value objects, or building data-access layers where type safety matters but a full ORM is overkill.
🌱Maturity & risk
DISCONTINUED as of 2017. The project is stable and production-tested (178KB of well-organized Ruby code, full CI via Travis, test coverage tracked), but the maintainer explicitly recommends migrating to dry-types, dry-struct, and dry-schema instead. No active development; suitable for legacy maintenance only.
CRITICAL: Project is officially discontinued and unsupported—no security updates or bug fixes will be released. Single maintainer (solnic) with no activity for 6+ years. Depends on outdated gems (check Gemfile for exact versions); Ruby version compatibility beyond 2.x is uncertain. High risk for new projects; only accept in mature codebases already running this gem.
Active areas of work
Nothing—project is in maintenance-only mode. Last meaningful commits were years ago. No active PRs or development roadmap. Recommend reviewing Changelog.md for historical context and migrating to dry-rb successors.
🚀Get running
git clone https://github.com/solnic/virtus.git
cd virtus
bundle install
bundle exec rspec
Run tests with bundle exec rspec to verify local setup. Use Bundler (Gemfile present) for dependency management.
Daily commands:
This is a library, not an application. For development: bundle exec rspec runs tests. For usage in your own project: gem 'virtus' in Gemfile, then include Virtus.model in your class and define attributes. See spec/integration/ for working examples.
🗺️Map of the codebase
lib/virtus.rb— Main entry point that includes all core modules and initializes the gem; every developer must understand what gets loaded on require.lib/virtus/attribute.rb— Core Attribute class defining the foundation for all typed properties; essential to understand how attributes are modeled and coerced.lib/virtus/attribute/coercer.rb— Central type coercion engine that converts input values to target types; critical for understanding attribute value transformation logic.lib/virtus/model.rb— Primary module that mixes into classes to provide attribute declaration and instance-level functionality; the main public API.lib/virtus/builder.rb— Orchestrates attribute building and class extension during module inclusion; controls the initialization pipeline.lib/virtus/attribute_set.rb— Manages the collection of defined attributes for a class; provides attribute lookup and enumeration.lib/virtus/class_methods.rb— Provides theattributeDSL method for declaring typed properties; exposed to users as the primary configuration API.
🛠️How to make changes
Add a Custom Attribute Type
- Create a new attribute class in lib/virtus/attribute/ inheriting from Attribute::Coercible or Attribute. (
lib/virtus/attribute/custom_type.rb) - Implement the
coerce(value)method to define how values are transformed to your type. (lib/virtus/attribute/custom_type.rb) - Register the new attribute type in lib/virtus/attribute/builder.rb by adding a case branch in the
build_attributemethod. (lib/virtus/attribute/builder.rb) - Users can now declare the attribute:
attribute :my_field, MyCustomType(lib/virtus/class_methods.rb)
Add a New Attribute Modifier (like nullify_blank or lazy_default)
- Create a module in lib/virtus/attribute/ that implements the desired behavior (e.g., overriding
coerceorset). (lib/virtus/attribute/my_modifier.rb) - Add the module to Attribute's inclusion chain in lib/virtus/attribute/coercible.rb or directly in lib/virtus/attribute.rb. (
lib/virtus/attribute.rb) - Support the modifier option in lib/virtus/attribute/builder.rb by checking options and applying the mixin. (
lib/virtus/attribute/builder.rb) - Users declare:
attribute :field, String, my_modifier: true(lib/virtus/class_methods.rb)
Implement Custom Coercion Logic for a Third-Party Type
- Register a custom coercer in lib/virtus/coercer.rb by adding a method or modifying the coercion hierarchy. (
lib/virtus/coercer.rb) - Define coercion logic:
coerce_<typename>that handles conversion to your target type. (lib/virtus/coercer.rb) - Alternatively, extend lib/virtus/configuration.rb to inject custom coercers during setup. (
lib/virtus/configuration.rb) - Use in models:
attribute :field, MyCustomClassand the coercer automatically handles the conversion. (lib/virtus/class_methods.rb)
🔧Why these technologies
- Ruby metaprogramming (define_method, method_missing, include) — Enables dynamic attribute declaration and runtime accessor generation without boilerplate; core to the DSL design pattern.
- Module mixins (Coercible, DefaultValue, LazyDefault, etc.) — Allows composable attribute behaviors; modifiers can be mixed into Attribute classes to avoid tight coupling and inheritance chains.
- Coercion engine (type hierarchy matching) — Automatically converts input values to declared types; reduces boilerplate validation and casting in application code.
⚖️Trade-offs already made
-
Support both strict type checking and lenient coercion
- Why: Different use cases: strict for APIs, lenient for user input (forms, CLI).
- Consequence: Adds complexity in Attribute subclasses and Coercer; developers must choose mode per-attribute.
-
Attribute definitions as class-level declarations (not runtime instance configuration)
- Why: Simplicity and performance: attributes defined once at class load time, not per-instance.
- Consequence: Cannot dynamically add attributes to individual instances; must use separate instances or sub-classes.
-
Eager builder hook context vs. lazy initialization
- Why: Eager setup ensures all attributes are resolved before object instantiation for predictable behavior.
- Consequence: Class definition takes longer; slightly higher memory overhead for attribute metadata.
-
Embedded values and collections recursively coerce nested objects
- Why: Enables rich domain models with deeply typed structures; mirrors typical ORM behavior.
- Consequence: Performance overhead on deep nesting; potential infinite recursion if circular types declared.
🚫Non-goals (don't propose these)
- Does not provide validation (separate concern; use dry-schema or ActiveModel::Validations).
- Does not persist data or interact with databases (ORM is out of scope).
- Does not generate schemas or API documentation automatically.
- Does not handle real-time or streaming attributes.
- Project is discontinued; no new features planned (users directed to dry-types, dry-struct, dry-schema).
🪤Traps & gotchas
No hidden environment variables or services required. Key gotchas: (1) Default values must be explicitly callable or clonable—simple mutables like {} will share state across instances unless wrapped in a Proc. See attribute/default_value/ for handling. (2) Custom coercers must be registered in Virtus::Coercer before use; no auto-discovery. (3) Boolean coercion is non-standard (see attribute/boolean.rb): only false and nil coerce to false; any other value (including empty string) is true. (4) Requires Ruby 1.9.3+; exact upper bound uncertain due to age. (5) Mass-assignment bypasses accessor visibility (private setters are not enforced).
🏗️Architecture
💡Concepts to learn
- Attribute-based object composition — Virtus's core pattern—defining object structure as declarative attribute metadata rather than instance variables—underpins how coercion and mass-assignment work; understanding this is essential to extending Virtus with custom attributes.
- Type coercion chains — Virtus coerces input values through a chain of type-specific handlers (String→Integer→Enumerable, etc.); understanding the Coercer pipeline in lib/virtus/attribute/coercer.rb is critical for debugging why '42' becomes 42 or why custom types fail silently.
- Builder pattern (AttributeBuilder) — Virtus uses builders to construct Attribute instances from DSL declarations; the builder assembles type lookups, coercers, defaults, and accessors based on options—understanding this is key to adding new attribute behaviors or extending the DSL.
- Metaprogramming via module inclusion — Virtus injects class and instance methods into target classes via
include Virtus.model; grasping how module_eval, class_methods, and instance_methods mixins work is essential for understanding how the attribute DSL becomes available and how accessors are generated. - Default value factories (callable vs. clonable) — Virtus distinguishes between Proc/callable defaults (evaluated per-instance) and clonable defaults (Array, Hash, duplicated per-instance) to avoid shared mutable state; misunderstanding this leads to subtle bugs where all instances share the same default Array or Hash.
- Value Object pattern — One of Virtus's primary use cases; Virtus.value_object in lib/virtus/value_object.rb provides immutable attributes and equality semantics—understanding when and how to use value objects shapes how you model data in Virtus-based code.
- Embedded and collection object mapping — Virtus's advanced feature (attribute/embedded_value.rb and attribute/collection.rb) allows recursive coercion of nested objects (JSON→nested domain objects) and typed collections; this is what differentiates Virtus from simple property setters and requires understanding both coercer composition and recursive attribute building.
🔗Related repos
dry-rb/dry-struct— Official successor to Virtus; provides the same struct-like attributes with type safety but with better separation of concerns and dry-types integrationdry-rb/dry-types— Companion project; handles all type coercion and composition that Virtus baked into attributes, now as standalone, composable type objectsrom-rb/rom— Related data-mapping library in the Ruby ecosystem; provides alternative ORM-lite approach for mapping relational data to objectssinatra/sinatra— Common pairing: Virtus used for sanitizing and coercing request parameters in lightweight Sinatra web apps before passing to domain objectsruby/gem_rbs_collection— Modern type-safety alternative via Ruby type annotations (.rbs files); newer approach to the same type-safety problem Virtus solved via runtime coercion
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive unit tests for lib/virtus/attribute/builder.rb
The AttributeBuilder class is a critical component that handles attribute instantiation and configuration, but there are no dedicated unit tests in spec/unit for this file. Currently, builder behavior is only tested through integration tests. This creates a gap in test coverage for edge cases like custom attribute types, conflicting options, and builder state management. Adding focused unit tests would improve debugging and make future refactoring safer.
- [ ] Create spec/unit/virtus/attribute/builder_spec.rb
- [ ] Add tests for attribute type resolution with custom coercers
- [ ] Add tests for default value handling in builder initialization
- [ ] Add tests for accessor generation with various option combinations
- [ ] Add tests for error cases (invalid options, missing dependencies)
- [ ] Run full test suite to ensure no regressions
Add integration tests for lib/virtus/coercer.rb type coercion edge cases
The Coercer class handles type coercion across the library, but spec/integration lacks dedicated tests for coercion behavior with nested custom objects, circular references, and non-standard Ruby types (Rational, Complex, etc.). Current tests focus on basic types. Adding comprehensive coercion tests would improve reliability when users integrate Virtus with libraries like Sequel or ROM that rely on coercion.
- [ ] Create spec/integration/custom_coercer_types_spec.rb
- [ ] Add tests for coercion with Rational, Complex, and Set types
- [ ] Add tests for coercion behavior with frozen objects
- [ ] Add tests for custom to_proc coercers with Virtus attributes
- [ ] Add tests for coercion error handling and fallback behavior
- [ ] Document edge cases in CONTRIBUTING.md
Add missing unit tests for lib/virtus/attribute/default_value/ submodules
The default_value directory contains three specialized strategies (from_callable.rb, from_clonable.rb, from_symbol.rb), but there are no dedicated unit tests for each strategy. DefaultValue behavior is tested only through integration/default_values_spec.rb. Adding unit tests for each strategy would improve code clarity, catch edge cases (e.g., callable that raises, symbol resolution failures), and make it easier to maintain or extend default value behavior.
- [ ] Create spec/unit/virtus/attribute/default_value/from_callable_spec.rb
- [ ] Create spec/unit/virtus/attribute/default_value/from_clonable_spec.rb
- [ ] Create spec/unit/virtus/attribute/default_value/from_symbol_spec.rb
- [ ] Add tests for handling callables that raise exceptions
- [ ] Add tests for cloning immutable objects (frozen strings, symbols)
- [ ] Add tests for symbol resolution with missing methods
- [ ] Run full test suite and update spec/integration/default_values_spec.rb if needed
🌿Good first issues
- Add comprehensive coercion tests for
attribute/hash.rb—the file exists but spec/integration lacks a dedicated Hash coercion integration test similar to collection_member_coercion_spec.rb; write one demonstrating nested hash type coercion and key/value type safety. - Document the Virtus::Coercer extension point: the lib/virtus/coercer.rb class is the public API for adding custom type coercers, but README.md has no examples. Add a 'Custom Coercers' section with a concrete example (e.g., coercing a String to a custom Money object).
- Extract and test the Virtus::Support::TypeLookup logic separately: lib/virtus/support/type_lookup.rb handles String type name resolution (e.g., 'String' → String constant), but has no dedicated spec file. Write spec/unit/virtus/support/type_lookup_spec.rb covering edge cases like namespaced types and missing constants.
⭐Top contributors
Click to expand
Top contributors
- @solnic — 69 commits
- @booch — 5 commits
- @novikserg — 5 commits
- @envygeeks — 4 commits
- [@Blanka Kulik](https://github.com/Blanka Kulik) — 2 commits
📝Recent commits
Click to expand
Recent commits
fce56bd— Update Changelog.md (solnic)9e4f56f— Update Changelog (solnic)671c770— Replace Equalizer with Virtus::Equalizer (solnic)bff73b0— Update Changelog (solnic)0843384— Bump virtus to 2.0.0 (solnic)ae497dd— Add simplecov (solnic)1167fef— Update CC reporting on travis (solnic)3783485— Run CC coverage under 3.0 (solnic)eeb15c8— Remove deps that are not critical to run the test suite (solnic)5a3776f— Re-add 2.0 to travis and set >= 2.0 as min in gemspec (solnic)
🔒Security observations
The virtus gem presents significant security concerns primarily due to its discontinued and unmaintained status. No active security patches will be provided for discovered vulnerabilities. Additionally, the dynamic nature of type coercion, attribute handling, and callable/symbol defaults introduce potential attack vectors that require careful review. The codebase lacks modern security practices and documentation. Users should prioritize migration to actively maintained successor projects (dry-types, dry-struct, dry-schema). If continued use is unavoidable, comprehensive security auditing and compensating controls are essential.
- High · Discontinued Project with No Active Maintenance —
Project root / README.md. The virtus gem is officially discontinued and no longer maintained. The project maintainer has explicitly stated that dry-types, dry-struct, and dry-schema should be used as successors. Continued use of abandoned software poses significant security risks as vulnerabilities will not be patched. Fix: Migrate to actively maintained successor projects: dry-types, dry-struct, or dry-schema. If migration is not immediately possible, conduct a thorough security audit of the codebase and implement compensating controls. - Medium · Dynamic Code Evaluation Patterns —
lib/virtus/support/type_lookup.rb, lib/virtus/attribute/coercer.rb, lib/virtus/attribute/builder.rb. The codebase contains attribute coercion and type handling logic that performs dynamic type lookups and instantiation (lib/virtus/support/type_lookup.rb, lib/virtus/attribute/coercer.rb). Without proper input validation, this could potentially be exploited for arbitrary code execution through specially crafted attribute values. Fix: Review and audit all dynamic type resolution and coercion logic. Implement strict whitelist validation for allowed types. Ensure no user input can directly influence type resolution without sanitization. - Medium · Unsafe Callable and Symbol Default Values —
lib/virtus/attribute/default_value/from_callable.rb, lib/virtus/attribute/default_value/from_symbol.rb. The attribute system supports callable and symbol-based default values (lib/virtus/attribute/default_value/from_callable.rb, lib/virtus/attribute/default_value/from_symbol.rb). If user input can influence which callables or symbols are used as defaults, this could lead to unintended method invocation or object instantiation. Fix: Restrict callable and symbol defaults to predefined, safe options only. Implement strict validation that user input cannot introduce arbitrary callables or symbols as defaults. - Medium · Potential Mass Assignment Vulnerability —
lib/virtus/instance_methods.rb, lib/virtus/class_methods.rb, spec/integration/mass_assignment_with_accessors_spec.rb. The codebase implements mass assignment functionality (evidenced by spec files like mass_assignment_with_accessors_spec.rb and instance_level_attributes_spec.rb). Without proper attribute whitelisting, mass assignment could allow attackers to set unintended attributes. Fix: Ensure all mass assignment implementations use explicit attribute whitelisting. Do not allow arbitrary attributes to be set through mass assignment. Document the secure usage patterns clearly. - Low · Missing Dependency Information —
Gemfile, gemspec file (not provided). The provided context does not include the Gemfile or gemspec file contents, making it impossible to assess whether dependencies themselves contain known vulnerabilities. Fix: Run 'bundle audit' regularly to check for known vulnerabilities in dependencies. Maintain an up-to-date dependency inventory and monitor security advisories. - Low · No Security Configuration Files Detected —
Project root. There is no evidence of security-focused configuration files such as SECURITY.md, security policies, or vulnerability disclosure guidelines. Fix: Create a SECURITY.md file outlining the project's security policy and vulnerability disclosure process. Given that this is a discontinued project, clearly communicate the end-of-life status and direct users to maintained alternatives.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.