decko-commons/decko

View on GitHub
card/lib/card/fetch/card_class.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
92%
class Card
  class Fetch
    # A multipurpose retrieval system that integrates caching, database lookups,
    # and "virtual" card construction
    module CardClass
      # Look for cards in
      #   * cache
      #   * database
      #   * virtual cards
      #
      # @param args [Integer, String, Card::Name, Symbol, Array]
      #    Initials args must be one or more "marks," which uniquely idenfify cards:
      #
      #
      #      1. a name/key. (String or Card::Name)
      #      2. a numeric id. Can be (a) an Integer or (b) a String with an integer
      #         prefixed with a tilde, eg "~1234"
      #      3. a codename. Can be (a) a Symbol or (b) a String with a colon prefix,
      #         eg :mycodename
      #
      #    If you pass more then one mark or an array of marks they get joined with a '+'.
      #    The final argument can be a Hash to set the following options
      #
      #        :skip_virtual               Real cards only
      #        :skip_modules               Don't load Set modules
      #        :look_in_trash              Return trashed card objects
      #        :local_only                 Use only local cache for lookup and storing
      #        new: { opts for Card#new }  Return a new card when not found
      #
      # @return [Card]
      def fetch *args
        f = Fetch.new(*args)
        f.retrieve_or_new
      rescue ActiveModel::RangeError => _e
        Card.new name: "card id out of range: #{f.mark}"
      end

      # a shortcut for fetch that returns only real cards (no virtuals)
      #
      # @param mark [various] - see {#fetch}
      # @return [Card]
      def [] *mark
        fetch(*mark, skip_virtual: true)
      end

      # fetch real cards without set modules loaded. Should only be used for
      # simple attributes
      #
      # @example
      #   quick_fetch "A", :self, :structure
      #
      # @param mark - see #fetch
      # @return [Card]
      def quick_fetch *mark
        fetch(*mark, skip_virtual: true, skip_modules: true)
      end

      # @return [Card]
      def fetch_from_cast cast
        fetch_args = cast[:id] ? [cast[:id].to_i] : [cast[:name], { new: cast }]
        fetch(*fetch_args)
      end

      #----------------------------------------------------------------------
      # ATTRIBUTE FETCHING
      # The following methods optimize fetching of specific attributes

      # numerical card id
      # @params cardish [various] interprets integers as id, symbols as codename, etc
      # @return [Integer]
      def id cardish
        case cardish
        when Integer then cardish
        when Card    then cardish.id
        when Symbol  then Codename.id cardish
        when String  then Lexicon.id cardish
        else quick_fetch(cardish)&.id
        end
      end

      # @param mark - see #fetch
      # @return [Card::Name]
      def fetch_name *mark, &block
        if (card = quick_fetch(*mark))
          card.name
        elsif block_given?
          yield.to_name
        end
      rescue StandardError => e
        rescue_fetch_name e, &block
      end

      # @param mark - see #fetch
      # @return [Integer]
      def fetch_type_id *mark
        fetch(*mark, skip_modules: true)&.type_id
      end

      # Specialized fetching appropriate for cards requested by URI
      # @param params [Hash] hash in the style of parameters expected by Decko
      # @option params [Hash] :card arguments for Card.new
      # @option params [String] :mark.
      # @option params [String] :type shortcut for card[:type]
      # @option params [True/False] :look_in_trash - passed to Card.fetch
      # @option params [True/False] :assign - override attributes of fetched card with
      #   card hash
      def uri_fetch params
        card_opts = uri_fetch_opts params
        if params[:action] == "create"
          # FIXME: we currently need a "new" card to catch duplicates
          # (otherwise save will just act like a normal update)
          # We may need a "#create" instance method to handle this checking?
          Card.new card_opts
        else
          standard_uri_fetch params, card_opts
        end
      end

      private

      def standard_uri_fetch params, card_opts
        mark = params[:mark] || card_opts[:name]
        card = fetch mark, new: card_opts,
                           skip_modules: true,
                           look_in_trash: params[:look_in_trash]
        card.assign_attributes card_opts if params[:assign] && card&.real?
        card&.include_set_modules
        card
      end

      def uri_fetch_opts params
        Env.hash(params[:card]).tap do |opts|
          opts[:type] ||= params[:type] if params[:type] # for /new/:type shortcut.
          opts[:name] ||= Name[params[:mark]]&.tr "_", " "
        end
      end

      def rescue_fetch_name error, &block
        if rescued_fetch_name_to_name? error, &block
          yield.to_name
        elsif error.is_a? ActiveModel::RangeError
          nil
        else
          raise error
        end
      end

      def rescued_fetch_name_to_name? error
        return unless block_given?

        error.is_a?(ActiveModel::RangeError) || error.is_a?(Error::CodenameNotFound)
      end
    end
  end
end