Class: HasHelpers::Cards::Importer

Inherits:
Object
  • Object
show all
Defined in:
app/importers/has_helpers/cards/importer.rb

Defined Under Namespace

Classes: AuditError, Diff, Result

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(application:, resources:, mode:, audit:) ⇒ Importer

Constructor is internal; external callers use .run.

resources: Class|Symbol|Array or nil (no filter)
mode: :apply or :dry_run
audit: true/false (whether to run drift detection)


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'app/importers/has_helpers/cards/importer.rb', line 134

def initialize(application:, resources:, mode:, audit:)
  # Normalize resource filter into a compact set of symbols representing resource names (e.g. [:advisor, :carrier]).
  @application_name = application.to_s
  @application = ::HasHelpers::Application.find_by!("LOWER(name) = ?", @application_name.downcase)
  @resource_filter = normalize_resource_filter(resources)
  @mode            = (mode || :apply).to_sym
  @audit           = audit

  # This struct accumulates counts while importing.
  @result = Result.new(
    created_cards:     0,
    updated_cards:     0,
    created_fields:    0,
    updated_fields:    0,
    created_resources: 0,
    updated_resources: 0,
    skipped_cards:     0
  )
end

Class Method Details

.run(*args, **opts) ⇒ Object

Public API entry point.

Import all resources declared in the DSL

HasHelpers::Cards::Importer.run

# Import only selected resource types
HasHelpers::Cards::Importer.run([Advisor, Carrier]) # or symbols: [:advisor, :carrier]

# Options (keyword args)
HasHelpers::Cards::Importer.run(resources: [:advisor], mode: :apply, audit: true)

Supported modes (ClickUp):

- mode: :dry_run → compute and print diffs, no DB writes,
raise error if audit fails.
- mode: :apply   → default. Always audit first. If drift is breaking,
raise and do not persist.

Raises:

  • (ArgumentError)


87
88
89
90
91
92
93
94
95
# File 'app/importers/has_helpers/cards/importer.rb', line 87

def run(*args, **opts)
  ::HasHelpers::Cards::Registry.cards
  resources, mode, audit = normalize_run_arguments(args, opts)

  application = opts[:application] || opts[:application_name]
  raise ArgumentError, "application is required (e.g. application: 'AgenciesHQ')" if application.blank?

  new(application: application, resources: resources, mode: mode, audit: audit).run
end

Instance Method Details

#hover_only_definition?(raw_def) ⇒ Boolean

Definitions where hover_only: true are skipped at import — they live in memory only and are assembled from the registry at request time.

Returns:

  • (Boolean)


206
207
208
209
# File 'app/importers/has_helpers/cards/importer.rb', line 206

def hover_only_definition?(raw_def)
  defn = raw_def.is_a?(Hash) ? raw_def.deep_symbolize_keys : raw_def.to_h.deep_symbolize_keys
  !!defn[:hover_only]
end

#purge_persisted_hover_only_cards!(card_pairs) ⇒ Object

Removes any persisted CardDefinition rows that match a hover-only DSL definition (by identifier). Hover-only cards are in-memory only; this purges any rows left over from prior runs before we made them in-memory. Safe to call repeatedly. Dependent rows cascade via :dependent => :destroy associations.



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'app/importers/has_helpers/cards/importer.rb', line 215

def purge_persisted_hover_only_cards!(card_pairs)
  return if mode == :dry_run

  hover_only_ids =
    card_pairs.
      select { |_klass, defn| hover_only_definition?(defn) }.
      map { |_klass, defn| normalize_identifier((defn.deep_symbolize_keys)[:identifier]) }.
      compact.
      uniq
  return if hover_only_ids.empty?

  identifier_ids = ::HasHelpers::Cards::CardIdentifier.where(name: hover_only_ids).pluck(:id)
  return if identifier_ids.empty?

  ::HasHelpers::Cards::CardDefinition.where(identifier_id: identifier_ids).find_each(&:destroy)
end

#runObject

Main execution entry.

"Importer Flow"

Steps:

1. (Optionally) run migrations for cards (hook only for now).
2. Discover card definitions from the DSL (CardBuilder::Base descendants).
3. For each card:
  - Normalize + compute checksum (code view).
  - Build DB view (if card exists).
  - Diff + classify changes.
  - Audit: if breaking drift, raise AuditError.
  - If mode = :apply, upsert card_definitions + card_fields +
    card_definition_resources inside a transaction.
  - If mode = :dry_run, just log diffs and don't write.
4. Optional global audit: detect cards existing only in DB but
 not in code, when there is no resource filter (breaking).
5. Log summary of created/updated/skipped records.


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'app/importers/has_helpers/cards/importer.rb', line 173

def run
  # "We may add a migrations section just like we have for reports and webhooks"
  run_migrations_if_any!

  # Discover all card definitions exposed by the DSL (HasHelpers::Cards::CardBuilder::Base subclasses).
  card_pairs = discover_card_definitions
  # Each element is: [CardClass, raw_hash_definition]
  validate_hover_cards!(card_pairs)

  # Hover-only cards live entirely in-memory (DSL registry). They are NOT persisted.
  # Any pre-existing rows for hover-only identifiers (whether currently flagged as
  # hover_only in DB or not) are dropped here, then we skip the import for them below.
  purge_persisted_hover_only_cards!(card_pairs)

  # Import each card definition (or audit, depending on mode), skipping hover-only definitions.
  card_pairs.each do |klass, raw_def|
    next if hover_only_definition?(raw_def)
    import_card!(klass, raw_def)
  end

  # Global audit for "cards present in DB but missing in code".
  # This is only meaningful when we are importing "all resources" (i.e. no resource filter is applied).
  audit_removed_cards!(card_pairs) if audit && resource_filter.nil?

  # Log structured summary for debugging / future dashboards.
  log_summary

  # Return Result struct (created/updated counts, etc.)
  result
end