Skip to content

Post, Event, and Image are separate models — not a unified Post model

Circle.so uses a single Post model with post_type (basic|event|image) and conditional attributes (event_settings_attributes for events, gallery for images). We chose separate models — Post, Event, Image — each in its own table with only its relevant fields.

Considered Options

  • Unified Post (Circle.so model): Single table with nullable columns. Easier to add a "recent activity" feed that mixes types. Harder to validate type-specific constraints at the database level. Event-specific fields (e.g. starts_at) would be nullable on every row.
  • Separate models (chosen): Each model has only the fields it needs, with database-enforced NOT NULL constraints where appropriate. Event-specific validation lives in Event.clean(), not in conditional logic on Post.

Why separate was chosen

Separate models give stronger schema guarantees. An Event has starts_at and ends_at as required fields — in a unified Post model they'd be nullable, and any post_type=basic row would have nulls in event columns. The ORM makes cross-type queries harder (need GenericForeignKey or unions), but in practice the app rarely needs to query across types — you browse posts in a basic space, events in an event space, images in an image space.

Consequences

  • Adding a new content type (e.g. polls, audio) means a new model, views, serializer, URL routing, and tests — not just a new post_type value. This is more upfront work but produces more explicit, self-documenting code.
  • Cross-type features like "recent activity feed" require querying three tables and merging results, which is less efficient than a single query on Post.

Strum — Documentação.