database_recorder 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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