thecore_generators 3.1.0 → 3.6.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.
@@ -30,21 +30,32 @@ module Thecore
30
30
 
31
31
  def initialize(*args)
32
32
  super
33
+ @host_app_root = destination_root
33
34
  self.destination_root = atom_dir if atom_dir
34
35
  end
35
36
 
37
+ # The true host-app root, captured before `destination_root` is
38
+ # (possibly) overridden above — for a real `rails generate` invocation
39
+ # this is `Rails::Command.root`, in tests whatever
40
+ # `Rails::Generators::TestCase` configured as `destination_root`.
41
+ # Unlike `destination_root` (which becomes the ATOM dir once
42
+ # overridden), this always stays the app root, giving
43
+ # Thecore::Generators::WorkspaceContext.model_root_for a stable anchor
44
+ # to search from regardless of where *this* invocation itself landed.
45
+ attr_reader :host_app_root
46
+
36
47
  # The absolute ATOM directory this generator's files should land in, or
37
48
  # nil for plain host-app context. Resolved once, before
38
49
  # `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`.
50
+ # pre-override `destination_root` (== `host_app_root`) is the correct
51
+ # app-root anchor for resolving an explicit `--atom=NAME`.
41
52
  def atom_dir
42
53
  return @atom_dir if @atom_dir_resolved
43
54
 
44
55
  @atom_dir_resolved = true
45
56
  @atom_dir = Thecore::Generators::WorkspaceContext.atom_dir_for(
46
57
  cwd: Dir.pwd,
47
- app_root: destination_root,
58
+ app_root: host_app_root,
48
59
  atom_name: options[:atom]
49
60
  )
50
61
  end
