Class: LcpRuby::Generators::EntityGenerator

Inherits:
Rails::Generators::NamedBase
  • Object
show all
Includes:
FormatSupport, Prerequisites
Defined in:
lib/generators/lcp_ruby/entity_generator.rb

Constant Summary collapse

SYSTEM_FIELDS =
%i[id created_at updated_at created_by_id updated_by_id].freeze
SYNTHETIC_FIELDS =

Feature flags that require a backing column declared in ‘fields:` for ConfigurationValidator to accept the configuration. Each entry: [flag, field]. See `inject_synthetic_fields!` for the rationale.

[
  [ :positioning, :position ],
  [ :tree,        :parent_id ]
].freeze

Constants included from FormatSupport

FormatSupport::VALID_FORMATS

Instance Method Summary collapse

Methods included from FormatSupport

included, #validate_format

Methods included from Prerequisites

included, #lcp_format_missing_prereqs

Instance Method Details

#auto_add_reverse_associationsObject

Must run before post_validate so the validator does not see the “no corresponding has_many” warnings that auto-add just resolved.



236
237
238
239
240
241
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 236

def auto_add_reverse_associations
  @inverse_suggestions = compute_inverse_suggestions
  return if @inverse_suggestions.empty?
  return unless options[:auto_add_inverses]
  process_inverse_suggestions!
end

#create_modelObject



187
188
189
190
191
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 187

def create_model
  copy_dsl_or_yaml "model.rb",
    dsl_target: "config/lcp_ruby/models/#{singular_name}.rb",
    yaml_target: "config/lcp_ruby/models/#{singular_name}.yml"
end

#create_permissionsObject



199
200
201
202
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 199

def create_permissions
  @roles = Entity::RoleDiscovery.call(destination_root) { |w| @warnings << w }
  template "permissions.yml", "config/lcp_ruby/permissions/#{singular_name}.yml"
end

#create_presenterObject



193
194
195
196
197
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 193

def create_presenter
  copy_dsl_or_yaml "presenter.rb",
    dsl_target: "config/lcp_ruby/presenters/#{plural_name}.rb",
    yaml_target: "config/lcp_ruby/presenters/#{plural_name}.yml"
end

#create_view_groupObject



204
205
206
207
208
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 204

def create_view_group
  copy_dsl_or_yaml "view_group.rb",
    dsl_target: "config/lcp_ruby/views/#{singular_name}.rb",
    yaml_target: "config/lcp_ruby/views/#{singular_name}.yml"
end

#inject_synthetic_fields!Object

Some feature flags require a backing column declared in ‘fields:` for ConfigurationValidator to accept the configuration:

--positioning  →  `position` (acts_as_list expects an explicit field;
                  without it: "positioning field 'position' is not defined").
--tree         →  `parent_id` (validator
                  `configuration_validator.rb:1228` requires
                  "tree parent_field 'parent_id' must be declared in fields";
                  the showcase Category model declares it for the same reason).

Synthetic descriptors are marked ‘:hidden` so they do not appear in the generated index/show/form sections — these are platform-managed columns, not editable user content. Skipped if the user already declared the field.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 96

def inject_synthetic_fields!
  SYNTHETIC_FIELDS.each do |flag, field_name|
    next unless options[flag]
    next if @descriptors.any? { |d| d.name == field_name }
    # `synthetic: true` marks the descriptor as generator-injected (not
    # user-declared) so check_reserved_names! skips macro-clash checks
    # on it — the synthetic positioning :position field IS the macro's
    # column, not a separate user field that conflicts with it.
    @descriptors << Entity::FieldDescriptor.new(
      name: field_name, type: :integer,
      enum_values: nil, modifiers: { hidden: true, synthetic: true }, options: {}
    )
  end
end

#lcp_dynamic_required_featuresObject

Hook consumed by the ‘Prerequisites` concern’s ‘check_lcp_prerequisites!` Thor task. Returns features required by option-driven flags (–auditing → audit_log, –custom-fields → custom_field_definition). Static prereqs would go via `requires_features` at class level; entity has none.



132
133
134
135
136
137
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 132

def lcp_dynamic_required_features
  out = []
  out << "auditing"      if options[:auditing]
  out << "custom_fields" if options[:custom_fields]
  out
end

#parse_fieldsObject



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 71

def parse_fields
  @warnings = []
  # Phase 2 of type-system defaults: the generator now consults the
  # type registry for null_false_validation, default_index_visible,
  # renderer, and reserved_clashes. The registry is normally populated
  # at engine boot (engine.rb:246), but the generator runs from the
  # `rails generate` command without a full engine boot — so we
  # register built-ins lazily here. Idempotent (register_all! overwrites
  # by name).
  Types::BuiltInTypes.register_all!
  @descriptors = Entity::FieldTokenParser.parse(fields)
  inject_synthetic_fields!
end

#plan_menu_entryObject

Preflight for –menu. Runs BEFORE any file write so a missing marker / malformed flag / position-on-dropdown error aborts without leaving half-generated model/presenter/permissions on disk (D7 §“Open Question 2: –menu errors in middle of multi-step generation”). Stashes the plan in @menu_write_plan for write_menu_entry to consume.



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 144

def plan_menu_entry
  @menu_write_plan = nil
  return unless options[:menu]

  menu_path = File.join(destination_root, "config/lcp_ruby/menu.yml")
  unless File.exist?(menu_path)
    raise Thor::Error,
      "menu.yml at config/lcp_ruby/menu.yml is missing. " \
      "Run `rails generate lcp_ruby:install` first or add the file manually."
  end

  @menu_yml_lines = File.readlines(menu_path)
  scan_result = LcpRuby::Generators::EntityMenuWriter.scan(@menu_yml_lines)

  if scan_result[:markers].empty?
    raise Thor::Error,
      "menu.yml has no `# lcp:menu <section>` insertion markers. " \
      "Re-run `rails generate lcp_ruby:install` (which seeds markers via " \
      "--menu-layout=top|sidebar|both) or add a marker manually."
  end

  flag = LcpRuby::Generators::EntityMenuWriter.parse_flag(
    options[:menu],
    available_sections: scan_result[:sections_present]
  )

  @menu_write_plan = LcpRuby::Generators::EntityMenuWriter.plan_insert(
    scan_result,
    section: flag[:section],
    parent: flag[:parent],
    view_group: singular_name,
    icon: options[:menu_icon],
    position: flag[:position]
  )
