snappier 0.1.0 → 0.1.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: 2927f513086b61df8edaadcc710dfdfde6bce30f94710c3d78a343ddce5b1161
4
- data.tar.gz: 825ad55fc4328d3168d7c37fbd4e85952546c9d0c77281b466f93e0e569e44ea
3
+ metadata.gz: a44e2f17a1206aea3035128b9d17325d26a1deac42391cbb279ff7178734b367
4
+ data.tar.gz: 3beeaa24a2d05bf6d473a53912cc770bc00a6179fd9e60f9fc45080a52c099a5
5
5
  SHA512:
6
- metadata.gz: e8a9a9c560b514a332ed813a53c1d9f9e9845ed44cbdbe7eb0bd36f0e5c9f4ffd834cc5d0f236a5b914f0d48a2d385e5f17ff06808b6d3e8dffa37f12f6ed30a
7
- data.tar.gz: fdff950af91eaa75ebfd44b02ecc3d25ebf58541fd10adb92cf57d6856ae0d3fae5318315b268f72d2e1d9127025146fa5240c889e8842c22796ae49368765a3
6
+ metadata.gz: '058f1a6f65c067d9a76da692572b46a1f8c2d994939cb78b784766d9edb00ad07985fcbd39823e33bbcbc6454807f873aa1ee026a438839e69f735d0bbca565f'
7
+ data.tar.gz: d73c327fd27b0e264253f7c4fd81d7dc40caed14c6a6f82a273f8ff9389f271ce5568b03031d691c6afcd9ad365da6ebe9860218230d34f20d2e8ead01f2e4f6
data/.rubocop.yml CHANGED
@@ -1,8 +1,37 @@
1
+ plugins:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+
1
5
  AllCops:
2
6
  TargetRubyVersion: 3.1
7
+ NewCops: enable
3
8
 
4
9
  Style/StringLiterals:
5
10
  EnforcedStyle: double_quotes
6
11
 
7
12
  Style/StringLiteralsInInterpolation:
8
13
  EnforcedStyle: double_quotes