@@ -0,0 +1,142 @@
1
+ require "yaml"
2
+
3
+ module Thecore
4
+ module Generators
5
+ # Shared companion-file behavior for Thecore's own custom-action
6
+ # generators: ensuring config/initializers/after_initialize.rb and
7
+ # assets.rb exist and carry the right require/precompile line, and
8
+ # writing the RailsAdmin action's locale entries. Introduced by
9
+ # RootActionGenerator (thecore_generators#11) and reused as-is by
10
+ # MemberActionGenerator (thecore_generators#12).
11
+ #
12
+ # Faithful Ruby port of the corresponding parts of thecore_code_extension's
13
+ # addRootAction.js/addMemberAction.js: the after_initialize.rb/assets.rb
14
+ # ensure-and-append logic, and the locale-entry merge behavior — broadened
15
+ # per thecore_generators#11's acceptance criteria to write into every
16
+ # *.yml file already present under the locales directory, not just
17
+ # en.yml/it.yml (the JS original only ever touched those two).
18
+ #
19
+ # Deliberately duplicates (rather than shares) the after_initialize.rb
20
+ # skeleton Thecore::Generators::AssociationWiring already writes — same
21
+ # structure/anchor, so the two compose fine regardless of which one
22
+ # happens to create the file first, but kept separate to avoid touching
23
+ # already-shipped Phase 1 code for this ticket.
24
+ module CompanionFiles
25
+ AFTER_INITIALIZE_TEMPLATE = <<~RUBY.freeze
26
+ Rails.application.configure do
27
+ config.after_initialize do
28
+ end
29
+ end
30
+ RUBY
31
+
32
+ ASSETS_TEMPLATE = <<~RUBY.freeze
33
+ # PLEASE, uncomment if needed.
34
+ # For Example: in the case there's a root action called tcp_debug, add the following lines to include css and javascripts for auto loading:
35
+ # Rails.application.config.assets.precompile += %w(
36
+ # main_tcp_debug.js
37
+ # main_tcp_debug.css
38
+ # )
39
+ RUBY
40
+
41
+ private
42
+
43
+ # Renders the shared view/JS/SCSS companion trio for a RailsAdmin
44
+ # custom action (root or member) into the workspace's fixed
45
+ # app/views/rails_admin/main, app/assets/javascripts/rails_admin/actions,
46
+ # and app/assets/stylesheets/rails_admin/actions directories — the same
47
+ # relative paths regardless of ATOM vs host-app context (only the
48
+ # action's own controller-config file, handled by the including
49
+ # generator, is placed differently: lib/root_actions vs
50
+ # config/root_actions, and similarly for member actions).
51
+ # Requires "action.html.erb.tt"/"action.js.tt"/"action.scss.tt" to be
52
+ # present in the including generator's own source_paths. Takes no
53
+ # argument deliberately: the template bodies themselves read the
54
+ # including generator's own `file_name` (via NamedBase) through the ERB
55
+ # binding `template` evaluates them in, so a separate action-name
56
+ # argument here would only rename the destination files while the
57
+ # content inside kept using `file_name` — a silent name/content
58
+ # mismatch. Callers needing a different action name for content must
59
+ # get there via their own `file_name`, not a parameter to this method.
60
+ def render_view_js_scss_companions!
61
+ template "action.html.erb.tt", File.join("app/views/rails_admin/main", "#{file_name}.html.erb")
62
+ template "action.js.tt", File.join("app/assets/javascripts/rails_admin/actions", "#{file_name}.js")
63
+ template "action.scss.tt", File.join("app/assets/stylesheets/rails_admin/actions", "#{file_name}.scss")
64
+ end
65
+
66
+ # Ensures config/initializers/after_initialize.rb exists (creating it
67
+ # from the skeleton above if absent) and that `require_line` is present
68
+ # inside its `config.after_initialize do ... end` block — idempotently.
69
+ def ensure_after_initialize_require!(require_line)
70
+ path = "config/initializers/after_initialize.rb"
71
+ full_path = File.join(destination_root, path)
72
+
73
+ create_file(path, AFTER_INITIALIZE_TEMPLATE) unless File.exist?(full_path)
74
+
75
+ content = File.read(full_path)
76
+ if content.include?(require_line)
77
+ say_status :skip, "#{path} already requires it", :blue
78
+ else
79
+ insert_into_file(path, " #{require_line}\n", after: /config\.after_initialize do\n/)
80
+ end
81
+ end
82
+
83
+ # Ensures config/initializers/assets.rb exists (creating it from the
84
+ # skeleton above if absent) and that `precompile_line` is appended to
85
+ # it — idempotently.
86
+ def ensure_assets_precompile_line!(precompile_line)
87
+ path = "config/initializers/assets.rb"
88
+ full_path = File.join(destination_root, path)
89
+
90
+ create_file(path, ASSETS_TEMPLATE) unless File.exist?(full_path)
91
+
92
+ content = File.read(full_path)
93
+ if content.include?(precompile_line)
94
+ say_status :skip, "#{path} already has the precompile line", :blue
95
+ else
96
+ append_to_file(path, "\n#{precompile_line}\n")
97
+ end
98
+ end
99
+
100
+ # Writes the RailsAdmin action's menu/title/breadcrumb locale entry
101
+ # (all three set to `title`, matching addRootAction.js/
102
+ # addMemberAction.js's mergeYaml behavior) under `admin.actions.<key>`
103
+ # into every *.yml file already present under config/locales — not just
104
+ # en.yml/it.yml, per thecore_generators#11's acceptance criteria. When
105
+ # the locales directory has no *.yml file yet, en.yml and it.yml are
106
+ # created first and then updated the same way.
107
+ def write_action_locale_entries!(key, title)
108
+ locales_dir = File.join(destination_root, "config", "locales")
109
+ existing = Dir.exist?(locales_dir) ? Dir.children(locales_dir).select { |f| f.end_with?(".yml") } : []
110
+
111
+ existing = %w[en.yml it.yml] if existing.empty?
112
+
113
+ existing.sort.each do |file|
114
+ rel_path = File.join("config", "locales", file)
115
+ full_path = File.join(destination_root, rel_path)
116
+ default_lang = File.basename(file, ".yml")
117
+
118
+ create_file(rel_path, "#{default_lang}:\n") unless File.exist?(full_path)
119
+ merge_action_locale_entry!(rel_path, default_lang, key, title)
120
+ end
121
+ end
122
+
123
+ # `lang` is the file's own single top-level key when it already has
124
+ # exactly one (the locale code, e.g. "en" in a Rails/Devise-style
125
+ # "devise.en.yml" whose filename does not equal its locale code) —
126
+ # falling back to `default_lang` (derived from the filename) only for
127
+ # an empty/freshly-created file, where there is nothing yet to read the
128
+ # real locale code from.
129
+ def merge_action_locale_entry!(rel_path, default_lang, key, title)
130
+ full_path = File.join(destination_root, rel_path)
131
+ data = YAML.load_file(full_path) || {}
132
+ lang = data.size == 1 ? data.keys.first : default_lang
133
+ data[lang] = {} unless data[lang].is_a?(Hash)
134
+ data[lang]["admin"] = {} unless data[lang]["admin"].is_a?(Hash)
135
+ data[lang]["admin"]["actions"] = {} unless data[lang]["admin"]["actions"].is_a?(Hash)
136
+ data[lang]["admin"]["actions"][key] = { "menu" => title, "title" => title, "breadcrumb" => title }
137
+
138
+ create_file(rel_path, YAML.dump(data), force: true, verbose: false)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,61 @@
1
+ require "rails/generators/named_base"
2
+ require "generators/thecore/atom_aware"
3
+ require "generators/thecore/companion_files"
4
+ require "generators/thecore/action_companion"
5
+
6
+ module Thecore
7
+ module Generators
8
+ # `rails generate thecore:member_action NAME` — a Ruby port of
9
+ # thecore_code_extension's addMemberAction.js (thecore_generators#12),
10
+ # the Member Action counterpart to RootActionGenerator
11
+ # (thecore_generators#11). Shares the entire generator step sequence and
12
+ # placement/naming logic with it via Thecore::Generators::ActionCompanion
13
+ # (see that module and RootActionGenerator's own comment) — only this
14
+ # class's own `templates/action.rb.tt`/`action.html.erb.tt`/
15
+ # `action.js.tt` are Member-specific: the RailsAdmin `:member` action
16
+ # type and its XHR + form PATCH example, matching what
17
+ # `addMemberAction.js` produces today (not unified with Root's fetch +
18
+ # ActionCable-broadcast template).
19
+ #
20
+ # Discovered automatically by Rails::Generators' own namespace-by-path
21
+ # convention (`generators/thecore/member_action/member_action_generator.rb`
22
+ # → "thecore:member_action") — no Railtie registration needed, same as
23
+ # RootActionGenerator.
24
+ class MemberActionGenerator < Rails::Generators::NamedBase
25
+ include Thecore::Generators::AtomAware
26
+ include Thecore::Generators::CompanionFiles
27
+ include Thecore::Generators::ActionCompanion
28
+
29
+ action_kind "member_action"
30
+
31
+ source_root File.expand_path("templates", __dir__)
32
+
33
+ # Thin task methods, required on each class directly (see
34
+ # ActionCompanion's own comment for why) - each delegates to shared
35
+ # private logic there.
36
+ def validate_action_name!
37
+ validate_action_name_for_kind!
38
+ end
39
+
40
+ def create_action_file
41
+ template "action.rb.tt", action_file_path
42
+ end
43
+
44
+ def create_view_js_scss_companions
45
+ render_view_js_scss_companions!
46
+ end
47
+
48
+ def add_after_initialize_require
49
+ ensure_after_initialize_require!(require_line)
50
+ end
51
+
52
+ def add_assets_precompile_line
53
+ ensure_assets_precompile_line!(assets_precompile_line)
54
+ end
55
+
56
+ def add_locale_entries
57
+ write_action_locale_entries!(file_name, title_case_name)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,16 @@
1
+ <%%= stylesheet_link_tag 'rails_admin/actions/<%= file_name %>' %>
2
+ <%%= form_with(url: <%= file_name %>_path, html: { method: :patch }, class: "main", id: "<%= file_name %>-form") do |f| %>
3
+ <div class="form-actions row justify-content-end my-3">
4
+ <div class="col-sm-10">
5
+ <input name="return_to" type="hidden" value="<%%=edit_path(@abstract_model, @object.id)%>">
6
+ <button class="btn btn-primary" data-disable-with="Save" name="_save" type="submit">
7
+ <i class="fas fa-check"></i>
8
+ Test
9
+ </button>
10
+ </div>
11
+ </div>
12
+ <%% end %>
13
+ <!-- The button to test the xhr get request -->
14
+ <button id="<%= file_name %>-id" class="btn btn-primary" data-url="<%%= rails_admin.<%= file_name %>_path %>">Test <%= file_name %></button>
15
+ <div id="<%= file_name %>-response"></div>
16
+ <%%= javascript_include_tag "rails_admin/actions/<%= file_name %>" %>
@@ -0,0 +1,44 @@
1
+ var <%= action_name_camel_case %>Cable = null;
2
+ // If the <%= action_name_camel_case %>Function is already defined, then don't redefine it and don't attach it to the eventListener
3
+ if (typeof <%= action_name_camel_case %>Function !== 'function') {
4
+ function <%= action_name_camel_case %>Function(event) {
5
+ console.log('Hello from <%= file_name %>', event);
6
+ // Action Cable WebSocket connection only if <%= action_name_camel_case %>Cable is not already defined and valid
7
+ if (typeof <%= action_name_camel_case %>Cable !== 'object' || <%= action_name_camel_case %>Cable === null) {
8
+ <%= action_name_camel_case %>Cable = App.cable.subscriptions.create("ActivityLogChannel", {
9
+ connected() {
10
+ console.log("Connected to the channel:", this);
11
+ this.send({ message: '<%= file_name %> Client is connected', topic: "<%= file_name %>", namespace: "subscriptions" });
12
+ },
13
+ disconnected() {
14
+ console.log("<%= file_name %> Client Disconnected");
15
+ },
16
+ received(data) {
17
+ if(data["topic"] == "<%= file_name %>") {
18
+ console.log("<%= file_name %>", data);
19
+ document.getElementById('<%= file_name %>-response').innerHTML = data["message"];
20
+ }
21
+ }
22
+ });
23
+ }
24
+ // Send a message to the server
25
+ <%= action_name_camel_case %>Cable.send({ message: '<%= file_name %> Client is sending a message', topic: "<%= file_name %>", namespace: "subscriptions" });
26
+ // Attach a click event listener to the button which sends an XHR GET request and shows the response.
27
+ // The URL is read from the data-url attribute to avoid ERB interpolation in plain .js files.
28
+ document.getElementById('<%= file_name %>-id').addEventListener('click', function() {
29
+ var url = this.dataset.url;
30
+ var xhr = new XMLHttpRequest();
31
+ xhr.open('GET', url, true);
32
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
33
+ xhr.onreadystatechange = function() {
34
+ if (xhr.readyState == 4 && xhr.status == 200) {
35
+ var response = JSON.parse(xhr.responseText);
36
+ document.getElementById('<%= file_name %>-response').innerHTML = response.message;
37
+ }
38
+ }
39
+ xhr.send();
40
+ });
41
+ }
42
+ }
43
+ // Attach the function to the eventListener
44
+ document.addEventListener('turbo:load', <%= action_name_camel_case %>Function);
@@ -0,0 +1,25 @@
1
+ RailsAdmin::Config::Actions.add_action "<%= file_name %>", :base, :member do
2
+ link_icon 'fas fa-file'
3
+ http_methods [:get, :patch]
4
+ # Customize visibility: show this action only for specific models.
5
+ # Example: visible only for the User model:
6
+ # visible do
7
+ # bindings[:object].is_a?(::User)
8
+ # end
9
+ # Or use the default authorization check:
10
+ visible? authorized?
11
+ # Adding the controller which is needed to compute calls from the ui
12
+ controller do
13
+ proc do
14
+ # if it's a form submission, then update the password
15
+ if !request.xhr? && request.patch?
16
+ flash[:success] = I18n.t("Successfully clicked on sample action")
17
+ # Redirect to the object
18
+ redirect_to index_path(model_name: @abstract_model.to_param)
19
+ elsif request.xhr? && request.get?
20
+ # Return a json response
21
+ render json: { message: "Hello from <%= file_name %>" }, status: :ok
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ // Spinner
2
+ .loader {
3
+ width: 40px;
4
+ height: 40px;
5
+ position: relative;
6
+ margin: 0 auto;
7
+ }
8
+ .double-bounce1,
9
+ .double-bounce2 {
10
+ width: 100%;
11
+ height: 100%;
12
+ border-radius: 50%;
13
+ background-color: #333;
14
+ opacity: 0.6;
15
+ position: absolute;
16
+ top: 0;
17
+ left: 0;
18
+ animation: sk-bounce 2.0s infinite ease-in-out;
19
+ }
20
+ .double-bounce2 {
21
+ animation-delay: -1.0s;
22
+ }
23
+ @keyframes sk-bounce {
24
+ 0%,
25
+ 100% {
26
+ transform: scale(0.0);
27
+ }
28
+ 50% {
29
+ transform: scale(1.0);
30
+ }
31
+ }
32
+ // End Spinner
33
+ #<%= file_name %>-response {
34
+ border-radius: 1em;
35
+ display: flex;
36
+ flex-direction: column;
37
+ justify-content: center;
38
+ }
@@ -1,5 +1,6 @@
1
1
  require "rails/generators/active_record/migration/migration_generator"
