thecore_generators 3.0.0 → 3.2.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 +4 -4
- data/README.md +105 -5
- data/lib/generators/thecore/atom_aware.rb +59 -0
- data/lib/generators/thecore/migration/migration_generator.rb +25 -0
- data/lib/generators/thecore/model/model_generator.rb +100 -0
- data/lib/generators/thecore/model/templates/api_concern.rb.tt +19 -0
- data/lib/generators/thecore/model/templates/rails_admin_concern.rb.tt +10 -0
- data/lib/generators/thecore/workspace_context.rb +107 -0
- data/lib/thecore_generators/railtie.rb +20 -8
- data/lib/thecore_generators/version.rb +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48198bd6f914066ca468eae2e95cb9dba85003d49532b1db28f8620db3c40b21
|
|
4
|
+
data.tar.gz: 7a3e542b82e0ff43691e3a988dad6e42f59fc51da0e460220e5481881eb55ba3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82748585f40f3f1e3975a3c1186d4bb98a3d8c4963ebcb495d9ba4c3c86701b2a48e981c28a6bfe220bd20b90fbcdacffa7e332b36edcbba2d4c3ab80e097e0d
|
|
7
|
+
data.tar.gz: ca82db7bdc678f66789b0ed9af24bf28f4f2253e2c025c3972118e602a86e662fc845863b098ba220d3163739c30b73f2588b30eb0aa8384300bc86a25adf013
|
data/README.md
CHANGED
|
@@ -11,10 +11,98 @@ 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:**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
- **No concern files by default.** `Api::ModelName`/`RailsAdmin::ModelName` concern files
|
|
30
|
+
are **not** generated (per
|
|
31
|
+
[ADR 0001](https://github.com/gabrieletassoni/thecore/blob/release/3/docs/adr/0001-application-record-defaults-over-generated-concerns.md)) —
|
|
32
|
+
the no-customization case relies entirely on the default `json_attrs`/`navigation_label`/
|
|
33
|
+
`navigation_icon` behavior that `model_driven_api` and `thecore_ui_rails_admin` `include`
|
|
34
|
+
into every `ApplicationRecord` subclass automatically
|
|
35
|
+
(`ThecoreBackendCommons::DefaultModuleRegistry`). Pass `--with-api-concern` and/or
|
|
36
|
+
`--with-admin-concern` to scaffold a starter concern file — identical in shape to what
|
|
37
|
+
this generator produced before this default changed — for the case where customization
|
|
38
|
+
is already known to be needed at generation time:
|
|
39
|
+
```bash
|
|
40
|
+
rails generate model Foo name:string --with-api-concern --with-admin-concern
|
|
41
|
+
```
|
|
42
|
+
See "Adding a concern by hand" below for the (more common) case of realizing
|
|
43
|
+
customization is needed *after* the model already exists.
|
|
44
|
+
- **No `Endpoints::ModelName` by default** (per ADR 0001) — add one by hand, following the
|
|
45
|
+
`after_initialize` + `class_eval` pattern, only when a real custom action is needed.
|
|
46
|
+
- **Test file generation is never suppressed** — a real Minitest file is generated, same
|
|
47
|
+
as Rails' own `active_record:model` default.
|
|
48
|
+
- **`rails generate active_record:model`/`active_record:migration` still work directly**
|
|
49
|
+
as an escape hatch, entirely unaffected by the hook above.
|
|
50
|
+
|
|
51
|
+
Both `Thecore::Generators::ModelGenerator` and `MigrationGenerator` wrap (not reimplement)
|
|
52
|
+
`ActiveRecord::Generators::ModelGenerator`/`MigrationGenerator` — all attribute parsing and
|
|
53
|
+
template content is inherited as-is; only file placement and the two opt-in concerns are
|
|
54
|
+
added on top.
|
|
55
|
+
|
|
56
|
+
### Adding a concern by hand
|
|
57
|
+
|
|
58
|
+
The common case is not knowing at `rails generate model` time that a model will need
|
|
59
|
+
custom API serialization or RailsAdmin configuration — that need usually surfaces later.
|
|
60
|
+
Since neither concern is generated by default, add the missing one directly instead of
|
|
61
|
+
regenerating the model:
|
|
62
|
+
|
|
63
|
+
**`Api::ModelName`** (custom `json_attrs`) — create `app/models/concerns/api/model_name.rb`:
|
|
64
|
+
```ruby
|
|
65
|
+
module Api::ModelName
|
|
66
|
+
extend ActiveSupport::Concern
|
|
67
|
+
|
|
68
|
+
included do
|
|
69
|
+
cattr_accessor :json_attrs
|
|
70
|
+
self.json_attrs = ::ModelDrivenApi.smart_merge(json_attrs || {}), { only: [:id, :name] }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
then `include Api::ModelName` in the model. Because the default module (`model_driven_api`'s
|
|
75
|
+
`ModelDrivenApiDefaultJsonAttrs`) is already `include`d by the time the model class body
|
|
76
|
+
runs, `::ModelDrivenApi.smart_merge(json_attrs || {}, ...)` composes on top of it rather
|
|
77
|
+
than starting from nothing — the same pattern the opt-in `--with-api-concern` template
|
|
78
|
+
below uses.
|
|
79
|
+
|
|
80
|
+
**`RailsAdmin::ModelName`** (custom admin config) — create
|
|
81
|
+
`app/models/concerns/rails_admin/model_name.rb`:
|
|
82
|
+
```ruby
|
|
83
|
+
module RailsAdmin::ModelName
|
|
84
|
+
extend ActiveSupport::Concern
|
|
85
|
+
|
|
86
|
+
included do
|
|
87
|
+
rails_admin do
|
|
88
|
+
navigation_label I18n.t('admin.registries.label')
|
|
89
|
+
navigation_icon 'fa fa-file' # see https://fontawesome.com/v5/search
|
|
90
|
+
configure :some_field do
|
|
91
|
+
hide
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
then `include RailsAdmin::ModelName` in the model. RailsAdmin evaluates same-origin
|
|
98
|
+
`rails_admin do ... end` blocks in registration order and later calls win on settings they
|
|
99
|
+
touch (`navigation_label`/`navigation_icon` are last-write-wins setters) — so this explicit
|
|
100
|
+
block, `include`d after the default from the class body, overrides the default's
|
|
101
|
+
`navigation_label`/`navigation_icon` while the default itself keeps applying to every other
|
|
102
|
+
model that has no concern of its own.
|
|
103
|
+
|
|
104
|
+
Either concern can be added independently — a model doesn't need both just because it
|
|
105
|
+
needs one.
|
|
18
106
|
|
|
19
107
|
## Installation
|
|
20
108
|
|
|
@@ -28,18 +116,30 @@ gem "thecore_generators", "~> 3.0"
|
|
|
28
116
|
|
|
29
117
|
Tests use a `Rails::Generators::TestCase`-based harness against the `test/dummy` Rails
|
|
30
118
|
app included in this repo (needed to exercise generators the way a real host app would).
|
|
119
|
+
`test/dummy` also boots real `model_driven_api`/`thecore_ui_rails_admin` (and their own
|
|
120
|
+
transitive `thecore_backend_commons`/`thecore_auth_commons` dependencies) as temporary
|
|
121
|
+
git-based dependencies — see the Gemfile's comment — purely so
|
|
122
|
+
`test/generators/thecore/model_generator_default_concern_behavior_test.rb` can prove the
|
|
123
|
+
no-concern default actually works at runtime, not just that no file was written.
|
|
31
124
|
|
|
32
125
|
```bash
|
|
33
126
|
bundle install
|
|
34
127
|
bundle exec rake test
|
|
35
128
|
```
|
|
36
129
|
|
|
130
|
+
If your shell has `DATABASE_URL` set to a PostgreSQL URL (e.g. inside the Thecore
|
|
131
|
+
devcontainer), unset it first — it overrides `test/dummy`'s own SQLite3 test config:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
env -u DATABASE_URL bundle exec rake test
|
|
135
|
+
```
|
|
136
|
+
|
|
37
137
|
`bundle exec rake` alone runs the same suite (`test` is the default Rake task).
|
|
38
138
|
|
|
39
139
|
To run a single test file:
|
|
40
140
|
|
|
41
141
|
```bash
|
|
42
|
-
bundle exec ruby -Itest test/generators/
|
|
142
|
+
bundle exec ruby -Itest test/generators/thecore/model_generator_test.rb
|
|
43
143
|
```
|
|
44
144
|
|
|
45
145
|
## 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,100 @@
|
|
|
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 NOT
|
|
18
|
+
# generated by default (thecore_generators#4 / ADR 0001 in the
|
|
19
|
+
# thecore repo): the no-customization case now relies entirely on
|
|
20
|
+
# the default `json_attrs`/`navigation_label`/`navigation_icon`
|
|
21
|
+
# modules that `model_driven_api` and `thecore_ui_rails_admin`
|
|
22
|
+
# `include` into every `ApplicationRecord` subclass automatically
|
|
23
|
+
# (`ThecoreBackendCommons::DefaultModuleRegistry`). Pass
|
|
24
|
+
# `--with-api-concern`/`--with-admin-concern` to scaffold a starter
|
|
25
|
+
# concern file — identical in shape to what this generator produced
|
|
26
|
+
# before this ticket — for the case where customization is already
|
|
27
|
+
# known to be needed at generation time. To add one later instead
|
|
28
|
+
# (the common case), see the "Adding a concern by hand" section of
|
|
29
|
+
# this gem's README.
|
|
30
|
+
# - `Endpoints::ModelName` is deliberately NOT generated (ADR 0001: it's
|
|
31
|
+
# never `include`d by default; add it by hand, following the
|
|
32
|
+
# after_initialize + class_eval pattern, only when a real custom
|
|
33
|
+
# action is needed).
|
|
34
|
+
# - Test file generation is never suppressed (no `--skip-test-framework`
|
|
35
|
+
# equivalent) — `hook_for :test_framework` runs exactly as it does for
|
|
36
|
+
# `active_record:model`.
|
|
37
|
+
class ModelGenerator < ActiveRecord::Generators::ModelGenerator
|
|
38
|
+
include Thecore::Generators::AtomAware
|
|
39
|
+
|
|
40
|
+
class_option :with_api_concern, type: :boolean, default: false,
|
|
41
|
+
desc: "Scaffold a starter app/models/concerns/api/<model>.rb, included into the model " \
|
|
42
|
+
"(pre-thecore_generators#4 default behavior). Opt in only when customization is " \
|
|
43
|
+
"already known to be needed at generation time."
|
|
44
|
+
class_option :with_admin_concern, type: :boolean, default: false,
|
|
45
|
+
desc: "Scaffold a starter app/models/concerns/rails_admin/<model>.rb, included into the " \
|
|
46
|
+
"model (pre-thecore_generators#4 default behavior). Opt in only when customization is " \
|
|
47
|
+
"already known to be needed at generation time."
|
|
48
|
+
|
|
49
|
+
# `source_root` (singular) must be set explicitly: Rails::Generators::Base's
|
|
50
|
+
# auto-computed `default_source_root` derives its path from *this*
|
|
51
|
+
# class's own base_name/generator_name ("thecore"/"model"), which
|
|
52
|
+
# doesn't exist on disk — so it silently resolves to nil unless
|
|
53
|
+
# pointed at ActiveRecord's own directory here. Our own
|
|
54
|
+
# api_concern.rb.tt/rails_admin_concern.rb.tt templates live alongside
|
|
55
|
+
# this file and are added to `source_paths` (plural) separately, since
|
|
56
|
+
# `source_root` only holds one path.
|
|
57
|
+
source_paths.unshift(File.expand_path("templates", __dir__))
|
|
58
|
+
source_root ActiveRecord::Generators::ModelGenerator.source_root
|
|
59
|
+
|
|
60
|
+
def create_model_file
|
|
61
|
+
super
|
|
62
|
+
add_opted_in_concerns
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# Faithful Ruby port of addModel.js's api_concern.rb/rails_admin_concern.rb
|
|
68
|
+
# templates and its `include Api::X` / `include RailsAdmin::X` model-file
|
|
69
|
+
# rewrite — down to the exact generated content — kept available behind
|
|
70
|
+
# `--with-api-concern`/`--with-admin-concern` for the explicit
|
|
71
|
+
# customization-at-generation-time case (thecore_generators#4).
|
|
72
|
+
def add_opted_in_concerns
|
|
73
|
+
include_lines = +""
|
|
74
|
+
|
|
75
|
+
if options[:with_api_concern]
|
|
76
|
+
template "api_concern.rb", File.join("app/models/concerns/api", class_path, "#{file_name}.rb")
|
|
77
|
+
include_lines << " include Api::#{class_name}\n"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if options[:with_admin_concern]
|
|
81
|
+
template "rails_admin_concern.rb", File.join("app/models/concerns/rails_admin", class_path, "#{file_name}.rb")
|
|
82
|
+
include_lines << " include RailsAdmin::#{class_name}\n"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
include_opted_in_concerns_in_model(include_lines) unless include_lines.empty?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def include_opted_in_concerns_in_model(include_lines)
|
|
89
|
+
model_file = File.join("app/models", class_path, "#{file_name}.rb")
|
|
90
|
+
simple_class_name = class_name.split("::").last
|
|
91
|
+
|
|
92
|
+
insert_into_file(
|
|
93
|
+
model_file,
|
|
94
|
+
include_lines,
|
|
95
|
+
after: /class #{Regexp.escape(simple_class_name)} < .*\n/
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
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
|
-
#
|
|
3
|
-
#
|
|
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
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# the
|
|
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
|
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.
|
|
4
|
+
version: 3.2.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
|