active_snapshot 0.1.0

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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +10 -0
  3. data/LICENSE +21 -0
  4. data/README.md +145 -0
  5. data/Rakefile +52 -0
  6. data/lib/active_snapshot.rb +17 -0
  7. data/lib/active_snapshot/models/concerns/snapshots_concern.rb +111 -0
  8. data/lib/active_snapshot/models/snapshot.rb +104 -0
  9. data/lib/active_snapshot/models/snapshot_item.rb +40 -0
  10. data/lib/active_snapshot/version.rb +3 -0
  11. data/lib/generators/active_snapshot/install/install_generator.rb +22 -0
  12. data/lib/generators/active_snapshot/install/templates/create_snapshots_tables.rb.erb +21 -0
  13. data/lib/generators/active_snapshot/migration_generator.rb +76 -0
  14. data/test/dummy_app/Rakefile +7 -0
  15. data/test/dummy_app/app/assets/config/manifest.js +3 -0
  16. data/test/dummy_app/app/assets/javascripts/application.js +0 -0
  17. data/test/dummy_app/app/assets/stylesheets/application.css +3 -0
  18. data/test/dummy_app/app/controllers/application_controller.rb +3 -0
  19. data/test/dummy_app/app/models/application_record.rb +3 -0
  20. data/test/dummy_app/app/models/comment.rb +5 -0
  21. data/test/dummy_app/app/models/post.rb +13 -0
  22. data/test/dummy_app/app/models/volatile_post.rb +5 -0
  23. data/test/dummy_app/app/views/layouts/application.html.erb +14 -0
  24. data/test/dummy_app/config.ru +4 -0
  25. data/test/dummy_app/config/application.rb +61 -0
  26. data/test/dummy_app/config/boot.rb +10 -0
  27. data/test/dummy_app/config/database.yml +20 -0
  28. data/test/dummy_app/config/environment.rb +5 -0
  29. data/test/dummy_app/config/environments/development.rb +30 -0
  30. data/test/dummy_app/config/environments/production.rb +60 -0
  31. data/test/dummy_app/config/environments/test.rb +41 -0
  32. data/test/dummy_app/config/initializers/backtrace_silencers.rb +7 -0
  33. data/test/dummy_app/config/initializers/inflections.rb +10 -0
  34. data/test/dummy_app/config/initializers/mime_types.rb +5 -0
  35. data/test/dummy_app/config/initializers/secret_token.rb +11 -0
  36. data/test/dummy_app/config/initializers/session_store.rb +8 -0
  37. data/test/dummy_app/config/initializers/wrap_parameters.rb +14 -0
  38. data/test/dummy_app/config/locales/en.yml +5 -0
  39. data/test/dummy_app/config/routes.rb +6 -0
  40. data/test/dummy_app/config/secrets.yml +22 -0
  41. data/test/dummy_app/db/migrate/20210128155312_set_up_test_tables.rb +19 -0
  42. data/test/dummy_app/db/migrate/20210306070749_create_snapshots_tables.rb +21 -0
  43. data/test/dummy_app/log/development.log +9 -0
  44. data/test/dummy_app/log/test.log +46812 -0
  45. data/test/generators/active_snapshot/install_generator_test.rb +31 -0
  46. data/test/models/snapshot_item_test.rb +75 -0
  47. data/test/models/snapshot_test.rb +109 -0
  48. data/test/models/snapshots_concern_test.rb +111 -0
  49. data/test/test_helper.rb +71 -0
  50. data/test/unit/active_snapshot_test.rb +60 -0
  51. data/test/unit/errors_test.rb +12 -0
  52. metadata +231 -0