2
2
  require "generators/thecore/atom_aware"
3
+ require "generators/thecore/association_wiring"
3
4
 
4
5
  module Thecore
5
6
  module Generators
@@ -9,17 +10,27 @@ module Thecore
9
10
  # Thecore::Generators::ModelGenerator for the namespace-resolution
10
11
  # mechanism, identical here).
11
12
  #
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`.
13
+ # 100% of ActiveRecord::Generators::MigrationGenerator's migration-content
14
+ # logic (add/remove/create-table detection, attribute parsing, templates)
15
+ # is inherited untouched. On top of that:
16
+ # - Thecore::Generators::AtomAware redirects the migration file into an
17
+ # ATOM's db/migrate when one is detected from `Dir.pwd`/`--atom=NAME`,
18
+ # so `rails generate migration AddBarToFoo bar:string` gets the same
19
+ # context-aware placement standalone, not just via `rails generate
20
+ # model`.
21
+ # - Thecore::Generators::AssociationWiring detects `references`
22
+ # attributes and writes the missing inverse `has_many`/`has_one` side
23
+ # into the target model's concern (ADR 0003 in the thecore repo).
19
24
  class MigrationGenerator < ActiveRecord::Generators::MigrationGenerator
20
25
  include Thecore::Generators::AtomAware