14
+
15
+ Style/TrailingCommaInArguments:
16
+ EnforcedStyleForMultiline: comma
17
+
18
+ Style/Documentation:
19
+ Enabled: false
20
+
21
+ RSpec/ExampleLength:
22
+ Enabled: false
23
+
24
+ RSpec/InstanceVariable:
25
+ Enabled: false
26
+
27
+ RSpec/MultipleExpectations:
28
+ Enabled: false
29
+
30
+ RSpec/VerifiedDoubles:
31
+ Enabled: false
32
+
33
+ Metrics/MethodLength:
34
+ Max: 20
35
+
36
+ Metrics/AbcSize:
37
+ Max: 20
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
5
+
6
+ require "snappier"
7
+
8
+ type = ARGV.shift
9
+ id = ARGV.shift
10
+
11
+ Snappier::Replay.for(
12
+ type: type,
13
+ id: id,
14
+ ) do |entry|
15
+ pp entry
16
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Snappier
6
+ class FilePersistence
7
+ def persist(type:, id:, at:, args:)
8
+ path = File.join("tmp", "snappier", type, id, "#{at}.yml")
9
+ FileUtils.mkdir_p(File.dirname(path))
10
+ File.write(path, args.to_yaml)
11
+ end
12
+
13
+ def each(type:, id:)
14
+ dir_path = File.join("tmp", "snappier", type.to_s, id)
15
+ Dir["#{dir_path}/*.yml"].each do |file_path|
16
+ milliseconds = File.basename(file_path).split(".").first
17
+ yield(
18
+ {
19
+ at: Time.at(milliseconds.to_i / 1000),
20
+ content: YAML.load_file(file_path)
21
+ }
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "registry"
4
+ require "sidekiq"
5
+ require "yaml"
6
+
7
+ module Snappier
8
+ class Job
9
+ include Sidekiq::Job
10
+
11
+ def perform(args)
12
+ type = args["type"]
13
+ id = args["id"]
14
+ at = args["at"]
15
+
16
+ args["at"] = Time.at(at.to_i / 1000).to_s
17
+ args["state"] = JSON.parse(args["state"])
18
+
19
+ Registry.persistence.persist(
20
+ type: type,
21
+ id: id,
22
+ at: at,
23
+ args: args,
24
+ )
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "file_persistence"
4
+
5
+ module Snappier
6
+ module Registry
7
+ def self.reset
8
+ @for_class_name = {}
9
+ @persistence = nil
10
+ end
11
+
12
+ def self.all
13
+ @for_class_name
14
+ end
15
+
16
+ def self.register_persistence(instance)
17
+ @persistence = instance
18
+ end
19
+
20
+ def self.persistence
21
+ @persistence || FilePersistence.new
22
+ end
23
+
24
+ def self.register(map)
25
+ @for_class_name ||= {}
26
+ map.each_key do |key|
27
+ @for_class_name[key] = map[key]
28
+ end
29
+ end
30
+
31
+ def self.for_entity(entity)
32
+ @for_class_name ||= {}
33
+ class_name = @for_class_name[entity.class.name]
34
+ return unless class_name
35
+
36
+ class_name.split("::").reduce(Object) { |acc, elem| acc.const_get(elem) }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "registry"
4
+
5
+ module Snappier
6
+ module Replay
7
+ def self.for(type:, id:)
8
+ previous_state = {}
9
+
10
+ Registry.persistence.each(type: type, id: id) do |current|
11
+ content = current.delete(:content)
12
+ current[:who] = content.delete("who")
13
+ current[:state] = content.delete("state")
14
+ current[:changes] = changes(previous_state, current[:state])
15
+
16
+ yield(current)
17
+
18
+ previous_state = current[:state]
19
+ end
20
+ end
21
+
22
+ def self.changes(previous_state, current_state)
23
+ changes = {}
24
+
25
+ keys = Set.new(previous_state.keys + current_state.keys)
26
+
27
+ keys.each do |key|
28
+ next if previous_state[key] == current_state[key]
29
+
30
+ changes[key] = [previous_state[key], current_state[key]]
31
+ end
32
+
33
+ changes
34
+ end
35
+
36
+ def self.for_entity(entity)
37
+ self.for(type: entity.class.name, id: entity.id)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "registry"
4
+ require_relative "job"
5
+ require_relative "who"
6
+ require "json"
7
+
8
+ module Snappier
9
+ module Take
10
+ def self.for(entity)
11
+ action = "update"
12
+ action = "create" if entity.previously_new_record?
13
+ action = "destroy" if entity.destroyed?
14
+
15
+ snapper = Registry.for_entity(entity)
16
+ state = snapper ? snapper.snap(entity) : entity.attributes
17
+
18
+ Job.perform_async(
19
+ "at" => Time.now.strftime("%s%L"),
20
+ "action" => action,
21
+ "id" => entity.id,
22
+ "type" => entity.class.name,
23
+ "state" => state.to_json,
24
+ "who" => Who.current,
25
+ )
26
+
27
+ entity
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Snappier
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Snappier
4
+ module Who
5
+ def self.current=(value)
6
+ Thread.current[:snappier_who_current] = value
7
+ end
8
+
9
+ def self.current
10
+ Thread.current[:snappier_who_current]
11
+ end
12
+ end
13
+ end
data/lib/snappier.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "snappier/version"
4
+ require_relative "snappier/take"
5
+ require_relative "snappier/replay"
4
6
 
5
7
  module Snappier
6
8
  class Error < StandardError; end
metadata CHANGED
@@ -1,17 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snappier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Ryall
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 2025-05-25 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: sidekiq
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  email:
13
27
  - mark@ryall.name
14
- executables: []
28
+ executables:
29
+ - snappier_replay
15
30
  extensions: []
16
31
  extra_rdoc_files: []
17
32
  files:
@@ -21,8 +36,15 @@ files:
21
36
  - LICENSE.txt
22
37
  - README.md
23
38
  - Rakefile
39
+ - exe/snappier_replay
24
40
  - lib/snappier.rb
41
+ - lib/snappier/file_persistence.rb
42
+ - lib/snappier/job.rb
43
+ - lib/snappier/registry.rb
44
+ - lib/snappier/replay.rb
45
+ - lib/snappier/take.rb
25
46
  - lib/snappier/version.rb
47
+ - lib/snappier/who.rb
26
48
  - sig/snappier.rbs
27
49
  homepage: https://github.com/markryall/snappier
28
50
  licenses:
@@ -30,6 +52,7 @@ licenses:
30
52
  metadata:
31
53
  homepage_uri: https://github.com/markryall/snappier
32
54
  source_code_uri: https://github.com/markryall/snappier
55
+ rubygems_mfa_required: 'true'
33
56
  rdoc_options: []
34
57
  require_paths:
35
58
  - lib