thecore_generators

Part of the Thecore framework.

Rails-native generators for Thecore 3 apps and ATOMs — replacing the scaffolding logic currently duplicated in the Thecore VS Code extension. Wherever Rails already has a native generator command to override (rails generate model/migration), this gem hooks it instead of inventing new vocabulary; operations with no Rails-native equivalent (ATOM creation, action scaffolding, app bootstrapping) get their own thecore:*-namespaced generators or an application template. See docs/adr/0002-thecore-generators-gem-and-generator-hook-mechanism.md in the thecore repo for the full design.

Status: Model + Migration generator hook (Phase 1 of ADR 0002). ThecoreGenerators::Railtie registers config.app_generators.orm :thecore, migration: true, timestamps: true, so plain rails generate model/rails generate migration transparently apply thecore's scaffolding conventions — no new command vocabulary.

What rails generate model/rails generate migration do now

  • Context-aware placement. Thecore::Generators::WorkspaceContext detects whether the invoking process's Dir.pwd is inside a host app or an ATOM (vendor/submodules/<atom>/, by gemspec presence — a Ruby port of thecore_code_extension's workspaceContext.js). When an ATOM is detected, the model/migration/test files land inside that ATOM's own app/models/db/migrate/test instead of the host app's. Pass --atom=NAME to override detection explicitly (works independent of cwd, e.g. from CI or the host-app root).

  • No concern files by default. Api::ModelName/RailsAdmin::ModelName concern files are not generated (per ADR 0001) — the no-customization case relies entirely on the default json_attrs/navigation_label/ navigation_icon behavior that model_driven_api and thecore_ui_rails_admin include into every ApplicationRecord subclass automatically (ThecoreBackendCommons::DefaultModuleRegistry). Pass --with-api-concern and/or --with-admin-concern to scaffold a starter concern file — identical in shape to what this generator produced before this default changed — for the case where customization is already known to be needed at generation time:

    rails generate model Foo name:string --with-api-concern --with-admin-concern
    

    See "Adding a concern by hand" below for the (more common) case of realizing customization is needed after the model already exists.

  • No Endpoints::ModelName by default (per ADR 0001) — add one by hand, following the after_initialize + class_eval pattern, only when a real custom action is needed.

  • Test file generation is never suppressed — a real Minitest file is generated, same as Rails' own active_record:model default.

  • rails generate active_record:model/active_record:migration still work directly as an escape hatch, entirely unaffected by the hook above.

Both Thecore::Generators::ModelGenerator and MigrationGenerator wrap (not reimplement) ActiveRecord::Generators::ModelGenerator/MigrationGenerator — all attribute parsing and template content is inherited as-is; only file placement and the two opt-in concerns are added on top.

Adding a concern by hand

The common case is not knowing at rails generate model time that a model will need custom API serialization or RailsAdmin configuration — that need usually surfaces later. Since neither concern is generated by default, add the missing one directly instead of regenerating the model:

Api::ModelName (custom json_attrs) — create app/models/concerns/api/model_name.rb:

module Api::ModelName
  extend ActiveSupport::Concern

  included do
    cattr_accessor :json_attrs
    self.json_attrs = ::ModelDrivenApi.smart_merge(json_attrs || {}), { only: [:id, :name] }
  end
end

then include Api::ModelName in the model. Because the default module (model_driven_api's ModelDrivenApiDefaultJsonAttrs) is already included by the time the model class body runs, ::ModelDrivenApi.smart_merge(json_attrs || {}, ...) composes on top of it rather than starting from nothing — the same pattern the opt-in --with-api-concern template below uses.

RailsAdmin::ModelName (custom admin config) — create app/models/concerns/rails_admin/model_name.rb:

module RailsAdmin::ModelName
  extend ActiveSupport::Concern

  included do
    rails_admin do
      navigation_label I18n.t('admin.registries.label')
      navigation_icon 'fa fa-file' # see https://fontawesome.com/v5/search
      configure :some_field do
        hide
      end
    end
  end
end

then include RailsAdmin::ModelName in the model. RailsAdmin evaluates same-origin rails_admin do ... end blocks in registration order and later calls win on settings they touch (navigation_label/navigation_icon are last-write-wins setters) — so this explicit block, included after the default from the class body, overrides the default's navigation_label/navigation_icon while the default itself keeps applying to every other model that has no concern of its own.

Either concern can be added independently — a model doesn't need both just because it needs one.

Installation

Add to your host app's or ATOM's Gemfile:

gem "thecore_generators", "~> 3.0"

Running tests locally

Tests use a Rails::Generators::TestCase-based harness against the test/dummy Rails app included in this repo (needed to exercise generators the way a real host app would). test/dummy also boots real model_driven_api/thecore_ui_rails_admin (and their own transitive thecore_backend_commons/thecore_auth_commons dependencies) as temporary git-based dependencies — see the Gemfile's comment — purely so test/generators/thecore/model_generator_default_concern_behavior_test.rb can prove the no-concern default actually works at runtime, not just that no file was written.

bundle install
bundle exec rake test

If your shell has DATABASE_URL set to a PostgreSQL URL (e.g. inside the Thecore devcontainer), unset it first — it overrides test/dummy's own SQLite3 test config:

env -u DATABASE_URL bundle exec rake test

bundle exec rake alone runs the same suite (test is the default Rake task).

To run a single test file:

bundle exec ruby -Itest test/generators/thecore/model_generator_test.rb

Releasing

Version lives in lib/thecore_generators/version.rb. Pushing a commit that bumps it triggers .github/workflows/gempush.yml, which tags the commit with that version and publishes to RubyGems (skipped if the tag already exists) — the same pattern used by the other gems in this ecosystem (model_driven_api, thecore_backend_commons, etc.).

License

MIT — see MIT-LICENSE.