26
+ include Thecore::Generators::AssociationWiring
21
27
 
22
28
  source_root ActiveRecord::Generators::MigrationGenerator.source_root
29
+
30
+ def create_migration_file
31
+ super
32
+ wire_inverse_associations_from_references
33
+ end
23
34
  end
24
35
  end
25
36
  end
@@ -1,5 +1,6 @@
1
1
  require "rails/generators/active_record/model/model_generator"
2
2
  require "generators/thecore/atom_aware"
3
+ require "generators/thecore/association_wiring"
3
4
 
4
5
  module Thecore
5
6
  module Generators
@@ -14,11 +15,19 @@ module Thecore
14
15
  # generation are inherited as-is. On top of that:
15
16
  # - Thecore::Generators::AtomAware redirects placement into an ATOM's
16
17
  # 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).
18
+ # - `Api::ModelName`/`RailsAdmin::ModelName` concern files are NOT
19
+ # generated by default (thecore_generators#4 / ADR 0001 in the
20
+ # thecore repo): the no-customization case now relies entirely on
21
+ # the default `json_attrs`/`navigation_label`/`navigation_icon`
22
+ # modules that `model_driven_api` and `thecore_ui_rails_admin`
23
+ # `include` into every `ApplicationRecord` subclass automatically
24
+ # (`ThecoreBackendCommons::DefaultModuleRegistry`). Pass
25
+ # `--with-api-concern`/`--with-admin-concern` to scaffold a starter
26
+ # concern file — identical in shape to what this generator produced
27
+ # before this ticket — for the case where customization is already
28
+ # known to be needed at generation time. To add one later instead
29
+ # (the common case), see the "Adding a concern by hand" section of
30
+ # this gem's README.
22
31
  # - `Endpoints::ModelName` is deliberately NOT generated (ADR 0001: it's
