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.
- checksums.yaml +4 -4
- data/README.md +240 -9
- data/lib/generators/thecore/action_companion.rb +116 -0
- data/lib/generators/thecore/association_wiring.rb +217 -0
- data/lib/generators/thecore/atom_aware.rb +14 -3
- data/lib/generators/thecore/companion_files.rb +142 -0
- data/lib/generators/thecore/member_action/member_action_generator.rb +61 -0
- data/lib/generators/thecore/member_action/templates/action.html.erb.tt +16 -0
- data/lib/generators/thecore/member_action/templates/action.js.tt +44 -0
- data/lib/generators/thecore/member_action/templates/action.rb.tt +25 -0
- data/lib/generators/thecore/member_action/templates/action.scss.tt +38 -0
- data/lib/generators/thecore/migration/migration_generator.rb +18 -7
- data/lib/generators/thecore/model/model_generator.rb +55 -15
- data/lib/generators/thecore/root_action/root_action_generator.rb +80 -0
- data/lib/generators/thecore/root_action/templates/action.html.erb.tt +13 -0
- data/lib/generators/thecore/root_action/templates/action.js.tt +42 -0
- data/lib/generators/thecore/root_action/templates/action.rb.tt +33 -0
- data/lib/generators/thecore/root_action/templates/action.scss.tt +38 -0
- data/lib/generators/thecore/workspace_context.rb +48 -0
- data/lib/tasks/thecore_generators_tasks.rake +33 -0
- data/lib/thecore_generators/check_practices.rb +392 -0
- data/lib/thecore_generators/railtie.rb +4 -0
- data/lib/thecore_generators/version.rb +1 -1
- metadata +16 -1
|
@@ -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
|
|
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:
|
|
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
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# Thecore::Generators::AtomAware
|
|
16
|
-
#
|
|
17
|
-
# `rails generate migration AddBarToFoo bar:string` gets the same
|
|
18
|
-
#
|
|
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
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
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
|
-
|
|
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 —
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
99
|
+
include_opted_in_concerns_in_model(include_lines) unless include_lines.empty?
|
|
60
100
|
end
|
|
61
101
|
|
|
62
|
-
def
|
|
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
|
-
|
|
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 %>" %>
|