database_recorder 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c43f3f7036d73a7133d4a79984dc7035ec2ccfcaccbf23b3e2f36053debc0f5
4
- data.tar.gz: 0d8bc0cdf33d2626811f95bfac45b6dfce0383455a39b58940350048c9d92a70
3
+ metadata.gz: d0b2575a34a9e68ee59f68884a492a33c071a05b9d9999caf3baeca5c070c11d
4
+ data.tar.gz: 29b78920c82393f3e9a23d334b7675d29dc96f3b77f13ff1bf33384cc3206a0d
5
5
  SHA512:
6
- metadata.gz: 611e9419ea6c8f1e536c2b0ba5ad19b46327842e15032168c14db5ac8d109cca14a15bf000329538b894851bb51e6ceaf22fc5970356f31f2f3abd182cced339
7
- data.tar.gz: 25eba80806a035ca9da8063a1d745e218b3a0851b008fb0b31b3036680c82758210fa596d3386d31daed0bbee72041242150bcadd37337a828918ee511c0fdb1
6
+ metadata.gz: 0c7c56cc0c95e24b410ec5d63ac64b1944a20d7084d63ae64b0d80ea265b815435d2c3ce14eec3fb9d2f69261c567ddd59eaba24b4ca0c28f3993272fbb2e33b
7
+ data.tar.gz: 78db7a6377ba564f5e039bd74f32ffe4c4f6d6c6ff5719340818738a7a4ea40caf4f2164da1cf72e1ebb3800863e1921d3c2bbf32952ff00dc1154487367fe4a
data/README.md CHANGED
@@ -70,6 +70,12 @@ DatabaseRecorder::Config.replay_recordings = true
70
70
  # To store the queries: :file | :redis | nil
71
71
  DatabaseRecorder::Config.storage = :redis
72
72
  # nil to avoid storing the queries
