{"record":{"id":"318c36d4d96da7f3","repo":"instructure/canvas-lms","slug":"do-not-look-up-mediaobjects-by-media-id-use-the-scope-by","errorCode":null,"errorMessage":"Do not look up MediaObjects by media_id - use the scope by_media_id instead to support migrated content.","messagePattern":"Do not look up MediaObjects by media_id - use the scope by_media_id instead to support migrated content\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"app/models/media_object.rb","lineNumber":80,"sourceCode":"  after_create :retrieve_details_later\n  after_save :update_title_on_kaltura_later\n  serialize :data\n\n  attr_accessor :podcast_associated_asset, :current_attachment\n\n  def user_entered_title=(val)\n    @push_user_title = true\n    super\n  end\n\n  def update_title_on_kaltura_later\n    delay.update_title_on_kaltura if @push_user_title\n    @push_user_title = nil\n  end\n\n  def self.find_by(**kwargs)\n    if kwargs.key?(:media_id) && !Rails.env.production?\n      raise \"Do not look up MediaObjects by media_id - use the scope by_media_id instead to support migrated content.\"\n    end\n\n    super\n  end\n\n  def context_root_account(user = nil)\n    # Granular Permissions\n    #\n    # The primary use case for this method is for accurately checking\n    # feature flag enablement, given a user and the calling context.\n    # We want to prefer finding the root_account through the context\n    # of the authorizing resource or fallback to the user's active\n    # pseudonym's residing account.\n    return context.account if context.is_a?(User)\n\n    # return nil and don't raise if receiver doesn't respond to :root_account\n    context.try(:root_account) || user&.account\n  end","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/media_object.rb#L62-L98","documentation":"MediaObject.find_by is overridden to reject lookups keyed only on media_id outside production, because media_id is not globally unique after content migrations — records can share a media_id across contexts. Callers must use the by_media_id scope which resolves the correct object per context.","triggerScenarios":"Calling MediaObject.find_by(media_id: id) or find_by(media_id:, ...) with other keys in a non-production Rails environment; specs or console code using the plain finder.","commonSituations":"Old code predating migrated-content support; grep-found snippets copied from older plugins; developers probing in rails console in dev/test.","solutions":["Replace MediaObject.find_by(media_id: x) with MediaObject.by_media_id(x) (then narrow per context/attachment as needed)","If filtering by more keys is needed, use by_media_id scope plus additional where clauses","Update specs/console commands to the scope"],"exampleFix":"// before\nmo = MediaObject.find_by(media_id: media_id)\n// after\nmo = MediaObject.by_media_id(media_id).first","handlingStrategy":"type-guard","validationCode":"mo = MediaObject.by_media_id(media_id).first # never find_by(media_id:)","typeGuard":"def safe_media_lookup(media_id, context)\n  MediaObject.by_media_id(media_id).where(context_type: context.class.name, context_id: context.id).first\nend","tryCatchPattern":"begin\n  MediaObject.find_by(media_id: id)\nrescue RuntimeError => e\n  raise unless e.message.include?('by_media_id')\n  MediaObject.by_media_id(id).first\nend","preventionTips":["Grep the codebase for find_by(media_id: and replace with by_media_id scope","Note the guard is skipped in production — migrate code before deploy"],"tags":["ruby","activerecord","media","migrations"],"backgroundTag":"deprecated-api-usage","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}