23
32
  # never `include`d by default; add it by hand, following the
24
33
  # after_initialize + class_eval pattern, only when a real custom
@@ -26,8 +35,25 @@ module Thecore
26
35
  # - Test file generation is never suppressed (no `--skip-test-framework`
27
36
  # equivalent) — `hook_for :test_framework` runs exactly as it does for
28
37
  # `active_record:model`.
38
+ # - Thecore::Generators::AssociationWiring detects `references`
39
+ # attributes and writes the missing inverse `has_many`/`has_one` side
40
+ # into the target model's concern (ADR 0003 in the thecore repo) —
41
+ # needed here too, not just in MigrationGenerator, because
42
+ # `rails generate model Foo x:references` creates its migration via
43
+ # ActiveRecord::Generators::ModelGenerator#create_migration_file, a
44
+ # different method than MigrationGenerator's own.
29
45
  class ModelGenerator < ActiveRecord::Generators::ModelGenerator
30
46
  include Thecore::Generators::AtomAware
47
+ include Thecore::Generators::AssociationWiring
48
+
49
+ class_option :with_api_concern, type: :boolean, default: false,
50
+ desc: "Scaffold a starter app/models/concerns/api/<model>.rb, included into the model " \
51
+ "(pre-thecore_generators#4 default behavior). Opt in only when customization is " \
52
+ "already known to be needed at generation time."
53
+ class_option :with_admin_concern, type: :boolean, default: false,
54
+ desc: "Scaffold a starter app/models/concerns/rails_admin/<model>.rb, included into the " \
55
+ "model (pre-thecore_generators#4 default behavior). Opt in only when customization is " \
56
+ "already known to be needed at generation time."
31
57
 