73
+
74
+ # File storage options
75
+ DatabaseRecorder::Config.storage_options = { recordings_path: '/some/path' }
76
+
77
+ # Redis storage options
78
+ DatabaseRecorder::Config.storage_options = { connection: Redis.new }
73
79
  ```
74
80
 
75
81
  ## History of the queries
@@ -18,16 +18,16 @@ module DatabaseRecorder
18
18
  if Config.replay_recordings && Recording.from_cache
19
19
  Recording.push(sql: sql, binds: binds)
20
20
  data = Recording.cached_query_for(sql)
21
- return yield if !data || !data['result'] # cache miss
21
+ return yield if !data || !data[:result] # cache miss
22
22
 
23
- RecordedResult.new(data['result']['fields'], data['result']['values'])
23
+ RecordedResult.new(data[:result][:fields], data[:result][:values])
24
24
  else
25
25
  yield.tap do |result|
26
26
  result_data =
27
27
  if result && (result.respond_to?(:fields) || result.respond_to?(:columns))
28
28
  fields = result.respond_to?(:fields) ? result.fields : result.columns
29
29
  values = result.respond_to?(:values) ? result.values : result.to_a
30
- { 'count' => result.count, 'fields' => fields, 'values' => values }
30
+ { count: result.count, fields: fields, values: values }
31
31
  end
32
32
  Recording.push(sql: sql, name: name, binds: type_casted_binds, result: result_data)
33
33
  end
@@ -17,18 +17,20 @@ module DatabaseRecorder
17
17
  redis: DatabaseRecorder::Storage::Redis
18
18
  }.freeze
19
19
 
20
- attr_accessor :db_driver, :print_queries, :replay_recordings, :storage
20
+ attr_accessor :db_driver, :print_queries, :replay_recordings, :storage, :storage_options
21
21
 
22
22
  class << self
23
23
  extend Forwardable
24
24
 
25
- def_delegators :instance, :db_driver, :print_queries, :replay_recordings, :replay_recordings=, :storage
25
+ def_delegators :instance, :db_driver, :print_queries, :replay_recordings, :replay_recordings=, :storage,
26
+ :storage_options, :storage_options=
26
27
 
27
28
  def load_defaults
28
29
  instance.db_driver = DEFAULT_DB_DRIVER
29
30
  instance.print_queries = false
30
31
  instance.replay_recordings = false
31
32
  instance.storage = DEFAULT_STORAGE
33
+ instance.storage_options = {}
32
34
  end
33
35
 
34
36
  def db_driver=(value)
@@ -22,5 +22,29 @@ module DatabaseRecorder
22
22
  when :pg then PG::Recorder.setup
23
23
  end
24
24
  end
25
+
26
+ def string_keys_recursive(hash)
27
+ {}.tap do |h|
28
+ hash.each do |key, value|
29
+ h[key.to_s] = transform(value, :string_keys_recursive)
30
+ end
31
+ end
32
+ end
33
+
34
+ def symbolize_recursive(hash)
35
+ {}.tap do |h|
36
+ hash.each do |key, value|
37
+ h[key.to_sym] = transform(value, :symbolize_recursive)
38
+ end
39
+ end
40
+ end
41
+
42
+ def transform(value, source_method)
43
+ case value
44
+ when Hash then method(source_method).call(value)
45
+ when Array then value.map { |v| transform(v, source_method) }
46
+ else value
47
+ end
48
+ end
25
49
  end
26
50
  end
@@ -17,10 +17,10 @@ module DatabaseRecorder
17
17
  alias :size :count
18
18
 
19
19
  def prepare(data)
20
- @count = data['count']
21
- @fields = data['fields']
22
- @entries = data['values']
23
- # @values = data['values']
20
+ @count = data[:count]
21
+ @fields = data[:fields]
22
+ @entries = data[:values]
23
+ # @values = data[:values]
24
24
  end
25
25
 
26
26
  # def server_flags
@@ -13,7 +13,7 @@ module DatabaseRecorder
13
13
  end
14
14
 
15
15
  def format_result(result)
16
- { 'count' => result.count, 'fields' => result.fields, 'values' => result.to_a } if result.is_a?(::Mysql2::Result)
16
+ { count: result.count, fields: result.fields, values: result.to_a } if result.is_a?(::Mysql2::Result)
17
17
  # else
18
18
  # last_insert_id = adapter.query('SELECT LAST_INSERT_ID() AS _dbr_last_insert_id').to_a
19
19
  # { 'count' => last_insert_id.count, 'fields' => ['id'], 'values' => last_insert_id }
@@ -36,14 +36,14 @@ module DatabaseRecorder
36
36
 
37
37
  def store_prepared_statement(adapter, source:, binds:)
38
38
  # sql = @last_prepared&.send(:[], 'sql')
39
- sql = @last_prepared['sql']
39
+ sql = @last_prepared[:sql]
40
40
  Core.log_query(sql, source)
41
41
  if Config.replay_recordings && !Recording.cache.nil?
42
- data = Recording.cache.find { |query| query['sql'] == sql }
42
+ data = Recording.cache.find { |query| query[:sql] == sql }
43
43
  return yield unless data # cache miss
44
44
 
45
- Recording.push(sql: data['sql'], binds: data['binds'], source: source)
46
- RecordedResult.new(data['result'].slice('count', 'fields', 'values'))
45
+ Recording.push(sql: data[:sql], binds: data[:binds], source: source)
46
+ RecordedResult.new(data[:result].slice(:count, :fields, :values))
47
47
  else
48
48
  yield.tap do |result|
49
49
  Recording.update_prepared(sql: sql, binds: binds, result: format_result(result), source: source)
@@ -60,7 +60,7 @@ module DatabaseRecorder
60
60
  data = Recording.cached_query_for(sql)
61
61
  return yield unless data # cache miss
62
62
 
63
- RecordedResult.new.prepare(data['result'].slice('count', 'fields', 'values')) if data['result']
63
+ RecordedResult.new.prepare(data[:result].slice(:count, :fields, :values)) if data[:result]
64
64
  else
65
65
  yield.tap do |result|
66
66
  Recording.push(sql: sql, result: format_result(result), source: source)
@@ -11,9 +11,9 @@ module DatabaseRecorder
11
11
  alias :rows :values
12
12
 
13
13
  def initialize(data)
14
- @count = data['count']
15
- @fields = data['fields']
16
- @values = data['values']
14
+ @count = data[:count]
15
+ @fields = data[:fields]
16
+ @values = data[:values]
17
17
  end
18
18
 
19
19
  def clear; end
@@ -12,7 +12,7 @@ module DatabaseRecorder
12
12
  end
13
13
 
14
14
  def format_result(result)
15
- { 'count' => result.count, 'fields' => result.fields, 'values' => result.values } if result
15
+ { count: result.count, fields: result.fields, values: result.values } if result
16
16
  end
17
17
 
18
18
  def prepare_statement(sql: nil, name: nil, binds: nil, source: nil)
@@ -28,18 +28,18 @@ module DatabaseRecorder
28
28
 
29
29
  def store_prepared_statement(name: nil, sql: nil, binds: nil, source: nil)
30
30
  if Config.replay_recordings && !Recording.cache.nil?
31
- data = Recording.cache.find { |query| query['name'] == name }
31
+ data = Recording.cache.find { |query| query[:name] == name }
32
32
  return yield unless data # cache miss
33
33
 
34
- Core.log_query(data['sql'], source)
35
- Recording.push(sql: data['sql'], binds: data['binds'], source: source)
36
- RecordedResult.new(data['result'].slice('count', 'fields', 'values'))
34
+ Core.log_query(data[:sql], source)
35
+ Recording.push(sql: data[:sql], binds: data[:binds], source: source)
36
+ RecordedResult.new(data[:result].slice(:count, :fields, :values))
37
37
  else
38
38
  Core.log_query(sql, source)
39
39
  yield.tap do |query_result|
40
40
  result = format_result(query_result)
41
41
  query = Recording.update_prepared(name: name, sql: sql, binds: binds, result: result, source: source)
42
- Core.log_query(query['sql'], source)
42
+ Core.log_query(query[:sql], source)
43
43
  end
44
44
  end
45
45
  end
@@ -54,7 +54,7 @@ module DatabaseRecorder
54
54
  data = Recording.cached_query_for(sql)
55
55
  return yield unless data # cache miss
56
56
 
57
- RecordedResult.new(data['result'].slice('count', 'fields', 'values'))
57
+ RecordedResult.new(data[:result].slice(:count, :fields, :values))
58
58
  else
59
59
  yield.tap do |result|
60
60
  Recording.push(name: name, sql: sql, binds: binds, result: format_result(result), source: source)
@@ -4,13 +4,14 @@ require 'forwardable'
4
4
 
5
5
  module DatabaseRecorder
6
6
  class Recording
7
- attr_accessor :cache, :entities
7
+ attr_accessor :cache, :entities, :metadata
8
8
  attr_reader :from_cache, :options, :prepared_queries, :queries, :started
9
9
 
10
10
  def initialize(options: {})
11
11
  (@@instances ||= {})[Process.pid] = self
12
12
  @cache = nil
13
13
  @entities = []
14
+ @metadata = {}
14
15
  @options = options
15
16
  @queries = []
16
17
  @search_index = 0
@@ -21,7 +22,7 @@ module DatabaseRecorder
21
22
  current = @search_index
22
23
  match = cache[@search_index..].find do |item|
23
24
  current += 1
24
- item['sql'] == sql
25
+ item[:sql] == sql
25
26
  end
26
27
  return unless match
27
28
 
@@ -32,7 +33,7 @@ module DatabaseRecorder
32
33
  end
33
34
 
34
35
  def new_entity(model:, id:)
35
- @entities.push('model' => model, 'id' => id)
36
+ @entities.push(model: model, id: id)
36
37
  end
37
38
 
38
39
  def pull_entity
@@ -40,32 +41,32 @@ module DatabaseRecorder
40
41
  end
41
42
 
42
43
  def push(sql:, name: nil, binds: nil, result: nil, source: nil)
43
- query = { 'name' => name, 'sql' => sql, 'binds' => binds, 'result' => result }.compact
44
+ query = { name: name, sql: sql, binds: binds, result: result }.compact
44
45
  @queries.push(query)
45
46
  end
46
47
 
47
48
  def push_prepared(name: nil, sql: nil, binds: nil, result: nil, source: nil)
48
- query = { 'name' => name, 'sql' => sql, 'binds' => binds, 'result' => result }.compact
49
+ query = { name: name, sql: sql, binds: binds, result: result }.compact
49
50
  @@prepared_queries[name || sql] = query
50
51
  end
51
52
 
52
53
  def start
53
54
  @started = true
54
- storage = Config.storage&.new(self, name: options[:name])
55
+ storage = Config.storage&.new(self, name: options[:name], options: Config.storage_options)
55
56
  @from_cache = storage&.load
56
57
  yield
57
58
  storage&.save unless from_cache
58
59
  @started = false
59
- result = { current_queries: queries.map { _1['sql'] } }
60
- result[:stored_queries] = cache.map { _1['sql'] } if from_cache
60
+ result = { current_queries: queries.map { |query| query[:sql] } }
61
+ result[:stored_queries] = cache.map { |query| query[:sql] } if from_cache
61
62
  result
62
63
  end
63
64
 
64
65
  def update_prepared(name: nil, sql: nil, binds: nil, result: nil, source: nil)
65
66
  query = @@prepared_queries[name || sql]
66
- query['sql'] = sql if sql
67
- query['binds'] = binds if binds
68
- query['result'] = result if result
67
+ query[:sql] = sql if sql
68
+ query[:binds] = binds if binds
69
+ query[:result] = result if result
69
70
  @queries.push(query)
70
71
  query
71
72
  end
@@ -16,6 +16,7 @@ module DatabaseRecorder
16
16
  options.merge!(example.metadata[:dbr]) if example.metadata[:dbr].is_a?(Hash)
17
17
  options.merge!(example: example, name: "#{example.full_description}__#{ref}")
18
18
  Recording.new(options: options).tap do |recording|
19
+ recording.metadata = { example: example.id, started_at: Time.now }
19
20
  result = recording.start { example.run }
20
21
  if options[:verify_queries] && result[:stored_queries]
21
22
  expect(result[:stored_queries]).to match_array(result[:current_queries])
@@ -3,9 +3,10 @@
3
3
  module DatabaseRecorder
4
4
  module Storage
5
5
  class Base
6
- def initialize(recording, name:)
6
+ def initialize(recording, name:, options: {})
7
7
  @recording = recording
8
8
  @name = name
9
+ @options = options
9
10
  end
10
11
  end
11
12
  end
@@ -4,11 +4,12 @@ module DatabaseRecorder
4
4
  module Storage
5
5
  class File < Base
6
6
  def load
7
- stored_data = ::File.exist?(record_file) ? ::File.read(record_file) : false
7
+ stored_data = ::File.exist?(storage_path) ? ::File.read(storage_path) : false
8
8
  if stored_data
9
- data = YAML.load(stored_data) # rubocop:disable Security/YAMLLoad
10
- @recording.cache = data['queries']
11
- @recording.entities = data['entities']
9
+ parsed_data = YAML.load(stored_data) # rubocop:disable Security/YAMLLoad
10
+ data = Core.symbolize_recursive(parsed_data)
11
+ @recording.cache = data[:queries] || []
12
+ @recording.entities = data[:entities]
12
13
  true
13
14
  else
14
15
  false
@@ -16,10 +17,22 @@ module DatabaseRecorder
16
17
  end
17
18
 
18
19
  def save
19
- data = { 'queries' => @recording.queries }
20
- data['entities'] = @recording.entities if @recording.entities.any?
21
- serialized_data = data.to_yaml
22
- ::File.write(record_file, serialized_data)
20
+ data = {}
21
+ data[:metadata] = @recording.metadata unless @recording.metadata.empty?
22
+ data[:queries] = @recording.queries if @recording.queries.any?
23
+ data[:entities] = @recording.entities if @recording.entities.any?
24
+ serialized_data = Core.string_keys_recursive(data).to_yaml
25
+ ::File.write(storage_path, serialized_data)
26
+ true
27
+ end
28
+
29
+ def storage_path
30
+ @storage_path ||= begin
31
+ name = normalize_name(@name)
32
+ path = @options[:recordings_path] || 'spec/dbr'
33
+ FileUtils.mkdir_p(path)
34
+ "#{path}/#{name}.yml"
35
+ end
23
36
  end
24
37
 
25
38
  private
@@ -27,13 +40,6 @@ module DatabaseRecorder
27
40
  def normalize_name(string)
28
41
  string.gsub(%r{[:/]}, '-').gsub(/[^\w-]/, '_')
29
42
  end
30
-
31
- def record_file
32
- name = normalize_name(@name)
33
- path = 'spec/dbr'
34
- FileUtils.mkdir_p(path)
35
- "#{path}/#{name}.yml"
36
- end
37
43
  end
38
44
  end
39
45
  end
@@ -3,12 +3,17 @@
3
3
  module DatabaseRecorder
4
4
  module Storage
5
5
  class Redis < Base
6
+ def connection
7
+ @connection ||= @options[:connection] || ::Redis.new
8
+ end
9
+
6
10
  def load
7
- stored_data = self.class.connection.get(@name)
11
+ stored_data = connection.get(@name)
8
12
  if stored_data
9
- data = JSON.parse(stored_data)
10
- @recording.cache = data['queries']
11
- @recording.entities = data['entities']
13
+ parsed_data = JSON.parse(stored_data)
14
+ data = Core.symbolize_recursive(parsed_data)
15
+ @recording.cache = data[:queries] || []
16
+ @recording.entities = data[:entities]
12
17
  true
13
18
  else
14
19
  false
@@ -16,16 +21,13 @@ module DatabaseRecorder
16
21
  end
17
22
 
18
23
  def save
19
- data = { 'queries' => @recording.queries }
20
- data['entities'] = @recording.entities if @recording.entities.any?
24
+ data = {}
25
+ data[:metadata] = @recording.metadata unless @recording.metadata.empty?
26
+ data[:queries] = @recording.queries if @recording.queries.any?
27
+ data[:entities] = @recording.entities if @recording.entities.any?
21
28
  serialized_data = data.to_json
22
- self.class.connection.set(@name, serialized_data)
23
- end
24
-
25
- class << self
26
- def connection
27
- @connection ||= ::Redis.new
28
- end
29
+ connection.set(@name, serialized_data)
30
+ true
29
31
  end
30
32
  end
31
33
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :nocov:
3
4
  module DatabaseRecorder
4
- VERSION = '0.2.0'
5
+ VERSION = '0.2.1'
5
6
  end
7
+ # :nocov:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: database_recorder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mattia Roccoberton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-14 00:00:00.000000000 Z
11
+ date: 2022-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coderay