Module: Mcfly::Model::ClassMethods

Defined in:
lib/marty/mcfly_model.rb

Instance Method Summary collapse

Instance Method Details

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



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/marty/mcfly_model.rb', line 55

def base_mcfly_lookup(meth, name, options = {}, &block)

  priv = options[:private]

  send(meth, name, options) do |ts, *args|
    raise "time cannot be nil" if ts.nil?

    ts = Mcfly.normalize_infinity(ts)

    q = self.where("#{table_name}.obsoleted_dt >= ? AND " +
               "#{table_name}.created_dt < ?", ts, ts).scoping do
      block.call(ts, *args)
    end
    next q if priv

    fa = get_final_attrs
    q = q.select(*fa) if fa.present? && q.is_a?(ActiveRecord::Relation)

    case
    when q.is_a?(ActiveRecord::Relation)
      # shouldn't happen - lookups that are mode nil should be
      # private raise "#{self}.#{name} can't convert
      # ActiveRecord::Relation to OpenStruct"
      q
    when q.is_a?(ActiveRecord::Base)
      make_openstruct(q)
    else
      q
    end
  end
end

#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
# 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



87
88
89
# File 'lib/marty/mcfly_model.rb', line 87

def cached_mcfly_lookup(name, options = {}, &block)
  base_mcfly_lookup(:cached_delorean_fn, name, options, &block)
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



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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/marty/mcfly_model.rb', line 95

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_delorean_fn : :delorean_fn
  base_mcfly_lookup(fn, name, options + {sig: attrs.length+1}) 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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/marty/mcfly_model.rb', line 163

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

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



91
92
93
# File 'lib/marty/mcfly_model.rb', line 91

def mcfly_lookup(name, options = {}, &block)
  base_mcfly_lookup(:delorean_fn, name, options, &block)
end