32
58
  # `source_root` (singular) must be set explicitly: Rails::Generators::Base's
33
59
  # auto-computed `default_source_root` derives its path from *this*
@@ -42,30 +68,44 @@ module Thecore
42
68
 
43
69
  def create_model_file
44
70
  super
45
- add_default_concerns
71
+ add_opted_in_concerns
72
+ end
73
+
74
+ def create_migration_file
75
+ super
76
+ wire_inverse_associations_from_references
46
77
  end
47
78
 
48
79
  private
49
80
 
50
81
  # Faithful Ruby port of addModel.js's api_concern.rb/rails_admin_concern.rb
51
82
  # 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")
83
+ # rewrite — down to the exact generated content — kept available behind
84
+ # `--with-api-concern`/`--with-admin-concern` for the explicit
85
+ # customization-at-generation-time case (thecore_generators#4).
86
+ def add_opted_in_concerns
87
+ include_lines = +""
88
+
89
+ if options[:with_api_concern]
90
+ template "api_concern.rb", File.join("app/models/concerns/api", class_path, "#{file_name}.rb")
91
+ include_lines << " include Api::#{class_name}\n"
92
+ end
93
+
94
+ if options[:with_admin_concern]
95
+ template "rails_admin_concern.rb", File.join("app/models/concerns/rails_admin", class_path, "#{file_name}.rb")
96
+ include_lines << " include RailsAdmin::#{class_name}\n"
97
+ end
58
98
 
59
- include_default_concerns_in_model
99
+ include_opted_in_concerns_in_model(include_lines) unless include_lines.empty?
60
100
  end
61
101
 
62
- def include_default_concerns_in_model
102
+ def include_opted_in_concerns_in_model(include_lines)
63
103
  model_file = File.join("app/models", class_path, "#{file_name}.rb")
64
104
  simple_class_name = class_name.split("::").last
65
105
 
66
106
  insert_into_file(
67
107
  model_file,
68
- " include Api::#{class_name}\n include RailsAdmin::#{class_name}\n",
108
+ include_lines,
69
109
  after: /class #{Regexp.escape(simple_class_name)} < .*\n/
70
110
  )
71
111
  end
