def gen_mcfly_lookup_cat(name, catrel, attrs, options = {})
      rel_attr, cat_assoc_name, cat_attr = catrel

      raise "#{rel_attr} should be mapped in attrs" if attrs[rel_attr].nil?

      cat_assoc_klass = cat_assoc_name.constantize
      cat_attr_id = "#{cat_attr}_id"

      # replace rel_attr with cat_attr in attrs
      pc_attrs = attrs.each_with_object({}) do |(k, v), h|
        h[k == rel_attr ? cat_attr_id : k] = v
      end

      pc_name = "pc_#{name}".to_sym

      gen_mcfly_lookup(pc_name, pc_attrs, options + { private: true })

      lpi = attrs.keys.index rel_attr

      raise "should not include #{cat_attr}" if attrs.member?(cat_attr)
      raise "need #{rel_attr} argument" unless lpi

      # cache if mode is not nil
      fn = options.fetch(:mode, :first) ? :cached_delorean_fn : :delorean_fn
      priv = options[:private]

      send(fn, name, sig: attrs.length + 1) do |ts, *args|
        # Example: rel is a Gemini::SecurityInstrument instance.
        rel = args[lpi]
        raise "#{rel_attr} can't be nil" unless rel

        args[lpi] = cat_assoc_klass.
                      mcfly_pt(ts).
                      select(cat_attr_id).
                      find_by(rel_attr => rel).
                      send(cat_attr_id)

        q = send(pc_name, ts, *args)
        hash_if_necessary(q, priv)
      end
    end
def gen_mcfly_lookup_cat(name, catrel, attrs, options = {})
      rel_attr, cat_assoc_name, cat_attr = catrel

      raise "#{rel_attr} should be mapped in attrs" if attrs[rel_attr].nil?

      cat_assoc_klass = cat_assoc_name.constantize
      cat_attr_id = "#{cat_attr}_id"

      # replace rel_attr with cat_attr in attrs
      pc_attrs = attrs.each_with_object({}) do |(k, v), h|
        h[k == rel_attr ? cat_attr_id : k] = v
      end

      pc_name = "pc_#{name}".to_sym

      gen_mcfly_lookup(pc_name, pc_attrs, options + { private: true })

      lpi = attrs.keys.index rel_attr

      raise "should not include #{cat_attr}" if attrs.member?(cat_attr)
      raise "need #{rel_attr} argument" unless lpi

      # cache if mode is not nil
      fn = options.fetch(:mode, :first) ? :cached_delorean_fn : :delorean_fn
      priv = options[:private]

      send(fn, name, sig: attrs.length + 1) do |ts, *args|
        # Example: rel is a Gemini::SecurityInstrument instance.
        rel = args[lpi]
        raise "#{rel_attr} can't be nil" unless rel

        args[lpi] = cat_assoc_klass.
                      mcfly_pt(ts).
                      select(cat_attr_id).
                      find_by(rel_attr => rel).
                      send(cat_attr_id)

        q = send(pc_name, ts, *args)
        hash_if_necessary(q, priv)
      end
    end
Generates categorization lookups, e.g. given class GFee:
 gen_mcfly_lookup_cat :lookup_q,
 [:security_instrument,
  'Gemini::SecurityInstrumentCategorization',
  :g_fee_category],
 {
   entity: true,
   security_instrument: true,
   coupon: true,
 },
 nil
 rel_attr        = :security_instrument
 cat_assoc_klass = Gemini::SecurityInstrumentCategorization
 cat_attr        = :g_fee_category
 name            = :lookup_q
 pc_name         = :pc_lookup_q
 pc_attrs        = {entity: true, security_instrument: true, coupon: true}