This document defines all data models for the Codeplug Application. The app follows a "Rosetta Stone" pattern where users create a single canonical codeplug that can be exported to many different radio formats.
- System: A radio repeater or simplex frequency with technical specifications (shared resource)
- Zone: A standalone template defining which systems and talkgroups to include (owned by user, can be public/private)
- Channel: A user's configuration to access a System, generated from zones or created manually
- Codeplug: A user's complete radio programming configuration (contains generated channels, references zones)
- RadioModel: A specific make/model of radio with capabilities and limits
- CodeplugLayout: The CSV export format for a specific RadioModel
- TalkGroup: Digital radio talkgroup (DMR, P25, etc.)
- Network: Organization of TalkGroups (e.g., Brandmeister, DMRVA)
The zone architecture follows a template-based approach:
Zone (template, owned by user)
├── ZoneSystem (systems in this zone)
│ └── ZoneSystemTalkGroup (talkgroups for digital systems)
└── Linked to Codeplugs via CodeplugZone
Codeplug (user's radio configuration)
├── CodeplugZone (references to zones, ordered)
└── Channels (generated from zones or manual)
└── ChannelZone (channel position within zones)
Workflow:
- User creates standalone Zones with Systems and Talkgroups
- User adds Zones to a Codeplug via CodeplugZone
- User clicks "Generate Channels" to create Channels from the zones
- Channels are created with
source_zone_idtracking their origin - User can customize generated channels (changes persist until regeneration)
Standard Rails 8 authentication model with user preferences.
Attributes:
email(string, unique, required)password_digest(string, required)name(string)callsign(string) - ham radio callsigndefault_power_level(string)measurement_preference(string) - display format preferences
Associations:
has_many :codeplugshas_many :zones- standalone zones owned by user
Validations:
- Email presence and uniqueness
- Password requirements (Rails 8 default)
Radio manufacturers (Motorola, Baofeng, Kenwood, etc.)
Attributes:
name(string, required, unique)system_record(boolean) - true for system-provided recordsuser_id(integer, foreign key, nullable) - creator for user-defined records
Associations:
has_many :radio_modelsbelongs_to :user, optional: true
Validations:
- Name presence and uniqueness
Specific radio make/model with capabilities and constraints.
Attributes:
manufacturer_id(integer, foreign key, required)name(string, required) - e.g., "UV-5R", "APX 8000"supported_modes(text, array) - ["analog", "dmr", "p25", etc.]max_zones(integer) - maximum number of zones, null if unlimitedmax_channels_per_zone(integer) - maximum channels per zonelong_channel_name_length(integer)short_channel_name_length(integer)long_zone_name_length(integer)short_zone_name_length(integer)frequency_ranges(text/json) - array of hashessystem_record(boolean) - true for system-provided recordsuser_id(integer, foreign key, nullable) - creator for user-defined records
Associations:
belongs_to :manufacturerbelongs_to :user, optional: truehas_many :codeplug_layouts
Validations:
- Manufacturer presence
- Name presence
- At least one supported mode
Defines CSV export format for a specific RadioModel.
Attributes:
radio_model_id(integer, foreign key, required)name(string, required) - e.g., "Chirp CSV Format", "CPS Standard"user_id(integer, foreign key, nullable) - creator, null for system defaultslayout_definition(text/json) - field mapping configuration
Associations:
belongs_to :radio_modelbelongs_to :user, optional: true
Validations:
- Radio model presence
- Name presence
- Valid JSON structure for layout_definition
Organization/network that operates TalkGroups (e.g., Brandmeister, DMRVA).
Attributes:
name(string, required, unique)description(text)website(string)network_type(string) - e.g., "Digital-DMR", "Digital-P25", "Digital-NXDN"
Associations:
has_many :talk_groupshas_many :system_networkshas_many :systems, through: :system_networks
Validations:
- Name presence and uniqueness
Digital radio talkgroup identifier (DMR, P25, etc.).
Attributes:
network_id(integer, foreign key, required)name(string, required) - e.g., "Virginia", "Worldwide"talkgroup_number(string, required) - e.g., "3181", "91"description(text)
Associations:
belongs_to :networkhas_many :system_talk_groupshas_many :systems, through: :system_talk_groups
Validations:
- Network presence
- Name and talkgroup_number presence
- Unique talkgroup_number within network
Notes:
- Talkgroup numbers stored as strings to handle leading zeros
- Same talkgroup can be on different timeslots on different systems
A radio repeater or simplex frequency with technical specifications. Shared across all users.
Attributes:
name(string, required) - e.g., "W4ABC Repeater"mode(string, required, enum) - "analog", "dmr", "p25", "nxdn", etc.tx_frequency(decimal, required) - repeater transmit (radio receive)rx_frequency(decimal, required) - repeater receive (radio transmit)bandwidth(string) - default bandwidthsupports_tx_tone(boolean, default: false)supports_rx_tone(boolean, default: false)tx_tone_value(string, nullable)rx_tone_value(string, nullable)city,state,county(strings)latitude,longitude(decimals)mode_detail_id(integer, foreign key, polymorphic)mode_detail_type(string, polymorphic)
Associations:
belongs_to :mode_detail, polymorphic: truehas_many :system_networkshas_many :networks, through: :system_networkshas_many :system_talk_groupshas_many :talk_groups, through: :system_talk_groupshas_many :channelshas_many :zone_systemshas_many :zones, through: :zone_systems
Validations:
- Name, mode, tx_frequency, rx_frequency presence
- Valid mode from enum
- Frequencies within valid ranges
Base for mode-specific system attributes.
Attributes:
color_code(integer, required, 0-15)
Attributes:
nac(string, required) - Network Access Code
Attributes:
- (No additional attributes needed)
Associates Systems with Networks (many-to-many).
Attributes:
system_id(integer, foreign key, required)network_id(integer, foreign key, required)
Associations:
belongs_to :systembelongs_to :network
Validations:
- Unique combination of system_id and network_id
Associates TalkGroups with Systems, including timeslot for DMR.
Attributes:
system_id(integer, foreign key, required)talk_group_id(integer, foreign key, required)timeslot(integer, nullable) - DMR timeslot (1 or 2)
Associations:
belongs_to :systembelongs_to :talk_grouphas_many :channelshas_many :zone_system_talk_groups
Validations:
- System and talk_group presence
- Unique combination of system, talk_group, and timeslot
- Timeslot 1 or 2 if present (DMR only)
User's complete radio programming configuration.
Attributes:
user_id(integer, foreign key, required)name(string, required)description(text)public(boolean, default: false)
Associations:
belongs_to :userhas_many :channels, dependent: :destroyhas_many :codeplug_zones, dependent: :destroyhas_many :zones, through: :codeplug_zones
Validations:
- User presence
- Name presence
Notes:
- Zones are linked via CodeplugZone (many-to-many)
- Channels are generated from zones or created manually
- Can be exported to multiple radio formats
Standalone template defining which systems and talkgroups to include. Owned by a user and can be public or private.
Attributes:
user_id(integer, foreign key, required)name(string, required)long_name(string) - for radios supporting long zone namesshort_name(string) - for radios requiring short zone namespublic(boolean, default: false) - whether other users can view/use
Associations:
belongs_to :userhas_many :zone_systems, dependent: :destroyhas_many :systems, through: :zone_systemshas_many :codeplug_zones, dependent: :destroyhas_many :codeplugs, through: :codeplug_zoneshas_many :channel_zones, dependent: :destroyhas_many :channels, through: :channel_zones
Scopes:
publicly_visible- zones marked as publicowned_by(user)- zones owned by specific useravailable_to_user(user)- public zones OR owned by user
Methods:
editable_by?(user)- true if user owns the zoneviewable_by?(user)- true if public OR owned by user
Validations:
- User presence
- Name presence
Notes:
- Zones are templates, not containers
- Public zones can be added to any user's codeplug
- Systems and talkgroups define what channels will be generated
Associates Systems with Zones, with position tracking.
Attributes:
zone_id(integer, foreign key, required)system_id(integer, foreign key, required)position(integer, required) - order within zone
Associations:
belongs_to :zonebelongs_to :systemhas_many :zone_system_talkgroups, dependent: :destroyhas_many :system_talkgroups, through: :zone_system_talkgroups
Validations:
- Zone and system presence
- Position > 0
- Unique system within zone
- Unique position within zone
Notes:
- Position determines system order when generating channels
- For digital systems, talkgroups are added via ZoneSystemTalkGroup
Associates SystemTalkGroups with ZoneSystems for digital modes.
Attributes:
zone_system_id(integer, foreign key, required)system_talk_group_id(integer, foreign key, required)
Associations:
belongs_to :zone_systembelongs_to :system_talk_group
Validations:
- ZoneSystem and SystemTalkGroup presence
- Unique combination
- SystemTalkGroup must belong to the same system as the ZoneSystem
Notes:
- Only used for digital systems (DMR, P25, NXDN)
- Each ZoneSystemTalkGroup results in one generated channel
Associates Zones with Codeplugs, with position tracking.
Attributes:
codeplug_id(integer, foreign key, required)zone_id(integer, foreign key, required)position(integer, required) - order within codeplug
Associations:
belongs_to :codeplugbelongs_to :zone
Validations:
- Codeplug and zone presence
- Position > 0
- Unique zone within codeplug
- Unique position within codeplug
Default Scope:
- Ordered by position ascending
Notes:
- Position determines zone order when generating channels
- Same zone can be in multiple codeplugs
User's configuration to access a System. Can be generated from zones or created manually.
Attributes:
codeplug_id(integer, foreign key, required)system_id(integer, foreign key, required)system_talk_group_id(integer, foreign key, nullable) - only for digital modessource_zone_id(integer, foreign key, nullable) - zone this channel was generated fromname(string, required)long_name(string)short_name(string)power_level(string)bandwidth(string, nullable)tone_mode(string, enum) - "none", "tx_only", "rx_only", "tx_rx"transmit_permission(string, enum) - "allow", "forbid_tx"
Associations:
belongs_to :codeplugbelongs_to :systembelongs_to :system_talk_group, optional: truebelongs_to :source_zone, class_name: "Zone", optional: truehas_many :channel_zones, dependent: :destroyhas_many :zones, through: :channel_zones
Methods:
generated?- true if source_zone_id is present
Validations:
- Codeplug and system presence
- Name presence
- Valid tone_mode from enum
- Valid transmit_permission from enum
Notes:
source_zone_idtracks which zone the channel was generated from- Generated channels can be customized; changes persist until regeneration
- For digital systems, must reference a SystemTalkGroup
Associates Channels with Zones, with position tracking.
Attributes:
channel_id(integer, foreign key, required)zone_id(integer, foreign key, required)position(integer, required) - channel number within zone (1-based)
Associations:
belongs_to :channelbelongs_to :zone
Validations:
- Channel and zone presence
- Position > 0
- Unique position within zone
Notes:
- Position determines channel order within zone
- Same channel can be at different positions in different zones
analogdmrp25nxdn
none- no tonestx_only- transmit tone onlyrx_only- receive tone only (squelch)tx_rx- both transmit and receive tones
allow- transmit allowedforbid_tx- receive only
Service class that generates channels from zones for a codeplug.
Usage:
generator = ChannelGenerator.new(codeplug)
result = generator.generate_channels(regenerate: false)
# => { channels_created: 5, channel_zones_created: 5, zones_processed: 2, skipped: false }Behavior:
- For analog systems: creates one channel per system
- For digital systems: creates one channel per ZoneSystemTalkGroup
- Sets
source_zone_idon generated channels - Creates ChannelZone records with correct positions
- With
regenerate: true: destroys existing channels first - Without
regenerate: skips if channels already exist
- Zones are standalone entities owned by users
- Zones can be public (viewable/usable by all) or private
- Zones define systems and talkgroups, not channels directly
- Zones are linked to codeplugs via CodeplugZone
- Channels are generated from zones using ChannelGenerator
- Process zones in CodeplugZone position order
- Within each zone, process systems in ZoneSystem position order
- For analog systems: create one channel per system
- For digital systems: create one channel per ZoneSystemTalkGroup
- Set source_zone_id to track origin
- Create ChannelZone with sequential positions
- If system mode is digital, channel must reference a system_talkgroup
- If system mode is analog, channel should not reference a system_talkgroup
- Channel tone_mode options limited by system's supports_tx_tone and supports_rx_tone
- Zones have unlimited channels in app
- On export to RadioModel:
- Check RadioModel.max_channels_per_zone
- If zone exceeds limit, prompt user for split strategy
- Generate physical zones on-the-fly during export
Tones stored as strings in database.
CTCSS (Hz):
- "67.0", "71.9", "74.4", "77.0", "79.7", "82.5", "85.4", "88.5", "91.5", "94.8", "97.4", "100.0", "103.5", "107.2", "110.9", "114.8", "118.8", "123.0", "127.3", "131.8", "136.5", "141.3", "146.2", "151.4", "156.7", "162.2", "167.9", "173.8", "179.9", "186.2", "192.8", "203.5", "210.7", "218.1", "225.7", "233.6", "241.8", "250.3"
DCS (Codes):
- "023", "025", "026", "031", "032", "036", "043", "047", "051", "053", "054", "065", "071", "072", "073", "074", etc.
Recommended indexes for performance:
users.email(unique)zones.user_idzones.publiczone_systems.zone_id, zone_systems.position(unique)zone_systems.zone_id, zone_systems.system_id(unique)zone_system_talk_groups.zone_system_id, zone_system_talk_groups.system_talk_group_id(unique)codeplug_zones.codeplug_id, codeplug_zones.position(unique)codeplug_zones.codeplug_id, codeplug_zones.zone_id(unique)channels.codeplug_idchannels.system_idchannels.source_zone_idchannel_zones.zone_id, channel_zones.position(unique)- Foreign key indexes on all join tables