thecore_generators 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f829a0f19f22655ce14f10be2c9c748dd67acc8a9432aced696cd9ca3ba7f8a
4
- data.tar.gz: '08b6468f673411496eab954f7d2511c4345242aa5aa1a020d7a3860ad42580bc'
3
+ metadata.gz: 6e0af52a7399d45699c4b84d80cead588d71f37e255bfa324141d8a0033aa561
4
+ data.tar.gz: 241b9957c4a166c010d78c5f1175b98bc802eb28f4757131fc69d1c1d4e24e2d
5
5
  SHA512:
6
- metadata.gz: 92ad6a86068b45d71a7a077cee5c083fb893724767314bb49d914a567cd09b4708091c97fa7d5772470e4f37bd068447f63b9b600081807fa73ed842455f9353
7
- data.tar.gz: 1e5430b20ea00057c7dea7b48736c31f2cbc64dc236550fc5957d90874dd3d42b1435968fb996bfcb2022ad9d3b7fe4b4cf79bcf6f4bd5b1fad358a9c0388cfa
6
+ metadata.gz: 736b763f3dbf6508d97e14b934240b2420db7da18b5e0eda0fd1c6c987e76e26ff3c36ea6a0c3c9d03ed6bf89fef1be5ad631303d38978fc52df021d0c717002
7
+ data.tar.gz: 32bb39ef9cdc30d2023986db40e487a36869555ba3ac02582bb8ba9d89622d1030ed4ce2f9be8b043cf2e521dac1f08f3c1805c4ef389a092c362b157952164d
data/README.md CHANGED
@@ -11,10 +11,37 @@ own `thecore:*`-namespaced generators or an application template. See
11
11
  [`docs/adr/0002-thecore-generators-gem-and-generator-hook-mechanism.md`](https://github.com/gabrieletassoni/thecore/blob/release/3/docs/adr/0002-thecore-generators-gem-and-generator-hook-mechanism.md)
12
12
  in the thecore repo for the full design.
13
13
 
14
- **Status:** bootstrap only. This release makes the gem buildable, testable, and
15
- publishable — it ships no generator behaviour yet. `ThecoreGenerators::Railtie`
16
- (`lib/thecore_generators/railtie.rb`) is a deliberate no-op; the `config.app_generators.orm`
17
- hook lands in a follow-up ticket.
14
+ **Status:** Model + Migration generator hook (Phase 1 of
15
+ [ADR 0002](https://github.com/gabrieletassoni/thecore/blob/release/3/docs/adr/0002-thecore-generators-gem-and-generator-hook-mechanism.md)).
16
+ `ThecoreGenerators::Railtie` registers `config.app_generators.orm :thecore, migration:
17
+ true, timestamps: true`, so plain `rails generate model`/`rails generate migration`
18
+ transparently apply thecore's scaffolding conventions — no new command vocabulary.
19
+
20
+ ### What `rails generate model`/`rails generate migration` do now
21
+
22
+ - **Context-aware placement.** `Thecore::Generators::WorkspaceContext` detects whether
23
+ the invoking process's `Dir.pwd` is inside a host app or an ATOM (`vendor/submodules/<atom>/`,
24
+ by gemspec presence — a Ruby port of `thecore_code_extension`'s `workspaceContext.js`).
25
+ When an ATOM is detected, the model/migration/test files land inside that ATOM's own
26
+ `app/models`/`db/migrate`/`test` instead of the host app's. Pass `--atom=NAME` to
27
+ override detection explicitly (works independent of `cwd`, e.g. from CI or the host-app
28
+ root).
29
+ - **Default concerns, unchanged.** `Api::ModelName`/`RailsAdmin::ModelName` concern files
30
+ are generated and `include`d into the model, exactly as `thecore_code_extension`'s
31
+ `addModel.js` templates do today.
32
+ - **No `Endpoints::ModelName` by default** (per
33
+ [ADR 0001](https://github.com/gabrieletassoni/thecore/blob/release/3/docs/adr/0001-application-record-defaults-over-generated-concerns.md)) —
34
+ add one by hand, following the `after_initialize` + `class_eval` pattern, only when a
35
+ real custom action is needed.
36
+ - **Test file generation is never suppressed** — a real Minitest file is generated, same
37
+ as Rails' own `active_record:model` default.
38
+ - **`rails generate active_record:model`/`active_record:migration` still work directly**
39
+ as an escape hatch, entirely unaffected by the hook above.
40
+
41
+ Both `Thecore::Generators::ModelGenerator` and `MigrationGenerator` wrap (not reimplement)
42
+ `ActiveRecord::Generators::ModelGenerator`/`MigrationGenerator` — all attribute parsing and
43
+ template content is inherited as-is; only file placement and the two default concerns are
44
+ added on top.
18
45
 
19
46
  ## Installation
20
47
 
@@ -39,7 +66,7 @@ bundle exec rake test
39
66
  To run a single test file:
40
67
 
41
68
  ```bash
42
- bundle exec ruby -Itest test/generators/placeholder_generator_test.rb
69
+ bundle exec ruby -Itest test/generators/thecore/model_generator_test.rb
43
70
  ```
44
71
 
45
72
  ## Releasing
@@ -0,0 +1,59 @@
1
+ require "generators/thecore/workspace_context"
2
+
3
+ module Thecore
4
+ module Generators
5
+ # Shared by Thecore::Generators::ModelGenerator and MigrationGenerator:
6
+ # redirects file placement into an ATOM directory when
7
+ # Thecore::Generators::WorkspaceContext detects one (from `Dir.pwd` or an
8
+ # explicit `--atom=NAME`), and otherwise leaves the wrapped ActiveRecord
9
+ # generator's own placement (relative to `destination_root`, already the
10
+ # host app root for a real `rails generate` invocation) untouched.
11
+ #
12
+ # Two distinct placement mechanisms need covering, since ActiveRecord's
13
+ # generators don't derive both from `destination_root`:
14
+ # - Model/module/test files are `template`d at paths relative to
15
+ # `destination_root` — overriding `destination_root` itself (in
16
+ # `initialize`) redirects all of these, including the file the
17
+ # inherited `hook_for :test_framework` generates, since Thor's
18
+ # `_shared_configuration` passes the (already-overridden)
19
+ # `destination_root` on to hooked generators automatically.
20
+ # - The migration file's directory instead comes from
21
+ # `ActiveRecord::Generators::Migration#db_migrate_path`, computed from
22
+ # `Rails.application.config.paths["db/migrate"]` — i.e. always the
23
+ # real app root, regardless of `destination_root`. This must be
24
+ # overridden separately.
25
+ module AtomAware
26
+ def self.included(base)
27
+ base.class_option :atom, type: :string, default: nil,
28
+ desc: "Explicit ATOM name (under vendor/submodules/) to target, overriding cwd-based detection"
29
+ end
30
+
31
+ def initialize(*args)
32
+ super
33
+ self.destination_root = atom_dir if atom_dir
34
+ end
35
+
36
+ # The absolute ATOM directory this generator's files should land in, or
37
+ # nil for plain host-app context. Resolved once, before
38
+ # `destination_root` is (possibly) overridden above, since the
39
+ # pre-override `destination_root` is the correct app-root anchor for
40
+ # resolving an explicit `--atom=NAME`.
41
+ def atom_dir
42
+ return @atom_dir if @atom_dir_resolved
43
+
44
+ @atom_dir_resolved = true
45
+ @atom_dir = Thecore::Generators::WorkspaceContext.atom_dir_for(
46
+ cwd: Dir.pwd,
47
+ app_root: destination_root,
48
+ atom_name: options[:atom]
49
+ )
50
+ end
51
+
52
+ private
53
+
54
+ def db_migrate_path
55
+ atom_dir ? File.join(atom_dir, "db", "migrate") : super
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ require "rails/generators/active_record/migration/migration_generator"
2
+ require "generators/thecore/atom_aware"
3
+
4
+ module Thecore
5
+ module Generators
6
+ # Resolved automatically by plain `rails generate migration` once
7
+ # ThecoreGenerators::Railtie registers `config.app_generators.orm
8
+ # :thecore, ...` (namespace "thecore:migration" — see
9
+ # Thecore::Generators::ModelGenerator for the namespace-resolution
10
+ # mechanism, identical here).
11
+ #
12
+ # A pure wrap: 100% of ActiveRecord::Generators::MigrationGenerator's
13
+ # migration-content logic (add/remove/create-table detection, attribute
14
+ # parsing, templates) is inherited untouched. The only addition is
15
+ # Thecore::Generators::AtomAware, redirecting the migration file into an
16
+ # ATOM's db/migrate when one is detected from `Dir.pwd`/`--atom=NAME`, so
17
+ # `rails generate migration AddBarToFoo bar:string` gets the same
18
+ # context-aware placement standalone, not just via `rails generate model`.
19
+ class MigrationGenerator < ActiveRecord::Generators::MigrationGenerator
20
+ include Thecore::Generators::AtomAware
21
+
22
+ source_root ActiveRecord::Generators::MigrationGenerator.source_root
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,74 @@
1
+ require "rails/generators/active_record/model/model_generator"
2
+ require "generators/thecore/atom_aware"
3
+
4
+ module Thecore
5
+ module Generators
6
+ # Resolved automatically by plain `rails generate model` once
7
+ # ThecoreGenerators::Railtie registers `config.app_generators.orm
8
+ # :thecore, ...` (namespace "thecore:model", derived from this class's
9
+ # own module nesting the same way ActiveRecord::Generators::ModelGenerator
10
+ # resolves to "active_record:model" — see Rails::Generators::Base#namespace).
11
+ #
12
+ # Wraps (does not reimplement) ActiveRecord's own model generator: all
13
+ # attribute parsing, the model/module templates, and migration-content
14
+ # generation are inherited as-is. On top of that:
15
+ # - Thecore::Generators::AtomAware redirects placement into an ATOM's
16
+ # app/models + db/migrate when one is detected (see its own docs).
17
+ # - `Api::ModelName`/`RailsAdmin::ModelName` concern files are
18
+ # generated and `include`d into the model, replicating
19
+ # thecore_code_extension's addModel.js templates exactly (unchanged
20
+ # by this ticket — see thecore_generators#3 / ADR 0001 in the thecore
21
+ # repo).
22
+ # - `Endpoints::ModelName` is deliberately NOT generated (ADR 0001: it's
23
+ # never `include`d by default; add it by hand, following the
24
+ # after_initialize + class_eval pattern, only when a real custom
25
+ # action is needed).
26
+ # - Test file generation is never suppressed (no `--skip-test-framework`
27
+ # equivalent) — `hook_for :test_framework` runs exactly as it does for
28
+ # `active_record:model`.
29
+ class ModelGenerator < ActiveRecord::Generators::ModelGenerator
30
+ include Thecore::Generators::AtomAware
31
+
32
+ # `source_root` (singular) must be set explicitly: Rails::Generators::Base's
33
+ # auto-computed `default_source_root` derives its path from *this*
34
+ # class's own base_name/generator_name ("thecore"/"model"), which
35
+ # doesn't exist on disk — so it silently resolves to nil unless
36
+ # pointed at ActiveRecord's own directory here. Our own
37
+ # api_concern.rb.tt/rails_admin_concern.rb.tt templates live alongside
38
+ # this file and are added to `source_paths` (plural) separately, since
39
+ # `source_root` only holds one path.
40
+ source_paths.unshift(File.expand_path("templates", __dir__))
41
+ source_root ActiveRecord::Generators::ModelGenerator.source_root
42
+
43
+ def create_model_file
44
+ super
45
+ add_default_concerns
46
+ end
47
+
48
+ private
49
+
50
+ # Faithful Ruby port of addModel.js's api_concern.rb/rails_admin_concern.rb
51
+ # templates and its `include Api::X` / `include RailsAdmin::X` model-file
52
+ # rewrite — down to the exact generated content — per this ticket's
53
+ # explicit "unchanged, still generated exactly as today" scope (that
54
+ # removal is thecore_generators#6, gated on other work landing first).
55
+ def add_default_concerns
56
+ template "api_concern.rb", File.join("app/models/concerns/api", class_path, "#{file_name}.rb")
57
+ template "rails_admin_concern.rb", File.join("app/models/concerns/rails_admin", class_path, "#{file_name}.rb")
58
+
59
+ include_default_concerns_in_model
60
+ end
61
+
62
+ def include_default_concerns_in_model
63
+ model_file = File.join("app/models", class_path, "#{file_name}.rb")
64
+ simple_class_name = class_name.split("::").last
65
+
66
+ insert_into_file(
67
+ model_file,
68
+ " include Api::#{class_name}\n include RailsAdmin::#{class_name}\n",
69
+ after: /class #{Regexp.escape(simple_class_name)} < .*\n/
70
+ )
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,19 @@
1
+ module Api::<%= class_name %>
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # Use self.json_attrs to drive json rendering for
6
+ # API model responses (index, show and update ones).
7
+ # For reference:
8
+ # https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
9
+ # The object passed accepts only these keys:
10
+ # - only: list [] of model fields to be shown in JSON serialization
11
+ # - except: exclude these fields from the JSON serialization, is a list []
12
+ # - methods: include the result of some method defined in the model
13
+ # - include: include associated models, it's an object {} which also accepts the keys described here
14
+ cattr_accessor :json_attrs
15
+ self.json_attrs = ::ModelDrivenApi.smart_merge (json_attrs || {}), {}
16
+
17
+ # Custom action callable by the API must be defined in /app/models/concerns/endpoints/
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module RailsAdmin::<%= class_name %>
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ rails_admin do
6
+ navigation_label I18n.t('admin.registries.label')
7
+ navigation_icon 'fa fa-file' # TODO: customize icon, see https://fontawesome.com/v5/search
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,107 @@
1
+ require "thor/error"
2
+
3
+ module Thecore
4
+ module Generators
5
+ # Ruby port of thecore_code_extension's libs/workspaceContext.js
6
+ # (specifically its `atomRootOf`/`hasGemspec` gemspec-presence-under-
7
+ # vendor/submodules detection). The extension resolved context from a
8
+ # right-clicked VS Code folder; here we resolve it from the invoking
9
+ # process's `Dir.pwd`, since a terminal has no clicked folder — see
10
+ # docs/adr/0002-thecore-generators-gem-and-generator-hook-mechanism.md in
11
+ # the thecore repo.
12
+ #
13
+ # There is no AppContext/ATOMContext class pair here (unlike the JS
14
+ # original): a generator only ever needs one thing from this module — the
15
+ # absolute ATOM directory to redirect file placement to, or `nil` when
16
+ # `cwd` (and no `--atom` override) resolves to plain host-app context, in
17
+ # which case the generator leaves Rails' own default placement (relative
18
+ # to `destination_root`, already the app root for a real `rails generate`
19
+ # invocation) untouched.
20
+ module WorkspaceContext
21
+ module_function
22
+
23
+ # Returns the absolute ATOM directory for placement, or nil for
24
+ # host-app context.
25
+ #
26
+ # `atom_name`, when present (a generator's `--atom=NAME` option), is an
27
+ # explicit override that skips `cwd`-based detection entirely and is
28
+ # resolved as `<app_root>/vendor/submodules/<atom_name>` instead — this
29
+ # is what lets `--atom=NAME` work "from anywhere", independent of `cwd`.
30
+ # `app_root` is the generator's own (pre-override) `destination_root`:
31
+ # for a real `rails generate` invocation that is already
32
+ # `Rails::Command.root` (see rails/commands/generate/generate_command.rb),
33
+ # and in tests it's whatever `Rails::Generators::TestCase` configured as
34
+ # `destination_root` — either way it's the correct anchor without this
35
+ # module needing its own notion of "the app root".
36
+ def atom_dir_for(cwd:, app_root:, atom_name: nil)
37
+ atom_name = atom_name.to_s.strip
38
+ return resolve_named_atom(app_root, atom_name) unless atom_name.empty?
39
+
40
+ resolve_cwd_atom(cwd)
41
+ end
42
+
43
+ # Walks up from `dir` until the immediate parent directory is
44
+ # `vendor/submodules` — that child is the ATOM root. Mirrors
45
+ # workspaceContext.js's `atomRootOf`. Returns nil if `dir` is not
46
+ # inside a `vendor/submodules/<atom>/` tree at all.
47
+ def atom_root_of(dir)
48
+ current = File.expand_path(dir.to_s)
49
+
50
+ loop do
51
+ parent = File.dirname(current)
52
+ return nil if parent == current # reached the filesystem root
53
+
54
+ if File.basename(parent) == "submodules" && File.basename(File.dirname(parent)) == "vendor"
55
+ return current
56
+ end
57
+
58
+ current = parent
59
+ end
60
+ end
61
+
62
+ # Ported from workspaceContext.js's `hasGemspec`: an ATOM directory is
63
+ # valid when it contains `<dirname>.gemspec` or, since gem names can't
64
+ # contain dashes, the dash-to-underscore variant of it.
65
+ def gemspec_path_for(atom_dir)
66
+ atom_name = File.basename(atom_dir)
67
+
68
+ [atom_name, atom_name.tr("-", "_")].each do |candidate|
69
+ path = File.join(atom_dir, "#{candidate}.gemspec")
70
+ return path if File.exist?(path)
71
+ end
72
+
73
+ nil
74
+ end
75
+
76
+ def valid_atom_dir?(atom_dir)
77
+ File.directory?(atom_dir) && !gemspec_path_for(atom_dir).nil?
78
+ end
79
+
80
+ class << self
81
+ private
82
+
83
+ def resolve_named_atom(app_root, atom_name)
84
+ candidate = File.join(app_root.to_s, "vendor", "submodules", atom_name)
85
+ return candidate if valid_atom_dir?(candidate)
86
+
87
+ raise Thor::Error,
88
+ "No ATOM named '#{atom_name}' found at #{candidate} " \
89
+ "(expected a #{atom_name}.gemspec or #{atom_name.tr('-', '_')}.gemspec inside it)."
90
+ end
91
+
92
+ def resolve_cwd_atom(cwd)
93
+ candidate = atom_root_of(cwd)
94
+ return nil unless candidate
95
+
96
+ unless valid_atom_dir?(candidate)
97
+ raise Thor::Error,
98
+ "#{candidate} is under vendor/submodules/ but has no gemspec - " \
99
+ "not a valid Thecore ATOM."
100
+ end
101
+
102
+ candidate
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -1,13 +1,25 @@
1
+ require "rails/railtie"
2
+
1
3
  module ThecoreGenerators
2
- # Boot-time hook point for this gem. Intentionally empty for now: no
3
- # generator behaviour ships in this release. A future ticket will add
4
+ # Hooks `rails generate model`/`rails generate migration` the same way
5
+ # ActiveRecord's own Railtie does (`config.app_generators.orm :active_record,
6
+ # migration: true, timestamps: true` in active_record/railtie.rb) and Mongoid
7
+ # does for its own ORM. Registering this at the class body level — not inside
8
+ # an `initializer` block — matters: `config.app_generators` is a process-wide
9
+ # singleton (`Rails::Railtie::Configuration#app_generators`) copied into
10
+ # `Rails::Generators.options` early during boot, so this must run at require
11
+ # time, exactly mirroring ActiveRecord's own placement, to reliably win by
12
+ # require order (see docs/adr/0002-thecore-generators-gem-and-generator-hook-mechanism.md
13
+ # in the thecore repo).
4
14
  #
5
- # config.app_generators.orm :thecore, migration: true, timestamps: true
6
- #
7
- # here, following the same mechanism ActiveRecord's own Railtie and
8
- # Mongoid both use to hook `rails generate model`/`migration` (see
9
- # docs/adr/0002-thecore-generators-gem-and-generator-hook-mechanism.md in
10
- # the thecore repo).
15
+ # This makes plain `rails generate model`/`rails generate migration` resolve
16
+ # to Thecore::Generators::ModelGenerator/MigrationGenerator (namespaces
17
+ # "thecore:model"/"thecore:migration") instead of ActiveRecord's own
18
+ # generators, with zero new command vocabulary for developers.
19
+ # `rails generate active_record:model`/`active_record:migration` remain
20
+ # available directly as an escape hatch — Rails only hides the override from
21
+ # `--help`, it never blocks direct namespace invocation.
11
22
  class Railtie < ::Rails::Railtie
23
+ config.app_generators.orm :thecore, migration: true, timestamps: true
12
24
  end
13
25
  end
@@ -1,3 +1,3 @@
1
1
  module ThecoreGenerators
2
- VERSION = "3.0.0"
2
+ VERSION = "3.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thecore_generators
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
@@ -36,6 +36,12 @@ files:
36
36
  - MIT-LICENSE
37
37
  - README.md
38
38
  - Rakefile
39
+ - lib/generators/thecore/atom_aware.rb
40
+ - lib/generators/thecore/migration/migration_generator.rb
41
+ - lib/generators/thecore/model/model_generator.rb
42
+ - lib/generators/thecore/model/templates/api_concern.rb.tt
43
+ - lib/generators/thecore/model/templates/rails_admin_concern.rb.tt
44
+ - lib/generators/thecore/workspace_context.rb
39
45
  - lib/thecore_generators.rb
40
46
  - lib/thecore_generators/railtie.rb
41
47
  - lib/thecore_generators/version.rb