database_recorder 0.1.1 → 0.2.2

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: bd8b63b3fa1b57dd6c2c5cdf501af590f75a5fcb52c322bc4eee702c55026db6
4
- data.tar.gz: f2753254f7c7d79ef0fd7dd65e3439197419ae01d96e4625eb3a9f4632835bd9
3
+ metadata.gz: 48d1f4f88105fa9ac49075181f90208ca051bec2273daaeaf77cca9ad2c1ed77
4
+ data.tar.gz: 9ef096a48a9a0dafd7bbab635a20805d4b5ec0f06710d9b596554a1f03016c8e
5
5
  SHA512:
6
- metadata.gz: 343167b893fd491b57405ad465e1b835fcf077d3cc806a4c3a4e6ca7c1253e0967acca90760c30e395de30aa8529421305910f9714879f118892259acf03dbe7
7
- data.tar.gz: a2f8defdbd1e9f251afd6c999077870689c20f9c7868cc5d01a2947ab63c3ee1eec2c5401b14eef1378e450dbeb2fb170f93d011e54f8c8e677c150394f0b024
6
+ metadata.gz: 177da34267bb1fb58fb1b99e68d68430f8d36822facffb9fcd42e2e024176bbfc7be4d57ef807ce834bf1522151255c1614e5a9bb73833d86b70eb502aac0a30
7
+ data.tar.gz: 43d907dd4dc7902280bb379a4ae20dadca9c89712e62a188be7609795efd0c87db968c0b938e303e1f3e38bf44e23d34ed91be567169e118b42b3c00bd7e212e
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![Specs PostgreSQL](https://github.com/blocknotes/database_recorder/actions/workflows/specs_postgres.yml/badge.svg)](https://github.com/blocknotes/database_recorder/actions/workflows/specs_postgres.yml)
7
7
 
8
8
  Record database queries for testing and development purposes.
9
- Supports only RSpec at the moment. Store queries information on files or Redis.
9
+ Store queries information on files or Redis.
10
10
 
11
11
  Main features:
12
12
  - store the history of the queries of a test when it run (for monitoring);
@@ -15,10 +15,16 @@ Main features:
15
15
 
16
16
  Sample output: [test.yml](extra/sample.yml)
17
17
 
18
+ Creating an environment variable to enable it with RSpec:
19
+
20
+ ![image1](extra/image1.png)
21
+
18
22
  ## Install
19
23
 
24
+ ### With RSpec
25
+
20
26
  - Add to your Gemfile: `gem 'database_recorder', require: false` (:development, :test groups recommended)
21
- - Using RSpec, add in **rails_helper.rb**:
27
+ - Add in **rails_helper.rb**:
22
28
 
23
29
  ```rb
24
30
  require 'database_recorder'
@@ -49,9 +55,20 @@ RSpec.configure do |config|
49
55
  end
50
56
  ```
51
57
 
52
- Using an environment variable to enable it:
58
+ ### With plain Ruby
53
59
 
54
- ![image1](extra/image1.png)
60
+ ```rb
61
+ DatabaseRecorder::Config.db_driver = :pg
62
+ DatabaseRecorder::Core.setup
63
+ DatabaseRecorder::Recording.new(options: { name: 'pg_file' }).tap do |recording|
64
+ pp recording.start do
65
+ PG.connect(DB_CONFIG).exec("INSERT INTO tags(name, created_at, updated_at) VALUES('tag1', NOW(), NOW())")
66
+ PG.connect(DB_CONFIG).exec("SELECT * FROM tags")
67
+ end
68
+ end
69
+ ```
70
+
71
+ Please check more [examples](examples).
55
72
 
56
73
  ## Config
57
74
 
@@ -61,7 +78,10 @@ Add to your _spec_helper.rb_:
61
78
  # Database driver to use: :active_record | :mysql2 | :pg
62
79
  DatabaseRecorder::Config.db_driver = :pg
63
80
 
64
- # To print the queries while executing the specs: false | true | :color
81
+ # Log queries format (default: '[DB] %sql [%name]')
82
+ DatabaseRecorder::Config.log_format = '>>> %name -- %sql'
83
+
84
+ # To print/log the queries while executing the specs: false | true | :color
65
85
  DatabaseRecorder::Config.print_queries = true
66
86
 
67
87
  # Replay the recordings intercepting the queries
@@ -70,6 +90,12 @@ DatabaseRecorder::Config.replay_recordings = true
70
90
  # To store the queries: :file | :redis | nil
71
91
  DatabaseRecorder::Config.storage = :redis
72
92
  # nil to avoid storing the queries
93
+
94
+ # File storage options
95
+ DatabaseRecorder::Config.storage_options = { recordings_path: '/some/path' }
96
+
97
+ # Redis storage options
98
+ DatabaseRecorder::Config.storage_options = { connection: Redis.new }
73
99
  ```
74
100
 
75
101
  ## 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
@@ -8,6 +8,7 @@ module DatabaseRecorder
8
8
  include Singleton
9
9
 
10
10
  DEFAULT_DB_DRIVER = :active_record
11
+ DEFAULT_LOG_FORMAT = '[DB] %sql [%name]'
11
12
  DEFAULT_STORAGE = DatabaseRecorder::Storage::File
12
13
 
13
14
  DB_DRIVER_VALUES = %i[active_record mysql2 pg].freeze
@@ -17,18 +18,21 @@ module DatabaseRecorder
17
18
  redis: DatabaseRecorder::Storage::Redis
18
19
  }.freeze
19
20
 
20
- attr_accessor :db_driver, :print_queries, :replay_recordings, :storage
21
+ attr_accessor :db_driver, :log_format, :print_queries, :replay_recordings, :storage, :storage_options
21
22
 
22
23
  class << self
23
24
  extend Forwardable
24
25
 
25
- def_delegators :instance, :db_driver, :print_queries, :replay_recordings, :replay_recordings=, :storage
26
+ def_delegators :instance, :db_driver, :log_format, :log_format=, :print_queries, :replay_recordings,
27
+ :replay_recordings=, :storage, :storage_options, :storage_options=
26
28
 
27
29
  def load_defaults
28
30
  instance.db_driver = DEFAULT_DB_DRIVER
31
+ instance.log_format = DEFAULT_LOG_FORMAT
29
32
  instance.print_queries = false
30
33
  instance.replay_recordings = false
31
34
  instance.storage = DEFAULT_STORAGE
35
+ instance.storage_options = {}
32
36
  end
33
37
 
34
38
  def db_driver=(value)
@@ -7,8 +7,11 @@ module DatabaseRecorder
7
7
  def log_query(sql, source = nil)
8
8
  log =
9
9
  case DatabaseRecorder::Config.print_queries
10
- when true then "[DB] #{sql} [#{source}]"
11
- when :color then "[DB] #{CodeRay.scan(sql, :sql).term} [#{source}]"
10
+ when true
11
+ DatabaseRecorder::Config.log_format.sub('%name', source.to_s).sub('%sql', sql)
12
+ when :color
13
+ code_ray_sql = CodeRay.scan(sql, :sql).term
14
+ DatabaseRecorder::Config.log_format.sub('%name', source.to_s).sub('%sql', code_ray_sql || '')
12
15
  end
13
16
 
14
17
  puts log if log
@@ -22,5 +25,29 @@ module DatabaseRecorder
22
25
  when :pg then PG::Recorder.setup
23
26
  end
24
27
  end
28
+
29
+ def string_keys_recursive(hash)
30
+ {}.tap do |h|
31
+ hash.each do |key, value|
32
+ h[key.to_s] = transform(value, :string_keys_recursive)
33
+ end
34
+ end
35
+ end
36
+
37
+ def symbolize_recursive(hash)
38
+ {}.tap do |h|
39
+ hash.each do |key, value|
40
+ h[key.to_sym] = transform(value, :symbolize_recursive)
41
+ end
42
+ end
43
+ end
44
+
45
+ def transform(value, source_method)
46
+ case value
47
+ when Hash then method(source_method).call(value)
48
+ when Array then value.map { |v| transform(v, source_method) }
49
+ else value
50
+ end
51
+ end
25
52
  end
26
53
  end
@@ -4,13 +4,13 @@ module DatabaseRecorder
4
4
  module Mysql2
5
5
  module ClientExt
6
6
  def query(sql, options = {})
7
- Recorder.record(self, sql: sql) do
7
+ Recorder.store_query(self, sql: sql, source: :query) do
8
8
  super
9
9
  end
10
10
  end
11
11
 
12
12
  def prepare(*args)
13
- Recorder.record(self, sql: args[0]) do
13
+ Recorder.prepare_statement(self, sql: args[0], source: :prepare) do
14
14
  super
15
15
  end
16
16
  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
@@ -12,29 +12,16 @@ module DatabaseRecorder
12
12
  sql.match?(/information_schema.statistics/)
13
13
  end
14
14
 
15
- def record(adapter, sql:)
16
- return yield if ignore_query?(sql)
17
-
18
- Core.log_query(sql)
19
- if Config.replay_recordings && !Recording.cache.nil?
20
- Recording.push(sql: sql)
21
- data = Recording.cached_query_for(sql)
22
- return yield unless data # cache miss
15
+ def format_result(result)
16
+ { count: result.count, fields: result.fields, values: result.to_a } if result.is_a?(::Mysql2::Result)
17
+ # else
18
+ # last_insert_id = adapter.query('SELECT LAST_INSERT_ID() AS _dbr_last_insert_id').to_a
19
+ # { 'count' => last_insert_id.count, 'fields' => ['id'], 'values' => last_insert_id }
20
+ end
23
21
 
24
- RecordedResult.new.prepare(data['result'].slice('count', 'fields', 'values')) if data['result']
25
- else
26
- yield.tap do |result|
27
- result_data =
28
- if result.is_a? ::Mysql2::Result
29
- { 'count' => result.count, 'fields' => result.fields, 'values' => result.to_a }
30
- # else
31
- # last_insert_id = adapter.query('SELECT LAST_INSERT_ID() AS _dbr_last_insert_id').to_a
32
- # { 'count' => last_insert_id.count, 'fields' => ['id'], 'values' => last_insert_id }
33
- end
34
-
35
- Recording.push(sql: sql, result: result_data)
36
- end
37
- end
22
+ def prepare_statement(adapter, sql: nil, name: nil, binds: nil, source: nil)
23
+ @last_prepared = Recording.push_prepared(name: name, sql: sql, binds: binds, source: source)
24
+ yield if !Config.replay_recordings || Recording.cache.nil?
38
25
  end
39
26
 
40
27
  def setup
@@ -47,9 +34,38 @@ module DatabaseRecorder
47
34
  end
48
35
  end
49
36
 
50
- def update_record(adapter, *args)
51
- Recording.update_last(*args)
52
- yield
37
+ def store_prepared_statement(adapter, source:, binds:)
38
+ # sql = @last_prepared&.send(:[], 'sql')
39
+ sql = @last_prepared[:sql]
40
+ Core.log_query(sql, source)
41
+ if Config.replay_recordings && !Recording.cache.nil?
42
+ data = Recording.cache.find { |query| query[:sql] == sql }
43
+ return yield unless data # cache miss
44
+
45
+ Recording.push(sql: data[:sql], binds: data[:binds], source: source)
46
+ RecordedResult.new(data[:result].slice(:count, :fields, :values))
47
+ else
48
+ yield.tap do |result|
49
+ Recording.update_prepared(sql: sql, binds: binds, result: format_result(result), source: source)
50
+ end
51
+ end
52
+ end
53
+
54
+ def store_query(adapter, sql:, source:)
55
+ return yield if ignore_query?(sql)
56
+
57
+ Core.log_query(sql, source)
58
+ if Config.replay_recordings && !Recording.cache.nil?
59
+ Recording.push(sql: sql, source: source)
60
+ data = Recording.cached_query_for(sql)
61
+ return yield unless data # cache miss
62
+
63
+ RecordedResult.new.prepare(data[:result].slice(:count, :fields, :values)) if data[:result]
64
+ else
65
+ yield.tap do |result|
66
+ Recording.push(sql: sql, result: format_result(result), source: source)
67
+ end
68
+ end
53
69
  end
54
70
  end
55
71
  end
@@ -4,7 +4,7 @@ module DatabaseRecorder
4
4
  module Mysql2
5
5
  module StatementExt
6
6
  def execute(*args, **kwargs)
7
- Recorder.update_record(self, *args) do
7
+ Recorder.store_prepared_statement(self, source: :execute, binds: args) do
8
8
  super
9
9
  end
10
10
  end
@@ -4,47 +4,43 @@ module DatabaseRecorder
4
4
  module PG
5
5
  module ConnectionExt
6
6
  def async_exec(sql)
7
- Recorder.record(sql: sql, source: :async_exec) do
7
+ Recorder.store_query(sql: sql, source: :async_exec) do
8
8
  super
9
9
  end
10
10
  end
11
11
 
12
12
  def sync_exec(sql)
13
- Recorder.record(sql: sql, source: :sync_exec) do
13
+ Recorder.store_query(sql: sql, source: :sync_exec) do
14
14
  super
15
15
  end
16
16
  end
17
17
 
18
18
  def exec(*args)
19
- Recorder.record(sql: args[0], source: :exec) do
19
+ Recorder.store_query(sql: args[0], source: :exec) do
20
20
  super
21
21
  end
22
22
  end
23
23
 
24
- def query(*args)
25
- Recorder.record(sql: args[0], source: :query) do
24
+ def exec_params(*args)
25
+ Recorder.store_query(sql: args[0], binds: args[1], source: :exec_params) do
26
26
  super
27
27
  end
28
28
  end
29
29
 
30
- def exec_params(*args)
31
- Recorder.record(sql: args[0], binds: args[1], source: :exec_params) do
30
+ def exec_prepared(*args)
31
+ Recorder.store_prepared_statement(name: args[0], binds: args[1], source: :exec_prepared) do
32
32
  super
33
33
  end
34
34
  end
35
35
 
36
- # def async_exec_params(*args)
37
- # puts ">>> #{args[0]}"
38
- # super
39
- # end
40
-
41
- # def sync_exec_params(*args)
42
- # puts ">>> #{args[0]}"
43
- # super
44
- # end
36
+ def prepare(*args)
37
+ Recorder.prepare_statement(name: args[0], sql: args[1], source: :prepare) do
38
+ super
39
+ end
40
+ end
45
41
 
46
- def exec_prepared(*args)
47
- Recorder.record(sql: args[0], binds: args[1], source: :exec_prepared) do
42
+ def query(*args)
43
+ Recorder.store_query(sql: args[0], source: :query) do
48
44
  super
49
45
  end
50
46
  end
@@ -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
@@ -11,29 +11,56 @@ module DatabaseRecorder
11
11
  sql.match?(/ pg_attribute |SHOW max_identifier_length|SHOW search_path/)
12
12
  end
13
13
 
14
- def record(sql:, binds: nil, source: nil)
14
+ def format_result(result)
15
+ { count: result.count, fields: result.fields, values: result.values } if result
16
+ end
17
+
18
+ def prepare_statement(sql: nil, name: nil, binds: nil, source: nil)
19
+ Recording.push_prepared(name: name, sql: sql, binds: binds, source: source)
20
+ yield if !Config.replay_recordings || Recording.cache.nil?
21
+ end
22
+
23
+ def setup
24
+ ::PG::Connection.class_eval do
25
+ prepend ConnectionExt
26
+ end
27
+ end
28
+
29
+ def store_prepared_statement(name: nil, sql: nil, binds: nil, source: nil)
30
+ if Config.replay_recordings && !Recording.cache.nil?
31
+ data = Recording.cache.find { |query| query[:name] == name }
32
+ return yield unless data # cache miss
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))
37
+ else
38
+ Core.log_query(sql, source)
39
+ yield.tap do |query_result|
40
+ result = format_result(query_result)
41
+ query = Recording.update_prepared(name: name, sql: sql, binds: binds, result: result, source: source)
42
+ Core.log_query(query[:sql], source)
43
+ end
44
+ end
45
+ end
46
+
47
+ def store_query(name: nil, sql: nil, binds: nil, source: nil)
15
48
  return yield if ignore_query?(sql)
16
49
 
17
50
  Core.log_query(sql, source)
51
+ @prepared_statement = nil
18
52
  if Config.replay_recordings && !Recording.cache.nil?
19
- Recording.push(sql: sql, binds: binds)
53
+ Recording.push(sql: sql, binds: binds, source: source)
20
54
  data = Recording.cached_query_for(sql)
21
55
  return yield unless data # cache miss
22
56
 
23
- RecordedResult.new(data['result'].slice('count', 'fields', 'values'))
57
+ RecordedResult.new(data[:result].slice(:count, :fields, :values))
24
58
  else
25
59
  yield.tap do |result|
26
- result_data = result ? { 'count' => result.count, 'fields' => result.fields, 'values' => result.values } : nil
27
- Recording.push(sql: sql, binds: binds, result: result_data)
60
+ Recording.push(name: name, sql: sql, binds: binds, result: format_result(result), source: source)
28
61
  end
29
62
  end
30
63
  end
31
-
32
- def setup
33
- ::PG::Connection.class_eval do
34
- prepend ConnectionExt
35
- end
36
- end
37
64
  end
38
65
  end
39
66
  end
@@ -4,23 +4,25 @@ require 'forwardable'
4
4
 
5
5
  module DatabaseRecorder
6
6
  class Recording
7
- attr_accessor :cache, :entities
8
- attr_reader :from_cache, :options, :queries, :started
7
+ attr_accessor :cache, :entities, :metadata
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
18
+ @@prepared_queries ||= {}
17
19
  end
18
20
 
19
21
  def cached_query_for(sql)
20
22
  current = @search_index
21
23
  match = cache[@search_index..].find do |item|
22
24
  current += 1
23
- item['sql'] == sql
25
+ item[:sql] == sql
24
26
  end
25
27
  return unless match
26
28
 
@@ -31,39 +33,49 @@ module DatabaseRecorder
31
33
  end
32
34
 
33
35
  def new_entity(model:, id:)
34
- @entities.push('model' => model, 'id' => id)
36
+ @entities.push(model: model, id: id)
35
37
  end
36
38
 
37
39
  def pull_entity
38
40
  @entities.shift
39
41
  end
40
42
 
41
- def push(sql:, binds: nil, result: nil, name: nil)
42
- query = { 'name' => name, 'sql' => sql, 'binds' => binds, 'result' => result }.compact
43
+ def push(sql:, name: nil, binds: nil, result: nil, source: nil)
44
+ query = { name: name, sql: sql, binds: binds, result: result }.compact
43
45
  @queries.push(query)
44
46
  end
45
47
 
48
+ def push_prepared(name: nil, sql: nil, binds: nil, result: nil, source: nil)
49
+ query = { name: name, sql: sql, binds: binds, result: result }.compact
50
+ @@prepared_queries[name || sql] = query
51
+ end
52
+
46
53
  def start
47
54
  @started = true
48
- storage = Config.storage&.new(self, name: options[:name])
55
+ storage = Config.storage&.new(self, name: options[:name], options: Config.storage_options)
49
56
  @from_cache = storage&.load
50
57
  yield
51
58
  storage&.save unless from_cache
52
59
  @started = false
53
- result = { current_queries: queries.map { _1['sql'] } }
54
- 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
55
62
  result
56
63
  end
57
64
 
58
- def update_last(*args)
59
- @queries.last['binds'] = args
65
+ def update_prepared(name: nil, sql: nil, binds: nil, result: nil, source: nil)
66
+ query = @@prepared_queries[name || sql]
67
+ query[:sql] = sql if sql
68
+ query[:binds] = binds if binds
69
+ query[:result] = result if result
70
+ @queries.push(query)
71
+ query
60
72
  end
61
73
 
62
74
  class << self
63
75
  extend Forwardable
64
76
 
65
- def_delegators :current_instance, :cache, :cached_query_for, :from_cache, :new_entity, :pull_entity, :push,
66
- :queries, :update_last
77
+ def_delegators :current_instance, :cache, :cached_query_for, :from_cache, :new_entity, :prepared_queries,
78
+ :pull_entity, :push, :push_prepared, :queries, :update_prepared
67
79
 
68
80
  def current_instance
69
81
  (@@instances ||= {})[Process.pid]
@@ -13,9 +13,10 @@ module DatabaseRecorder
13
13
  config.around(:each, :dbr) do |example|
14
14
  ref = (example.metadata[:scoped_id] || '').split(':')[-1]
15
15
  options = {}
16
+ options[:name] = "#{example.full_description}__#{ref}"
16
17
  options.merge!(example.metadata[:dbr]) if example.metadata[:dbr].is_a?(Hash)
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
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
3
6
  module DatabaseRecorder
4
7
  module Storage
5
8
  class File < Base
6
9
  def load
7
- stored_data = ::File.exist?(record_file) ? ::File.read(record_file) : false
10
+ stored_data = ::File.exist?(storage_path) ? ::File.read(storage_path) : false
8
11
  if stored_data
9
- data = YAML.load(stored_data) # rubocop:disable Security/YAMLLoad
10
- @recording.cache = data['queries']
11
- @recording.entities = data['entities']
12
+ parsed_data = YAML.load(stored_data) # rubocop:disable Security/YAMLLoad
13
+ data = Core.symbolize_recursive(parsed_data)
14
+ @recording.cache = data[:queries] || []
15
+ @recording.entities = data[:entities]
12
16
  true
13
17
  else
14
18
  false
@@ -16,10 +20,22 @@ module DatabaseRecorder
16
20
  end
17
21
 
18
22
  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)
23
+ data = {}
24
+ data[:metadata] = @recording.metadata unless @recording.metadata.empty?
25
+ data[:queries] = @recording.queries if @recording.queries.any?
26
+ data[:entities] = @recording.entities if @recording.entities.any?
27
+ serialized_data = ::YAML.dump(Core.string_keys_recursive(data))
28
+ ::File.write(storage_path, serialized_data)
29
+ true
30
+ end
31
+
32
+ def storage_path
33
+ @storage_path ||= begin
34
+ name = normalize_name(@name)
35
+ path = @options[:recordings_path] || 'spec/dbr'
36
+ ::FileUtils.mkdir_p(path)
37
+ "#{path}/#{name}.yml"
38
+ end
23
39
  end
24
40
 
25
41
  private
@@ -27,13 +43,6 @@ module DatabaseRecorder
27
43
  def normalize_name(string)
28
44
  string.gsub(%r{[:/]}, '-').gsub(/[^\w-]/, '_')
29
45
  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
46
  end
38
47
  end
39
48
  end
@@ -1,14 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module DatabaseRecorder
4
6
  module Storage
5
7
  class Redis < Base
8
+ def connection
9
+ @connection ||= @options[:connection] || ::Redis.new
10
+ end
11
+
6
12
  def load
7
- stored_data = self.class.connection.get(@name)
13
+ stored_data = connection.get(@name)
8
14
  if stored_data
9
- data = JSON.parse(stored_data)
10
- @recording.cache = data['queries']
11
- @recording.entities = data['entities']
15
+ parsed_data = JSON.parse(stored_data)
16
+ data = Core.symbolize_recursive(parsed_data)
17
+ @recording.cache = data[:queries] || []
18
+ @recording.entities = data[:entities]
12
19
  true
13
20
  else
14
21
  false
@@ -16,16 +23,13 @@ module DatabaseRecorder
16
23
  end
17
24
 
18
25
  def save
19
- data = { 'queries' => @recording.queries }
20
- data['entities'] = @recording.entities if @recording.entities.any?
26
+ data = {}
27
+ data[:metadata] = @recording.metadata unless @recording.metadata.empty?
28
+ data[:queries] = @recording.queries if @recording.queries.any?
29
+ data[:entities] = @recording.entities if @recording.entities.any?
21
30
  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
31
+ connection.set(@name, serialized_data)
32
+ true
29
33
  end
30
34
  end
31
35
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :nocov:
3
4
  module DatabaseRecorder
4
- VERSION = '0.1.1'
5
+ VERSION = '0.2.2'
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.1.1
4
+ version: 0.2.2
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-11 00:00:00.000000000 Z
11
+ date: 2022-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coderay