@@ -0,0 +1,80 @@
1
+ require "rails/generators/named_base"
2
+ require "generators/thecore/atom_aware"
3
+ require "generators/thecore/companion_files"
4
+ require "generators/thecore/action_companion"
5
+
6
+ module Thecore
7
+ module Generators
8
+ # `rails generate thecore:root_action NAME` — a Ruby port of
9
+ # thecore_code_extension's addRootAction.js (thecore_generators#11),
10
+ # producing the same end result from a terminal: the RailsAdmin root
11
+ # action file, its view/JS/SCSS companions, the after_initialize.rb
12
+ # require line, the assets.rb precompile line, and locale entries — with
13
+ # ATOM-aware placement via the same Thecore::Generators::AtomAware
14
+ # mechanism the Model/Migration generators already use.
15
+ #
16
+ # Discovered automatically by Rails::Generators' own namespace-by-path
17
+ # convention (this file's path derives the "thecore:root_action"
18
+ # namespace) — no Railtie registration needed, unlike
19
+ # ModelGenerator/MigrationGenerator, since there is no built-in Rails
20
+ # generator being overridden here.
21
+ #
22
+ # Unlike ModelGenerator/MigrationGenerator, Thecore::Generators::AtomAware
23
+ # only supplies `atom_dir`/`host_app_root` and the `--atom` option here —
24
+ # this generator does not override `destination_root` placement for a
25
+ # single fixed subpath the way Model/Migration's templates do, because the
26
+ # action file itself lives at a *different* relative path depending on
27
+ # context (ATOM: lib/root_actions/, host app: config/root_actions/ — see
28
+ # docs/adr/0001-main-app-actions-live-in-config.md in
29
+ # thecore_code_extension for why the host-app side avoids lib/). AtomAware
30
+ # still redirects `destination_root` into the ATOM dir when one is
31
+ # detected, so the view/JS/SCSS/locale/after_initialize/assets companions
32
+ # (fixed relative paths, shared with the host-app case) land in the right
33
+ # place automatically.
34
+ #
35
+ # The full generator step sequence (validate → create_action_file →
36
+ # create_view_js_scss_companions → add_after_initialize_require →
37
+ # add_assets_precompile_line → add_locale_entries) and everything about
38
+ # placement/naming lives in Thecore::Generators::ActionCompanion, shared
39
+ # with MemberActionGenerator (thecore_generators#12) — only this class's
40
+ # own `templates/action.rb.tt`/`action.html.erb.tt`/`action.js.tt` (the
41
+ # RailsAdmin `:root` action type and its fetch + ActionCable-broadcast
42
+ # example) are Root-specific.
43
+ class RootActionGenerator < Rails::Generators::NamedBase
44
+ include Thecore::Generators::AtomAware
45
+ include Thecore::Generators::CompanionFiles
46
+ include Thecore::Generators::ActionCompanion
47
+
48
+ action_kind "root_action"
49
+
50
+ source_root File.expand_path("templates", __dir__)
51
+
52
+ # Thin task methods, required on each class directly (see
53
+ # ActionCompanion's own comment for why) - each delegates to shared
54
+ # private logic there.
55
+ def validate_action_name!
56
+ validate_action_name_for_kind!
57
+ end
58
+
59
+ def create_action_file
60
+ template "action.rb.tt", action_file_path
61
+ end
62
+
63
+ def create_view_js_scss_companions
64
+ render_view_js_scss_companions!
65
+ end
66
+
67
+ def add_after_initialize_require
68
+ ensure_after_initialize_require!(require_line)
69
+ end
70
+
71
+ def add_assets_precompile_line
72
+ ensure_assets_precompile_line!(assets_precompile_line)
73
+ end
74
+
75
+ def add_locale_entries
76
+ write_action_locale_entries!(file_name, title_case_name)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,13 @@
1
+ <%%= stylesheet_link_tag 'rails_admin/actions/<%= file_name %>' %>
2
+ <div class="card mb-3">
3
+ <div class="card-body">
4
+ <div class="response <%= file_name %>-response" id="<%= file_name %>-response">
5
+ </div>
6
+ <div class="loader d-none" id="<%= file_name %>-loader">
7
+ <div class="double-bounce1"></div>
8
+ <div class="double-bounce2"></div>
9
+ </div>
10
+ </div>
11
+ </div>
12
+ <button class="btn btn-primary" id="<%= file_name %>-id" data-url="<%%= rails_admin.<%= file_name %>_path %>">Click me</button>
13
+ <%%= javascript_include_tag "rails_admin/actions/<%= file_name %>" %>