Module: TestProf::AnyFixture

Extended by:
Logging
Defined in:
lib/test_prof/any_fixture.rb,
lib/test_prof/any_fixture/dsl.rb,
lib/test_prof/any_fixture/dump.rb,
lib/test_prof/any_fixture/dump/digest.rb,
lib/test_prof/any_fixture/dump/sqlite.rb,
lib/test_prof/any_fixture/dump/postgresql.rb,
lib/test_prof/any_fixture/dump/base_adapter.rb

Overview

Make DB fixtures from blocks.

Defined Under Namespace

Modules: DSL Classes: Cache, Configuration, Dump

Constant Summary collapse

INSERT_RXP =
/^INSERT INTO (\S+)/.freeze
MODIFY_RXP =
/^(INSERT INTO|UPDATE|DELETE FROM) (\S+)/i.freeze
ANY_FIXTURE_RXP =
/(\/\*|--).*\bany_fixture:dump/.freeze
ANY_FIXTURE_IGNORE_RXP =
/(\/\*|--).*\bany_fixture:ignore/.freeze

Constants included from Logging

Logging::COLORS

Class Method Summary collapse

Methods included from Logging

log

Class Method Details

.after_fixtures_reset(&block) ⇒ Object



181
182
183
# File 'lib/test_prof/any_fixture.rb', line 181

def after_fixtures_reset(&block)
  callbacks[:after_fixtures_reset] << block
end

.before_fixtures_reset(&block) ⇒ Object



177
178
179
# File 'lib/test_prof/any_fixture.rb', line 177

def before_fixtures_reset(&block)
  callbacks[:before_fixtures_reset] << block
end

.cached(id) ⇒ Object



117
118
119
# File 'lib/test_prof/any_fixture.rb', line 117

def cached(id)
  cache.fetch(id) { yield }
end

.cleanObject

Clean all affected tables (but do not reset cache)



155
156
157
158
159
160
161
162
163
# File 'lib/test_prof/any_fixture.rb', line 155

def clean
  disable_referential_integrity do
    tables_cache.keys.reverse_each do |table|
      ActiveRecord::Base.connection.execute %(
        DELETE FROM #{table}
      )
    end
  end
end

.configObject



97
98
99
# File 'lib/test_prof/any_fixture.rb', line 97

def config
  @config ||= Configuration.new
end

.configure {|config| ... } ⇒ Object

Yields:



101
102
103
# File 'lib/test_prof/any_fixture.rb', line 101

def configure
  yield config
end

.register(id) ⇒ Object

Register a block of code as a fixture, returns the result of the block execution



107
108
109
110
111
112
113
114
115
# File 'lib/test_prof/any_fixture.rb', line 107

def register(id)
  cached(id) do
    raise "No fixture named #{id} has been registered" unless block_given?

    ActiveSupport::Notifications.subscribed(method(:subscriber), "sql.active_record") do
      yield
    end
  end
end

.register_dump(name, clean: true, **options) ⇒ Object

Create and register new SQL dump. Use ‘watch` to provide additional paths to watch for dump re-generation



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/test_prof/any_fixture.rb', line 124

def register_dump(name, clean: true, **options)
  called_from = caller_locations(1, 1).first.path
  watch = options.delete(:watch) || [called_from]
  cache_key = options.delete(:cache_key)
  skip = options.delete(:skip_if)

  id = "sql/#{name}"

  register_method = clean ? :register : :cached

  public_send(register_method, id) do
    dump = Dump.new(name, watch: watch, cache_key: cache_key)

    unless dump.force?
      next if skip&.call(dump: dump)

      next dump.within_prepared_env(import: true, **options) { dump.load } if dump.exists?
    end

    subscriber = ActiveSupport::Notifications.subscribe("sql.active_record", dump.subscriber)
    res = dump.within_prepared_env(**options) { yield }

    dump.commit!

    res
  ensure
    ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
  end
end

.report_statsObject



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/test_prof/any_fixture.rb', line 196

def report_stats
  if cache.stats.empty?
    log :info, "AnyFixture has not been used"
    return
  end

  msgs = []

  msgs <<
    <<~MSG
      AnyFixture usage stats:
    MSG

  first_column = cache.stats.keys.map(&:size).max + 2

  msgs << format(
    "%#{first_column}s  %12s  %9s  %12s",
    "key", "build time", "hit count", "saved time"
  )

  msgs << ""

  total_spent = 0.0
  total_saved = 0.0
  total_miss = 0.0

  cache.stats.to_a.sort_by { |(_, v)| -v[:hit] }.each do |(key, stats)|
    total_spent += stats[:time]

    saved = stats[:time] * stats[:hit]

    total_saved += saved

    total_miss += stats[:time] if stats[:hit].zero?

    msgs << format(
      "%#{first_column}s  %12s  %9d  %12s",
      key, stats[:time].duration, stats[:hit],
      saved.duration
    )
  end

  msgs <<
    <<~MSG

      Total time spent: #{total_spent.duration}
      Total time saved: #{total_saved.duration}
      Total time wasted: #{total_miss.duration}
    MSG

  log :info, msgs.join("\n")
end

.resetObject

Reset all information and clean tables



166
167
168
169
170
171
172
173
174
175
# File 'lib/test_prof/any_fixture.rb', line 166

def reset
  callbacks[:before_fixtures_reset].each(&:call)

  clean
  tables_cache.clear
  cache.clear

  callbacks[:after_fixtures_reset].each(&:call)
  callbacks.clear
end

.subscriber(_event, _start, _finish, _id, data) ⇒ Object



185
186
187
188
189
190
191
192
193
194
# File 'lib/test_prof/any_fixture.rb', line 185

def subscriber(_event, _start, _finish, _id, data)
  matches = data.fetch(:sql).match(INSERT_RXP)
  return unless matches

  table_name = matches[1]

  return if /sqlite_sequence/.match?(table_name)

  tables_cache[table_name] = true
end