Module: Mcfly::Model::ClassMethods

Defined in:
lib/marty/mcfly_model.rb

Instance Method Summary collapse

Instance Method Details

#cached_delorean_fn(name, options = {}, &block) ⇒ Object

Implements a VERY HACKY class-based (per process) caching mechanism for database lookup results. Issues include: cached values are ActiveRecord objects. Query results can be very large lists which we count as one item in the cache. Caching mechanism will result in large processes.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/marty/mcfly_model.rb', line 23

def cached_delorean_fn(name, options = {}, &block)
  @LOOKUP_CACHE ||= {}

  delorean_fn(name, options) do |ts, *args|
    cache_key = [name, ts] + args.map{ |a|
      a.is_a?(ActiveRecord::Base) ? a.id : a
    } unless Mcfly.is_infinity(ts)

    next @LOOKUP_CACHE[cache_key] if
      cache_key && @LOOKUP_CACHE.has_key?(cache_key)

    res = block.call(ts, *args)

    if cache_key
      # Cache has >1000 items, clear out the oldest 200.  FIXME:
      # hard-coded, should be configurable.  Cache
      # size/invalidation should be per lookup and not class.
      # We're invalidating cache items simply based on age and
      # not usage.  This is faster but not as fair.
      if @LOOKUP_CACHE.count > 1000
        @LOOKUP_CACHE.keys[0..200].each{|k| @LOOKUP_CACHE.delete(k)}
      end
      @LOOKUP_CACHE[cache_key] = res

      # Since we're caching this object and don't want anyone
      # changing it.  FIXME: ideally should freeze this object
      # recursively.
      res.freeze unless res.is_a?(ActiveRecord::Relation)
    end
    res
  end
end

#cached_mcfly_lookup(name, options = {}, &block) ⇒ Object

FIXME: duplicate code from Mcfly’s mcfly_lookup.



57
58
59
60
61
62
63
64
65
66
67
# File 'lib/marty/mcfly_model.rb', line 57

def cached_mcfly_lookup(name, options = {}, &block)
  cached_delorean_fn(name, options) do |ts, *args|
    raise "nil timestamp" if ts.nil?

    ts = Mcfly.normalize_infinity(ts)

    self.mcfly_pt(ts).scoping do
      block.call(ts, *args)
    end
  end
end

#clear_lookup_cache!Object



9
10
11
# File 'lib/marty/mcfly_model.rb', line 9

def clear_lookup_cache!
  @LOOKUP_CACHE.clear if @LOOKUP_CACHE
end

#gen_mcfly_lookup(name, attrs, options = {}) ⇒ Object

FIXME: add private mode. This should make the function unavailable to delorean.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/marty/mcfly_model.rb', line 71

def gen_mcfly_lookup(name, attrs, options={})
  raise "bad options #{options.keys}" unless
    (options.keys - [:mode, :cache, :private]).empty?

  mode = options.fetch(:mode, :first)

  # if mode is nil, don't cache -- i.e. don't cache AR queries
  cache = mode && options[:cache]

  # the older mode=:all is not supported (it's bogus)
  raise "bad mode #{mode}" unless [nil, :first].member?(mode)

  assoc = Set.new(self.reflect_on_all_associations.map(&:name))

  qstr = attrs.map {|k, v|
    k = "#{k}_id" if assoc.member?(k)

    v ? "(#{k} = ? OR #{k} IS NULL)" : "(#{k} = ?)"
  }.join(" AND ")

  if Hash === attrs
    order = attrs.select {|k, v| v}.keys.reverse.map { |k|
      k = "#{k}_id" if assoc.member?(k)

      "#{k} NULLS LAST"
    }.join(", ")
    attrs = attrs.keys
  else
    raise "bad attrs" unless Array === attrs
  end

  fn = cache ? :cached_mcfly_lookup : :mcfly_lookup

  # hacky: if private, set sig to bad value -- i.e. can't be
  # called from delorean.  Ideally, we should have a 'private'
  # option for delorean_fn.
  sig = options[:private] ? -1 : attrs.length+1

  send(fn, name, sig: sig) do
    |t, *attr_list|

    attr_list_ids = attr_list.each_with_index.map {|x, i|
      assoc.member?(attrs[i]) ?
        (attr_list[i] && attr_list[i].id) : attr_list[i]
    }

    q = self.where(qstr, *attr_list_ids)
    q = q.order(order) if order
    mode ? q.send(mode) : q
  end
end

#gen_mcfly_lookup_cat(name, catrel, attrs, options = {}) ⇒ Object

rel_attr = :security_instrument cat_assoc_klass = Gemini::SecurityInstrumentCategorization cat_attr = :g_fee_category name = :lookup_q pc_name = :pc_lookup_q pc_attrs = true, security_instrument: true, coupon: true



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/marty/mcfly_model.rb', line 145

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

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

  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

  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).
                  # FIXME: XXXX why is this join needed???
                  # joins(cat_attr).
                  where(rel_attr => rel).
                  pluck("#{cat_attr}_id").
                  first

    self.send(pc_name, ts, *args)
  end
end