snappier 0.1.3 → 0.1.5

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: 61932de9cdce32aa92b7837f38387b9fc763c7729e6d9f57886a2896c2c716cf
4
- data.tar.gz: d5d06a0b18c6279cafa7d703f238ccbac5c1812a4a6b435e82db6b3f6a16bc0b
3
+ metadata.gz: e522706b988ca8ec71eb16695db3dd727aa31b3e529d0c23353828106433078f
4
+ data.tar.gz: 80d6a461f498c1ee78ae550c70707fdecebe9c9eea3dd1fb1b408c7eff0e5a08
5
5
  SHA512:
6
- metadata.gz: 2e071037c3b594612045befde4d73c0e471ba76ce1793ce720958d52fd9d6f4035cefe3cd9f073b142ea61a3cb5344ed1411865810ec9a311b5f2b3c570ec4bb
7
- data.tar.gz: '09993e8a427a9c657f82c0fb8a3e49c326ca47bc100976c14883bce2f15775c771f7bf6624adf1f3f8d9b136050a2609f36d129a10d7b20336dcb757db476089'
6
+ metadata.gz: 266a8c2f2d3ee1fc8f1efa945d711ef9b0591fb80d6365b270e18c8c6f940782576cde9fd22da74d8978ef0dce0eb499676b2c2c81d68818d5383347782fa8d8
7
+ data.tar.gz: 69ddaa90ce8b412ca44b32ffc4c602a1eba8a1862462488262c8a0f4b68fa7ab2e6f95e3080b1e74c0cb8bca3fe5a97d8a090d61861fe8a2f6925f2971d90b47
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.4.4
data/README.md CHANGED
@@ -1,28 +1,68 @@
1
1
  # Snappier
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
4
-
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/snappier`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Imagine a dream where every moment your objects whisper their truths into the ether—each change a shimmer in time, gently bottled and sealed. Snappier is the archivist of that dream: a watchful spirit that captures the essence of Ruby objects, encasing their state in crystalline snapshots. Through enchanted serializers and portals like S3, their stories are preserved across realms. You shape the ritual—what is seen, how it's told, where it rests—yet Snappier hums quietly, faithfully, beneath it all.
6
4
 
7
5
  ## Installation
8
6
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
-
11
- Install the gem and add to the application's Gemfile by executing:
12
-
13
7
  ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
8
+ bundle add snappier
15
9
  ```
16
10
 
17
11
  If bundler is not being used to manage dependencies, install the gem by executing:
18
12
 
19
13
  ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
14
+ gem install snappier
21
15
  ```
22
16
 
23
17
  ## Usage
24
18
 
25
- TODO: Write usage instructions here
19
+ Right, so you’ve decided to track your data like a proper psycho—every twist, every turn, every bloody mutation. You take Snappier, slap it in your Gemfile like it owes you money, and set it up to watch your objects spill their guts. You tell it how to remember—JSON, YAML, dark magic—whatever. Then you point it somewhere to stash the loot: S3, disk, down the pub. When the time comes, you take the snapshot. Click. Another wee lie safely stored. Choose life. Choose history. Choose Snappier.
20
+
21
+ Call `Snappier::Take.for(entity)` in your application code and a snapshopt will be persisted via a sidekiq job (this specific dependency on sidekiq for async processing may be abstracted later).
22
+
23
+ You may call this in an active record model callback (like `after_save`) or anywhere else in your application code.
24
+
25
+ There is no specific dependency on active record but the methods `attributes`, `previously_new_record?` and `destroyed?` are used by default (the latter two deciding if snapshot is related to create/delete otherwise defaulting to update).
26
+
27
+ To attribute changes to a specific user, call `Snappier::Who.current = "<current user description>"` and that
28
+ information will be persisted with any subsequent snapshots. In rails, you may set this in a `before_action`
29
+ method to capture a description of the current user - this setting exists only in the current thread.
30
+
31
+ By default snapshots are persisted to `tmp/snappier`. You can instead persist to S3 using the `snappier-aws_s3` extension gem and the following configuration when your application starts up:
32
+
33
+ ```ruby
34
+ persistence = Snappier::AwsS3::Persistence.new(
35
+ region: aws_region,
36
+ bucket_name: bucket_name,
37
+ credentials: aws_credentials,
38
+ )
39
+ Snappier::Registry.register_persistence(persistence)
40
+ ```
41
+
42
+ By default, snapshot state is persisted by calling `record.attributes`, if you want to persist more or less information then you can create a module and register it when your application starts up:
43
+
44
+ ```ruby
45
+ module OrderSnapshot
46
+ def self.snap(order)
47
+ order.attributes.without(:created_at, :updated_at).tap do |attributes|
48
+ attributes["line_items"] = order.line_items.map { |line_item| line_item.attributes }
49
+ end
50
+ end
51
+ end
52
+
53
+ Snappier::Registry.register(
54
+ "Order" => "OrderSnapshot"
55
+ )
56
+ ```
57
+
58
+ It is possible to replay the snapshots for a specific record which will calculate any change for presentation in a UI:
59
+
60
+ ```ruby
61
+ Snappier::Replay.for(
62
+ type: Order,
63
+ id: "1"
64
+ ) { |change| pp change }
65
+ ```
26
66
 
27
67
  ## Development
28
68
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fileutils"
4
-
5
3
  module Snappier
6
4
  module Changes
7
5
  def self.between(previous_state, current_state)
@@ -47,23 +45,29 @@ module Snappier
47
45
  end
48
46
 
49
47
  def self.append_changes_for_collections(changes, previous_collection, current_collection, path)
50
- ids = Set.new
51
- previous_collection.each { |e| ids << e["id"] if e["id"] }
52
- current_collection.each { |e| ids << e["id"] if e["id"] }
48
+ ids = ids_for_collections(previous_collection, current_collection)
53
49
 
54
50
  if ids.empty?
55
- changes[path] = [previous_collection, current_collection]
51
+ changes[path] = [previous_collection, current_collection] unless previous_collection == current_collection
56
52
  return
57
53
  end
58
54
 
59
55
  ids.each do |id|
60
- previous_value = previous_collection.find { |r| r["id"] == id } || {}
61
- current_value = current_collection.find { |r| r["id"] == id } || {}
62
- previous_value.delete("id")
63
- current_value.delete("id")
56
+ previous_value = (previous_collection || []).find { |r| r["id"] == id } || {}
57
+ current_value = (current_collection || []).find { |r| r["id"] == id } || {}
58
+ previous_value&.delete("id")
59
+ current_value&.delete("id")
64
60
 
65
61
  append_changes(changes, previous_value, current_value, path + [id])
66
62
  end
67
63
  end
64
+
65
+ def self.ids_for_collections(*collections)
66
+ Set.new.tap do |ids|
67
+ collections.each do |collection|
68
+ (collection || []).each { |e| ids << e["id"] if e["id"] }
69
+ end
70
+ end
71
+ end
68
72
  end
69
73
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Snappier
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snappier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Ryall
@@ -32,6 +32,7 @@ extra_rdoc_files: []
32
32
  files:
33
33
  - ".rspec"
34
34
  - ".rubocop.yml"
35
+ - ".tool-versions"
35
36
  - CODE_OF_CONDUCT.md
36
37
  - LICENSE.txt
37
38
  - README.md