end

#post_validateObject



243
244
245
246
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 243

def post_validate
  return unless options[:validate]
  run_configuration_validator!
end

#preflightObject

Raises:

  • (Thor::Error)


111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 111

def preflight
  validate_format
  raise Thor::Error, "At least one field token is required." if @descriptors.empty?
  check_reserved_names!
  check_field_types!
  check_label_method!
  check_belongs_to_targets!
  check_polymorphic_conflicts!
  check_default_values!
  check_renderer_overrides!
  validate_searchable_fields!
  validate_index_fields!
  validate_display_template!
  validate_default_sort!
end

#show_post_install_messageObject



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 248

def show_post_install_message
  say ""
  say "LCP entity '#{singular_name}' generated.", :green
  say ""
  say "Generated files:"
  say "  - config/lcp_ruby/models/#{singular_name}.#{format_extension}"
  say "  - config/lcp_ruby/presenters/#{plural_name}.#{format_extension}"
  say "  - config/lcp_ruby/permissions/#{singular_name}.yml"
  say "  - config/lcp_ruby/views/#{singular_name}.#{format_extension}"
  if @warnings.any?
    say ""
    @warnings.each { |w| say_status :warn, w, :yellow }
  end
  say ""
  show_reverse_association_hints
  show_polymorphic_hint
  say "Next steps:"
  next_steps.each_with_index do |step, i|
    say "  #{i + 1}. #{step}"
  end
  say ""
  say "Tip: in bash, single-quote any field token whose braces contain a comma " \
      "(e.g. `'customer:belongs_to{required,link}'`) — bash brace-expands `{a,b}` patterns."
  say ""
end

#validate_menu_icon_flagObject

Standalone check: –menu-icon without –menu is an error even when –menu is not provided (avoids silent no-op).

Raises:

  • (Thor::Error)


182
183
184
185
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 182

def validate_menu_icon_flag
  return unless options[:menu_icon] && !options[:menu]
  raise Thor::Error, "--menu-icon requires --menu; icons are only meaningful with explicit placement."
end

#write_menu_entryObject

Writes the planned ‘- view_group: <name>` entry into menu.yml. Plan was computed in plan_menu_entry (preflight); we only need to splice and re-validate here. Backup is overwriteable single-shot so configurator can roll back the menu mutation with a single `mv` without rolling back the other generator outputs (which stay valid independently).



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/generators/lcp_ruby/entity_generator.rb', line 215

def write_menu_entry
  return if @menu_write_plan.nil?

  if @menu_write_plan[:action] == :skip
    say_status :skip, @menu_write_plan[:message]
    return
  end

  menu_path = File.join(destination_root, "config/lcp_ruby/menu.yml")
  backup_path = File.join(destination_root, "config/lcp_ruby/.menu.yml.bak")
  FileUtils.cp(menu_path, backup_path)

  LcpRuby::Generators::EntityMenuWriter.apply!(menu_path, @menu_write_plan,
    cached_lines: @menu_yml_lines)
  say_status :update, "config/lcp_ruby/menu.yml"

  validate_menu_post_write!(menu_path, backup_path)
end