@@ -0,0 +1,31 @@
1
+ require "test_helper"
2
+ require File.expand_path("../../../lib/generators/active_snapshot/install/install_generator", __dir__)
3
+
4
+ class InstallGeneratorTest < Rails::Generators::TestCase
5
+ tests ActiveSnapshot::InstallGenerator
6
+ destination File.expand_path("tmp", __dir__)
7
+
8
+ setup do
9
+ prepare_destination # cleanup the tmp directory
10
+ run_generator
11
+ end
12
+
13
+ teardown do
14
+ ### Disable during debugging
15
+ prepare_destination # cleanup the tmp directory
16
+ end
17
+
18
+ def test_should_add_migration
19
+ run_generator
20
+
21
+ relative_path = "db/migrate/create_snapshots_tables.rb"
22
+
23
+ assert_migration(relative_path) do |content|
24
+ assert_match(/create_table :snapshots/, content)
25
+ assert_match(/create_table :snapshot_items/, content)
26
+ end
27
+
28
+ ### Test for syntax errors in file
29
+ require send(:migration_file_name, relative_path)
30
+ end
31
+ end
@@ -0,0 +1,75 @@
1
+ require "test_helper"
2
+
3
+ class SnapshotItemTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+ @snapshot_klass = ActiveSnapshot::Snapshot
7
+ @snapshot_item_klass = ActiveSnapshot::SnapshotItem
8
+ end
9
+
10
+ def teardown
11
+ end
12
+
13
+ def test_relationships
14
+ instance = @snapshot_item_klass.new
15
+
16
+ assert instance.snapshot.nil?
17
+ assert instance.item.nil?
18
+
19
+ assert_raises do
20
+ instance.snapshot = instance
21
+ end
22
+
23
+ instance.snapshot = ActiveSnapshot::Snapshot.new
24
+
25
+ instance.item = instance
26
+
27
+ assert_not instance.snapshot.nil?
28
+ assert_not instance.item.nil?
29
+ end
30
+
31
+ def test_validations
32
+ instance = @snapshot_item_klass.new
33
+
34
+ instance.valid?
35
+
36
+ [:item_id, :item_type, :snapshot_id].each do |attr|
37
+ assert instance.errors[attr].present? ### presence error
38
+ end
39
+
40
+ shared_post = DATA[:shared_post]
41
+ snapshot = shared_post.snapshots.first
42
+
43
+ instance = @snapshot_item_klass.new(item: snapshot.item, snapshot: snapshot)
44
+
45
+ instance.valid?
46
+
47
+ assert instance.errors[:item_id].present? ### uniq error
48
+ assert instance.errors[:item_type].present? ### uniq error
49
+
50
+ instance = @snapshot_item_klass.new(item_id: 1, item_type: 'Foobar', snapshot: snapshot)
51
+
52
+ assert instance.valid?
53
+ end
54
+
55
+ def test_object
56
+ @snapshot = @snapshot_klass.includes(:snapshot_items).first
57
+
58
+ @snapshot_item = @snapshot.snapshot_items.first
59
+
60
+ assert @snapshot_item.object.is_a?(HashWithIndifferentAccess)
61
+
62
+ @snapshot_item.object = {foo: :bar}
63
+
64
+ assert 'bar', @snapshot_item.object['foo']
65
+ end
66
+
67
+ def test_restore_item!
68
+ @snapshot = @snapshot_klass.includes(:snapshot_items).first
69
+
70
+ @snapshot_item = @snapshot.snapshot_items.first
71
+
72
+ @snapshot_item.restore_item!
73
+ end
74
+
75
+ end
@@ -0,0 +1,109 @@
1
+ require "test_helper"
2
+
3
+ class SnapshotTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+ @snapshot_klass = ActiveSnapshot::Snapshot
7
+ end
8
+
9
+ def teardown
10
+ end
11
+
12
+ def test_relationships
13
+ shared_post = DATA[:shared_post]
14
+
15
+ instance = @snapshot_klass.new
16
+
17
+ assert instance.user.nil?
18
+ assert instance.item.nil?
19
+ assert instance.snapshot_items.empty?
20
+
21
+ instance.user = instance
22
+ instance.item = instance
23
+
24
+ assert_raises do
25
+ instance.snapshot_items << instance
26
+ end
27
+
28
+ instance.snapshot_items << ActiveSnapshot::SnapshotItem.new
29
+
30
+ assert_not instance.user.nil?
31
+ assert_not instance.item.nil?
32
+ assert_not instance.snapshot_items.empty?
33
+
34
+ instance = @snapshot_klass.new(item: shared_post, user: shared_post)
35
+
36
+ assert instance.item.id, shared_post.id
37
+ assert instance.user.id, shared_post.id
38
+ end
39
+
40
+ def test_validations
41
+ shared_post = DATA[:shared_post]
42
+ snapshot = shared_post.snapshots.first
43
+
44
+ instance = @snapshot_klass.new
45
+
46
+ instance.valid?
47
+
48
+ [:item_id, :item_type, :identifier].each do |attr|
49
+ assert instance.errors[attr].present? ### presence error
50
+ end
51
+
52
+ instance = @snapshot_klass.new(item: snapshot.item, identifier: snapshot.identifier)
53
+
54
+ instance.valid?
55
+
56
+ assert instance.errors[:identifier].present? ### uniq error
57
+
58
+ instance = @snapshot_klass.new(item: snapshot.item, identifier: 'random')
59
+
60
+ assert instance.valid?
61
+ end
62
+
63
+ def test_metadata
64
+ @snapshot = @snapshot_klass.first
65
+
66
+ assert @snapshot.metadata.is_a?(HashWithIndifferentAccess)
67
+
68
+ @snapshot.metadata = {foo: :bar}
69
+
70
+ assert_equal 'bar', @snapshot.metadata['foo']
71
+ end
72
+
73
+ def test_build_snapshot_item
74
+ @snapshot = @snapshot_klass.first
75
+
76
+ snapshot_item = @snapshot.build_snapshot_item(Post.first)
77
+
78
+ assert snapshot_item.is_a?(ActiveSnapshot::SnapshotItem)
79
+
80
+ assert snapshot_item.new_record?
81
+
82
+ assert_equal @snapshot.id, snapshot_item.snapshot_id
83
+
84
+ @snapshot.build_snapshot_item(Post.first, child_group_name: :foobar)
85
+ end
86
+
87
+ def test_restore
88
+ @snapshot = @snapshot_klass.first
89
+
90
+ @snapshot.restore!
91
+ end
92
+
93
+ def test_fetch_reified_items
94
+ @snapshot = @snapshot_klass.first
95
+
96
+ reified_items = @snapshot.fetch_reified_items
97
+
98
+ assert reified_items.is_a?(Array)
99
+
100
+ assert reified_items.first.readonly?
101
+
102
+ children_hash = reified_items.last
103
+
104
+ assert children_hash.is_a?(HashWithIndifferentAccess)
105
+
106
+ assert children_hash.all?{|k,v| v.all?{|x| x.readonly?} }
107
+ end
108
+
109
+ end
@@ -0,0 +1,111 @@
1
+ require "test_helper"
2
+
3
+ class SnapshotsConcernTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def teardown
9
+ end
10
+
11
+ def test_relationships
12
+ instance = Post.new
13
+
14
+ assert instance.snapshots.empty?
15
+ assert instance.snapshot_items.empty?
16
+
17
+ assert_raises do
18
+ instance.snapshots << instance
19
+ end
20
+
21
+ instance.snapshots << ActiveSnapshot::Snapshot.new
22
+
23
+ assert_raises do
24
+ instance.snapshot_items << instance
25
+ end
26
+
27
+ instance.snapshot_items << ActiveSnapshot::SnapshotItem.new
28
+
29
+ assert_not instance.snapshots.empty?
30
+ assert_not instance.snapshot_items.empty?
31
+ end
32
+
33
+ def test_create_snapshot!
34
+ @post = Post.first
35
+
36
+ #@user = User.first
37
+
38
+ snapshot = @post.create_snapshot!("foobar 1", user: @user, metadata: {foo: :bar})
39
+ assert_not snapshot.new_record?
40
+
41
+ snapshot = @post.create_snapshot!("foobar 2", user: @user)
42
+ assert_not snapshot.new_record?
43
+
44
+ snapshot = @post.create_snapshot!("foobar 3")
45
+ assert_not snapshot.new_record?
46
+
47
+ assert_raise do
48
+ @post.create_snapshot!("foobar 3")
49
+ end
50
+ end
51
+
52
+ def test_has_snapshot_children
53
+ klass = VolatilePost
54
+
55
+ assert_raise ArgumentError do
56
+ klass.has_snapshot_children
57
+ end
58
+
59
+ klass.has_snapshot_children do
60
+ {}
61
+ end
62
+
63
+ assert klass.instance_variable_get(:@snapshot_children_proc).is_a?(Proc)
64
+
65
+ klass.new.children_to_snapshot
66
+
67
+ invalid = [
68
+ "foobar",
69
+ true,
70
+ false,
71
+ nil,
72
+ "",
73
+ [],
74
+ [:foobar, 123],
75
+ {foo: :bar},
76
+ {foo: {}},
77
+ {foo: {records: 'bar', delete_method: 'bar'}},
78
+ ]
79
+
80
+ invalid.each do |x|
81
+ klass.has_snapshot_children do
82
+ x
83
+ end
84
+
85
+ assert_raise ArgumentError do
86
+ klass.new.children_to_snapshot
87
+ end
88
+ end
89
+
90
+ valid = [
91
+ {},
92
+ {foo: []},
93
+ {foo: [:foobar, 123]},
94
+ {foo: {record: 'bar'}},
95
+ {foo: {records: 'bar'}},
96
+ {foo: {record: Post.limit(1) }},
97
+ {foo: {records: Post.limit(1) }},
98
+ {foo: {records: [], delete_method: ->(){} }},
99
+ {foo: {records: [], delete_method: proc{} }},
100
+ ]
101
+
102
+ valid.each do |x|
103
+ klass.has_snapshot_children do
104
+ x
105
+ end
106
+
107
+ klass.new.children_to_snapshot
108
+ end
109
+ end
110
+
111
+ end
@@ -0,0 +1,71 @@
1
+ #$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require "active_snapshot"
5
+
6
+ begin
7
+ require 'warning'
8
+
9
+ Warning.ignore(
10
+ %r{mail/parsers/address_lists_parser}, ### Hide mail gem warnings
11
+ )
12
+ rescue LoadError
13
+ # Do nothing
14
+ end
15
+
16
+ ### Instantiates Rails
17
+ require File.expand_path("../dummy_app/config/environment.rb", __FILE__)
18
+
19
+ require "rails/test_help"
20
+
21
+ class ActiveSupport::TestCase
22
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
23
+ fixtures :all
24
+ end
25
+
26
+ Rails.backtrace_cleaner.remove_silencers!
27
+
28
+ require 'minitest/reporters'
29
+ Minitest::Reporters.use!(
30
+ Minitest::Reporters::DefaultReporter.new,
31
+ ENV,
32
+ Minitest.backtrace_filter
33
+ )
34
+
35
+ require "minitest/autorun"
36
+
37
+ # Run any available migration
38
+ if ActiveRecord.gem_version >= Gem::Version.new("6.0")
39
+ ActiveRecord::MigrationContext.new(File.expand_path("dummy_app/db/migrate/", __dir__), ActiveRecord::SchemaMigration).migrate
40
+ elsif ActiveRecord.gem_version >= Gem::Version.new("5.2")
41
+ ActiveRecord::MigrationContext.new(File.expand_path("dummy_app/db/migrate/", __dir__)).migrate
42
+ else
43
+ ActiveRecord::Migrator.migrate File.expand_path("dummy_app/db/migrate/", __dir__)
44
+ end
45
+
46
+ klasses = [
47
+ Post,
48
+ ActiveSnapshot::Snapshot,
49
+ ActiveSnapshot::SnapshotItem,
50
+ ]
51
+
52
+ klasses.each do |klass|
53
+ if defined?(SQLite3)
54
+ ActiveRecord::Base.connection.execute("DELETE FROM #{klass.table_name};")
55
+ ActiveRecord::Base.connection.execute("UPDATE `sqlite_sequence` SET `seq` = 0 WHERE `name` = '#{klass.table_name}';")
56
+ else
57
+ ActiveRecord::Base.connection.execute("TRUNCATE TABLE #{klass.table_name}")
58
+ end
59
+ end
60
+
61
+ DATA = {}.with_indifferent_access
62
+
63
+ DATA[:shared_post] = Post.find_or_create_by!(a: 1, b: 3)
64
+ DATA[:shared_post].create_snapshot!('v1')
65
+ DATA[:shared_post].update_columns(a: 2, b: 4)
66
+ DATA[:shared_post].create_snapshot!('v2')
67
+
68
+ def assert_time_match(a, b)
69
+ format = "%d-%m-%Y %h:%M:%S.%L" ### MUST LIMIT THE MILLISECONDS TO 3 decimal places of accuracy, the rest are dropped
70
+ assert_equal a.strftime(format), b.strftime(format)
71
+ end
@@ -0,0 +1,60 @@
1
+ require "test_helper"
2
+
3
+ class ActiveSnapshotTest < ActiveSupport::TestCase
4
+
5
+ def test_exposes_main_module
6
+ assert ActiveSnapshot.is_a?(Module)
7
+ end
8
+
9
+ def test_exposes_version
10
+ assert ActiveSnapshot::VERSION
11
+ end
12
+
13
+ def test_snapshot_lifecycle
14
+ identifier = "snapshot-1"
15
+
16
+ klass = Post
17
+
18
+ parent = klass.first
19
+
20
+ original_parent_updated_at = parent.updated_at
21
+
22
+ child = parent.comments.create!(content: :foo)
23
+ original_child_updated_at = child.updated_at
24
+
25
+ orig_comments_size = parent.children_to_snapshot[:comments][:records].count
26
+
27
+ assert_difference ->{ ActiveSnapshot::Snapshot.count }, 1 do
28
+ assert_difference ->{ ActiveSnapshot::SnapshotItem.count }, (orig_comments_size+1) do
29
+ @snapshot = parent.create_snapshot!(identifier)
30
+ end
31
+ end
32
+
33
+ assert_equal (orig_comments_size+1), @snapshot.snapshot_items.size
34
+
35
+ parent.update_columns(updated_at: 1.day.from_now)
36
+
37
+ parent.update_columns(updated_at: 1.day.from_now)
38
+
39
+ child.destroy!
40
+
41
+ parent.comments.create!(content: :foo)
42
+ parent.comments.create!(content: :bar)
43
+
44
+ assert_no_difference ->{ ActiveSnapshot::Snapshot.count } do
45
+ assert_no_difference ->{ ActiveSnapshot::SnapshotItem.count } do
46
+ @snapshot.restore!
47
+ end
48
+ end
49
+
50
+ assert_equal 1, ActiveSnapshot::Snapshot.where(identifier: identifier).count
51
+
52
+ parent.reload
53
+
54
+ assert_equal orig_comments_size, parent.children_to_snapshot[:comments][:records].count
55
+
56
+ ### Test Data Chang
57
+ assert_time_match original_parent_updated_at, parent.updated_at
58
+ assert_time_match original_child_updated_at, parent.children_to_snapshot[:comments][:records].first.updated_at
59
+ end
60
+ end