Class: Parse::Schema::SchemaDiff

Inherits:
Object
  • Object
show all
Defined in:
lib/parse/schema.rb

Overview

Represents the difference between local model and server schema.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model_class, server_schema) ⇒ SchemaDiff

Returns a new instance of SchemaDiff.



222
223
224
225
# File 'lib/parse/schema.rb', line 222

def initialize(model_class, server_schema)
  @model_class = model_class
  @server_schema = server_schema
end

Instance Attribute Details

#model_classObject (readonly)

Returns the value of attribute model_class.



220
221
222
# File 'lib/parse/schema.rb', line 220

def model_class
  @model_class
end

#server_schemaObject (readonly)

Returns the value of attribute server_schema.



220
221
222
# File 'lib/parse/schema.rb', line 220

def server_schema
  @server_schema
end

Instance Method Details

#in_sync?Boolean

Check if schemas are in sync.

Strict / bidirectional: requires the local and server schemas to match in BOTH directions — no fields missing on the server, no fields missing locally, and no type mismatches. A server that is a strict superset of the local model is NOT "in sync" by this measure (use #server_covers_local? for the one-way local ⊆ server check).

Returns:

  • (Boolean)


312
313
314
# File 'lib/parse/schema.rb', line 312

def in_sync?
  missing_on_server.empty? && missing_locally.empty? && type_mismatches.empty?
end

#missing_locallyHash

Fields on server but not defined locally.

Returns:

  • (Hash)

    field name => type pairs



257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/parse/schema.rb', line 257

def missing_locally
  return {} unless server_exists?

  server = @server_schema.fields
  local = local_field_names
  missing = {}
  server.each do |name, info|
    # Skip core fields
    next if %w[objectId createdAt updatedAt ACL].include?(name)
    missing[name] = info[:type] unless local.include?(name) || local.include?(name.underscore.to_sym)
  end
  missing
end

#missing_on_serverHash

Fields defined locally but missing on server.

Iterates the model's field_map (one entry per canonical property, canonical name => wire column name) rather than fields (which carries both the snake_case and camelCase keys for every property and therefore double-counts multi-word fields). The wire name resolved from field_map is the authoritative server column — including custom field: mappings — so this both dedupes and fixes custom-column detection. Result is keyed by the CANONICAL (snake) name with the type taken from fields[name].

Returns:

  • (Hash)

    canonical field name => type pairs



244
245
246
247
248
249
250
251
252
253
# File 'lib/parse/schema.rb', line 244

def missing_on_server
  server = server_exists? ? server_field_names : []
  missing = {}
  @model_class.field_map.each do |name, wire|
    next if core_field?(name)
    next if server.include?(wire.to_s)
    missing[name] = @model_class.fields[name]
  end
  missing
end

#server_covers_local?Boolean

Check whether the server schema covers every locally-defined field.

One-way (local ⊆ server): true when nothing the model declares is missing on the server and there are no type mismatches. Unlike #in_sync?, this ignores server-only columns, so a server that is a strict superset of the local model still satisfies it. This is the predicate that determines whether a migration has any work to do — extra server columns are not something the migrator would add.

Returns:

  • (Boolean)


325
326
327
# File 'lib/parse/schema.rb', line 325

def server_covers_local?
  missing_on_server.empty? && type_mismatches.empty?
end

#server_exists?Boolean

Check if server schema exists.

Returns:

  • (Boolean)


229
230
231
# File 'lib/parse/schema.rb', line 229

def server_exists?
  !@server_schema.nil?
end

#summaryString

Generate a human-readable summary.

Returns:



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/parse/schema.rb', line 331

def summary
  lines = ["Schema diff for #{@model_class.parse_class}:"]

  if !server_exists?
    lines << "  - Class does not exist on server"
  elsif in_sync?
    lines << "  - Schemas are in sync"
  else
    unless missing_on_server.empty?
      lines << "  Missing on server:"
      missing_on_server.each { |n, t| lines << "    + #{n}: #{t}" }
    end
    unless missing_locally.empty?
      lines << "  Missing locally:"
      missing_locally.each { |n, t| lines << "    - #{n}: #{t}" }
    end
    unless type_mismatches.empty?
      lines << "  Type mismatches:"
      type_mismatches.each { |n, m| lines << "    ~ #{n}: local=#{m[:local]}, server=#{m[:server]}" }
    end
  end

  lines.join("\n")
end

#type_mismatchesHash

Fields with type mismatches.

Iterates field_map (canonical name => wire column) rather than deriving the server key with camelize(:lower), so a property with a custom field: wire column (e.g. property :post_id, field: "postIdentifier") resolves to its real server column instead of a camelized guess. This both dedupes multi-word fields (which appear under two keys in fields) and matches the missing_on_server resolution path, so type drift on custom-mapped columns is no longer silently skipped.

Returns:

  • (Hash)

    canonical field name => { local: type, server: type }



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/parse/schema.rb', line 282

def type_mismatches
  return {} unless server_exists?

  mismatches = {}
  @model_class.field_map.each do |name, wire|
    next if core_field?(name)
    local_type = @model_class.fields[name]
    next if local_type.nil?
    server_type = @server_schema.field_type(wire.to_s)
    next unless server_type

    # Normalize types for comparison
    normalized_local = normalize_type(local_type)
    normalized_server = normalize_type(server_type)

    if normalized_local != normalized_server
      mismatches[name] = { local: local_type, server: server_type }
    end
  end
  mismatches
end