{"record":{"id":"9effbbe81cf58acd","repo":"antiwork/gumroad","slug":"memberships-should-only-have-one-tier-version-cate","errorCode":null,"errorMessage":"Memberships should only have one Tier version category.","messagePattern":"Memberships should only have one Tier version category\\.","errorType":"exception","errorClass":"Link::LinkInvalid","httpStatus":null,"severity":"error","filePath":"app/models/link.rb","lineNumber":1884,"sourceCode":"    def alive_category_variants_presence\n      return if deleted_at.present?\n      return if archived?\n\n      has_alive_categories_without_variants = variant_categories.alive.left_joins(:alive_variants).where(base_variants: { id: nil }).exists?\n\n      if has_alive_categories_without_variants\n        errors.add(:base, \"Sorry, the product versions must have at least one option.\")\n      end\n    end\n\n    def valid_tier_version_structure\n      return if deleted_at.present?\n      return if archived?\n      return if purchase_disabled_at.present? && purchase_disabled_at_changed?\n\n      if variant_categories.alive.size != 1\n        errors.add(:base, \"Memberships should only have one Tier version category.\")\n        raise LinkInvalid, \"Memberships should only have one Tier version category.\"\n        return\n      end\n\n      if variant_categories.alive.first.variants.alive.size == 0\n        errors.add(:base, \"Memberships should have at least one tier.\")\n        raise LinkInvalid, \"Memberships should have at least one tier.\"\n      end\n    end\n\n    def quantity_enabled_state_is_allowed\n      if quantity_enabled && !can_enable_quantity?\n        errors.add(:base, \"Customers cannot be allowed to choose a quantity for this product.\")\n      end\n    end\n\n    def custom_permalink_or_is_licensed_changed?\n      custom_permalink_changed? || is_licensed_changed?\n    end","sourceCodeStart":1866,"sourceCodeEnd":1902,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/models/link.rb#L1866-L1902","documentation":"Raised as Link::LinkInvalid by Link#valid_tier_version_structure when a membership product does not have exactly one alive variant_category. Memberships are modeled with a single 'Tier' variant category holding the tier variants; 0 or 2+ alive categories breaks that contract and the save is aborted. Skipped entirely for deleted or archived links, or when purchase_disabled_at just changed.","triggerScenarios":"Saving an alive, non-archived membership link whose variant_categories.alive count is not 1 — creating a membership with no Tier category, or a duplicate Tier category (often via API/imports creating categories directly).","commonSituations":"Bulk importers or API clients creating variant categories the UI never would; cleanup scripts that soft-delete the one alive category; migrations leaving stray alive categories on membership links.","solutions":["Ensure membership products have exactly one alive variant category named for tiers; soft-delete extras before saving.","For API-created memberships, create the single Tier category then its variants — never multiple categories.","Rescue Link::LinkInvalid and show errors.full_messages."],"exampleFix":"# before: two alive categories on a membership\nlink.variant_categories.alive.count # => 2\nlink.save! # LinkInvalid\n\n# after: keep exactly one Tier category\nlink.variant_categories.alive.where.not(name: \"Tier\").each(&:mark_deleted)\nlink.save!","handlingStrategy":"validation","validationCode":"return { error: 'One Tier category required.' } if link.is_membership && link.variant_categories.alive.size != 1","typeGuard":null,"tryCatchPattern":"begin\n  link.save!\nrescue Link::LinkInvalid\n  render json: { errors: link.errors.full_messages }, status: :unprocessable_entity\nend","preventionTips":["Only ever create a single variant category on memberships (the Tier).","Audit imports/scripts that touch variant_categories on membership links.","Deleted/archived links and purchase_disabled_at transitions skip the check — don't rely on that to hide bad data."],"tags":["links","memberships","variants","tiers","rails","linkinvalid","validation"],"backgroundTag":"variant-category-structure-invalid","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}