snail_trail 0.0.1 → 0.0.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/lib/generators/snail_trail/install/USAGE +3 -0
  4. data/lib/generators/snail_trail/install/install_generator.rb +108 -0
  5. data/lib/generators/snail_trail/install/templates/add_object_changes_to_versions.rb.erb +12 -0
  6. data/lib/generators/snail_trail/install/templates/add_transaction_id_column_to_versions.rb.erb +12 -0
  7. data/lib/generators/snail_trail/install/templates/create_versions.rb.erb +41 -0
  8. data/lib/generators/snail_trail/migration_generator.rb +38 -0
  9. data/lib/generators/snail_trail/update_item_subtype/USAGE +4 -0
  10. data/lib/generators/snail_trail/update_item_subtype/templates/update_versions_for_item_subtype.rb.erb +85 -0
  11. data/lib/generators/snail_trail/update_item_subtype/update_item_subtype_generator.rb +19 -0
  12. data/lib/snail_trail/attribute_serializers/README.md +10 -0
  13. data/lib/snail_trail/attribute_serializers/attribute_serializer_factory.rb +41 -0
  14. data/lib/snail_trail/attribute_serializers/cast_attribute_serializer.rb +51 -0
  15. data/lib/snail_trail/attribute_serializers/object_attribute.rb +51 -0
  16. data/lib/snail_trail/attribute_serializers/object_changes_attribute.rb +54 -0
  17. data/lib/snail_trail/cleaner.rb +60 -0
  18. data/lib/snail_trail/compatibility.rb +51 -0
  19. data/lib/snail_trail/config.rb +40 -0
  20. data/lib/snail_trail/errors.rb +33 -0
  21. data/lib/snail_trail/events/base.rb +343 -0
  22. data/lib/snail_trail/events/create.rb +32 -0
  23. data/lib/snail_trail/events/destroy.rb +42 -0
  24. data/lib/snail_trail/events/update.rb +76 -0
  25. data/lib/snail_trail/frameworks/active_record/models/snail_trail/version.rb +16 -0
  26. data/lib/snail_trail/frameworks/active_record.rb +12 -0
  27. data/lib/snail_trail/frameworks/cucumber.rb +33 -0
  28. data/lib/snail_trail/frameworks/rails/controller.rb +103 -0
  29. data/lib/snail_trail/frameworks/rails/railtie.rb +34 -0
  30. data/lib/snail_trail/frameworks/rails.rb +3 -0
  31. data/lib/snail_trail/frameworks/rspec/helpers.rb +29 -0
  32. data/lib/snail_trail/frameworks/rspec.rb +42 -0
  33. data/lib/snail_trail/has_snail_trail.rb +92 -0
  34. data/lib/snail_trail/model_config.rb +265 -0
  35. data/lib/snail_trail/queries/versions/where_attribute_changes.rb +50 -0
  36. data/lib/snail_trail/queries/versions/where_object.rb +65 -0
  37. data/lib/snail_trail/queries/versions/where_object_changes.rb +70 -0
  38. data/lib/snail_trail/queries/versions/where_object_changes_from.rb +57 -0
  39. data/lib/snail_trail/queries/versions/where_object_changes_to.rb +57 -0
  40. data/lib/snail_trail/record_history.rb +51 -0
  41. data/lib/snail_trail/record_trail.rb +375 -0
  42. data/lib/snail_trail/reifier.rb +147 -0
  43. data/lib/snail_trail/request.rb +180 -0
  44. data/lib/snail_trail/serializers/json.rb +36 -0
  45. data/lib/snail_trail/serializers/yaml.rb +68 -0
  46. data/lib/snail_trail/type_serializers/postgres_array_serializer.rb +35 -0
  47. data/lib/snail_trail/version_concern.rb +407 -0
  48. data/lib/snail_trail/version_number.rb +23 -0
  49. data/lib/snail_trail.rb +141 -1
  50. metadata +369 -13
  51. data/lib/snail_trail/version.rb +0 -5
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+ require "snail_trail/serializers/yaml"
5
+
6
+ module SnailTrail
7
+ # Global configuration affecting all threads. Some thread-specific
8
+ # configuration can be found in `snail_trail.rb`, others in `controller.rb`.
9
+ class Config
10
+ include Singleton
11
+
12
+ attr_accessor(
13
+ :object_changes_adapter,
14
+ :serializer,
15
+ :version_limit,
16
+ :has_snail_trail_defaults,
17
+ :version_error_behavior
18
+ )
19
+
20
+ def initialize
21
+ # Variables which affect all threads, whose access is synchronized.
22
+ @mutex = Mutex.new
23
+ @enabled = true
24
+
25
+ # Variables which affect all threads, whose access is *not* synchronized.
26
+ @serializer = SnailTrail::Serializers::YAML
27
+ @has_snail_trail_defaults = {}
28
+ @version_error_behavior = :legacy
29
+ end
30
+
31
+ # Indicates whether SnailTrail is on or off. Default: true.
32
+ def enabled
33
+ @mutex.synchronize { !!@enabled }
34
+ end
35
+
36
+ def enabled=(enable)
37
+ @mutex.synchronize { @enabled = enable }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SnailTrail
4
+ # Generic SnailTrail exception.
5
+ # @api public
6
+ class Error < StandardError
7
+ end
8
+
9
+ # An unexpected option, perhaps a typo, was passed to a public API method.
10
+ # @api public
11
+ class InvalidOption < Error
12
+ end
13
+
14
+ # The application's database schema is not supported.
15
+ # @api public
16
+ class UnsupportedSchema < Error
17
+ end
18
+
19
+ # The application's database column type is not supported.
20
+ # @api public
21
+ class UnsupportedColumnType < UnsupportedSchema
22
+ def initialize(method:, expected:, actual:)
23
+ super(
24
+ format(
25
+ "%s expected %s column, got %s",
26
+ method,
27
+ expected,
28
+ actual
29
+ )
30
+ )
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,343 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SnailTrail
4
+ module Events
5
+ # We refer to times in the lifecycle of a record as "events". There are
6
+ # three events:
7
+ #
8
+ # - create
9
+ # - `after_create` we call `RecordTrail#record_create`
10
+ # - update
11
+ # - `after_update` we call `RecordTrail#record_update`
12
+ # - `after_touch` we call `RecordTrail#record_update`
13
+ # - `RecordTrail#save_with_version` calls `RecordTrail#record_update`
14
+ # - `RecordTrail#update_columns` is also referred to as an update, though
15
+ # it uses `RecordTrail#record_update_columns` rather than
16
+ # `RecordTrail#record_update`
17
+ # - destroy
18
+ # - `before_destroy` or `after_destroy` we call `RecordTrail#record_destroy`
19
+ #
20
+ # The value inserted into the `event` column of the versions table can also
21
+ # be overridden by the user, with `snail_trail_event`.
22
+ #
23
+ # @api private
24
+ class Base
25
+ E_FORBIDDEN_METADATA_KEY = <<-EOS.squish
26
+ Forbidden metadata key: %s. As of ST 14, the following metadata keys are
27
+ forbidden: %s
28
+ EOS
29
+ FORBIDDEN_METADATA_KEYS = %i[
30
+ created_at
31
+ id
32
+ item_id
33
+ item_subtype
34
+ item_type
35
+ updated_at
36
+ ].freeze
37
+
38
+ # @api private
39
+ def initialize(record, in_after_callback)
40
+ @record = record
41
+ @in_after_callback = in_after_callback
42
+ end
43
+
44
+ # Determines whether it is appropriate to generate a new version
45
+ # instance. A timestamp-only update (e.g. only `updated_at` changed) is
46
+ # considered notable unless an ignored attribute was also changed.
47
+ #
48
+ # @api private
49
+ def changed_notably?
50
+ if ignored_attr_has_changed?
51
+ timestamps = @record.send(:timestamp_attributes_for_update_in_model).map(&:to_s)
52
+ (notably_changed - timestamps).any?
53
+ else
54
+ notably_changed.any?
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ # @api private
61
+ def assert_metadatum_key_is_permitted(key)
62
+ return unless FORBIDDEN_METADATA_KEYS.include?(key.to_sym)
63
+ raise SnailTrail::InvalidOption,
64
+ format(E_FORBIDDEN_METADATA_KEY, key, FORBIDDEN_METADATA_KEYS)
65
+ end
66
+
67
+ # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
68
+ # https://github.com/BrandsInsurance/snail_trail/pull/899
69
+ #
70
+ # @api private
71
+ def attribute_changed_in_latest_version?(attr_name)
72
+ if @in_after_callback
73
+ @record.saved_change_to_attribute?(attr_name.to_s)
74
+ else
75
+ @record.attribute_changed?(attr_name.to_s)
76
+ end
77
+ end
78
+
79
+ # @api private
80
+ def nonskipped_attributes_before_change(is_touch)
81
+ record_attributes = @record.attributes.except(*@record.snail_trail_options[:skip])
82
+ record_attributes.each_key do |k|
83
+ if @record.class.column_names.include?(k)
84
+ record_attributes[k] = attribute_in_previous_version(k, is_touch)
85
+ end
86
+ end
87
+ end
88
+
89
+ # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
90
+ # https://github.com/BrandsInsurance/snail_trail/pull/899
91
+ #
92
+ # Event can be any of the three (create, update, destroy).
93
+ #
94
+ # @api private
95
+ def attribute_in_previous_version(attr_name, is_touch)
96
+ if @in_after_callback && !is_touch
97
+ # For most events, we want the original value of the attribute, before
98
+ # the last save.
99
+ @record.attribute_before_last_save(attr_name.to_s)
100
+ else
101
+ # We are either performing a `record_destroy` or a
102
+ # `record_update(is_touch: true)`.
103
+ @record.attribute_in_database(attr_name.to_s)
104
+ end
105
+ end
106
+
107
+ # @api private
108
+ def calculated_ignored_array
109
+ ignore = @record.snail_trail_options[:ignore].dup
110
+ # Remove Hash arguments and then evaluate whether the attributes (the
111
+ # keys of the hash) should also get pushed into the collection.
112
+ ignore.delete_if do |obj|
113
+ obj.is_a?(Hash) &&
114
+ obj.each { |attr, condition|
115
+ ignore << attr if condition.respond_to?(:call) && condition.call(@record)
116
+ }
117
+ end
118
+ end
119
+
120
+ # @api private
121
+ def changed_and_not_ignored
122
+ skip = @record.snail_trail_options[:skip]
123
+ (changed_in_latest_version - calculated_ignored_array) - skip
124
+ end
125
+
126
+ # @api private
127
+ def changed_in_latest_version
128
+ # Memoized to reduce memory usage
129
+ @changed_in_latest_version ||= changes_in_latest_version.keys
130
+ end
131
+
132
+ # Memoized to reduce memory usage
133
+ #
134
+ # @api private
135
+ def changes_in_latest_version
136
+ @changes_in_latest_version ||= load_changes_in_latest_version
137
+ end
138
+
139
+ # @api private
140
+ def evaluate_only
141
+ only = @record.snail_trail_options[:only].dup
142
+ # Remove Hash arguments and then evaluate whether the attributes (the
143
+ # keys of the hash) should also get pushed into the collection.
144
+ only.delete_if do |obj|
145
+ obj.is_a?(Hash) &&
146
+ obj.each { |attr, condition|
147
+ only << attr if condition.respond_to?(:call) && condition.call(@record)
148
+ }
149
+ end
150
+ only
151
+ end
152
+
153
+ # An attributed is "ignored" if it is listed in the `:ignore` option
154
+ # and/or the `:skip` option. Returns true if an ignored attribute has
155
+ # changed.
156
+ #
157
+ # @api private
158
+ def ignored_attr_has_changed?
159
+ ignored = calculated_ignored_array + @record.snail_trail_options[:skip]
160
+ ignored.any? && changed_in_latest_version.intersect?(ignored)
161
+ end
162
+
163
+ # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
164
+ # https://github.com/BrandsInsurance/snail_trail/pull/899
165
+ #
166
+ # @api private
167
+ def load_changes_in_latest_version
168
+ if @in_after_callback
169
+ @record.saved_changes
170
+ else
171
+ @record.changes
172
+ end
173
+ end
174
+
175
+ # ST 10 has a new optional column, `item_subtype`
176
+ #
177
+ # @api private
178
+ def merge_item_subtype_into(data)
179
+ if @record.class.snail_trail.version_class.columns_hash.key?("item_subtype")
180
+ data.merge!(item_subtype: @record.class.name)
181
+ end
182
+ end
183
+
184
+ # Updates `data` from the model's `meta` option and from `controller_info`.
185
+ # Metadata is always recorded; that means all three events (create, update,
186
+ # destroy) and `update_columns`.
187
+ #
188
+ # @api private
189
+ def merge_metadata_into(data)
190
+ merge_metadata_from_model_into(data)
191
+ merge_metadata_from_controller_into(data)
192
+ end
193
+
194
+ # Updates `data` from `controller_info`.
195
+ #
196
+ # @api private
197
+ def merge_metadata_from_controller_into(data)
198
+ metadata = SnailTrail.request.controller_info || {}
199
+ metadata.keys.each { |k| assert_metadatum_key_is_permitted(k) }
200
+ data.merge(metadata)
201
+ end
202
+
203
+ # Updates `data` from the model's `meta` option.
204
+ #
205
+ # @api private
206
+ def merge_metadata_from_model_into(data)
207
+ @record.snail_trail_options[:meta].each do |k, v|
208
+ assert_metadatum_key_is_permitted(k)
209
+ data[k] = model_metadatum(v, data[:event])
210
+ end
211
+ end
212
+
213
+ # Given a `value` from the model's `meta` option, returns an object to be
214
+ # persisted. The `value` can be a simple scalar value, but it can also
215
+ # be a symbol that names a model method, or even a Proc.
216
+ #
217
+ # @api private
218
+ def model_metadatum(value, event)
219
+ if value.respond_to?(:call)
220
+ value.call(@record)
221
+ elsif value.is_a?(Symbol) && @record.respond_to?(value, true)
222
+ metadatum_from_model_method(event, value)
223
+ else
224
+ value
225
+ end
226
+ end
227
+
228
+ # The model method can either be an attribute or a non-attribute method.
229
+ #
230
+ # If it is an attribute that is changing in an existing object,
231
+ # be sure to grab the correct version.
232
+ #
233
+ # @api private
234
+ def metadatum_from_model_method(event, method)
235
+ if event != "create" &&
236
+ @record.has_attribute?(method) &&
237
+ attribute_changed_in_latest_version?(method)
238
+ attribute_in_previous_version(method, false)
239
+ else
240
+ @record.send(method)
241
+ end
242
+ end
243
+
244
+ # @api private
245
+ def notable_changes
246
+ changes_in_latest_version.delete_if { |k, _v|
247
+ notably_changed.exclude?(k)
248
+ }
249
+ end
250
+
251
+ # @api private
252
+ def notably_changed
253
+ # Memoized to reduce memory usage
254
+ @notably_changed ||= begin
255
+ only = evaluate_only
256
+ cani = changed_and_not_ignored
257
+ only.empty? ? cani : (cani & only)
258
+ end
259
+ end
260
+
261
+ # Returns hash of attributes (with appropriate attributes serialized),
262
+ # omitting attributes to be skipped.
263
+ #
264
+ # @api private
265
+ def object_attrs_for_snail_trail(is_touch)
266
+ attrs = nonskipped_attributes_before_change(is_touch)
267
+ AttributeSerializers::ObjectAttribute.new(@record.class).serialize(attrs)
268
+ attrs
269
+ end
270
+
271
+ # @api private
272
+ def prepare_object_changes(changes)
273
+ changes = serialize_object_changes(changes)
274
+ recordable_object_changes(changes)
275
+ end
276
+
277
+ # Returns an object which can be assigned to the `object_changes`
278
+ # attribute of a nascent version record. If the `object_changes` column is
279
+ # a postgres `json` column, then a hash can be used in the assignment,
280
+ # otherwise the column is a `text` column, and we must perform the
281
+ # serialization here, using `SnailTrail.serializer`.
282
+ #
283
+ # @api private
284
+ # @param changes HashWithIndifferentAccess
285
+ def recordable_object_changes(changes)
286
+ if SnailTrail.config.object_changes_adapter.respond_to?(:diff)
287
+ # We'd like to avoid the `to_hash` here, because it increases memory
288
+ # usage, but that would be a breaking change because
289
+ # `object_changes_adapter` expects a plain `Hash`, not a
290
+ # `HashWithIndifferentAccess`.
291
+ changes = SnailTrail.config.object_changes_adapter.diff(changes.to_hash)
292
+ end
293
+
294
+ if @record.class.snail_trail.version_class.object_changes_col_is_json?
295
+ changes
296
+ else
297
+ SnailTrail.serializer.dump(changes)
298
+ end
299
+ end
300
+
301
+ # Returns a boolean indicating whether to store serialized version diffs
302
+ # in the `object_changes` column of the version record.
303
+ #
304
+ # @api private
305
+ def record_object_changes?
306
+ @record.class.snail_trail.version_class.column_names.include?("object_changes")
307
+ end
308
+
309
+ # Returns a boolean indicating whether to store the original object during save.
310
+ #
311
+ # @api private
312
+ def record_object?
313
+ @record.class.snail_trail.version_class.column_names.include?("object")
314
+ end
315
+
316
+ # Returns an object which can be assigned to the `object` attribute of a
317
+ # nascent version record. If the `object` column is a postgres `json`
318
+ # column, then a hash can be used in the assignment, otherwise the column
319
+ # is a `text` column, and we must perform the serialization here, using
320
+ # `SnailTrail.serializer`.
321
+ #
322
+ # @api private
323
+ def recordable_object(is_touch)
324
+ if @record.class.snail_trail.version_class.object_col_is_json?
325
+ object_attrs_for_snail_trail(is_touch)
326
+ else
327
+ SnailTrail.serializer.dump(object_attrs_for_snail_trail(is_touch))
328
+ end
329
+ end
330
+
331
+ # @api private
332
+ def serialize_object_changes(changes)
333
+ AttributeSerializers::ObjectChangesAttribute.
334
+ new(@record.class).
335
+ serialize(changes)
336
+
337
+ # We'd like to convert this `HashWithIndifferentAccess` to a plain
338
+ # `Hash`, but we don't, to save memory.
339
+ changes
340
+ end
341
+ end
342
+ end
343
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "snail_trail/events/base"
4
+
5
+ module SnailTrail
6
+ module Events
7
+ # See docs in `Base`.
8
+ #
9
+ # @api private
10
+ class Create < Base
11
+ # Return attributes of nascent `Version` record.
12
+ #
13
+ # @api private
14
+ def data
15
+ data = {
16
+ item: @record,
17
+ event: @record.snail_trail_event || "create",
18
+ whodunnit: SnailTrail.request.whodunnit
19
+ }
20
+ if @record.respond_to?(:updated_at)
21
+ data[:created_at] = @record.updated_at
22
+ end
23
+ if record_object_changes? && changed_notably?
24
+ changes = notable_changes
25
+ data[:object_changes] = prepare_object_changes(changes)
26
+ end
27
+ merge_item_subtype_into(data)
28
+ merge_metadata_into(data)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "snail_trail/events/base"
4
+
5
+ module SnailTrail
6
+ module Events
7
+ # See docs in `Base`.
8
+ #
9
+ # @api private
10
+ class Destroy < Base
11
+ # Return attributes of nascent `Version` record.
12
+ #
13
+ # @api private
14
+ def data
15
+ data = {
16
+ item_id: @record.id,
17
+ item_type: @record.class.base_class.name,
18
+ event: @record.snail_trail_event || "destroy",
19
+ whodunnit: SnailTrail.request.whodunnit
20
+ }
21
+ if record_object?
22
+ data[:object] = recordable_object(false)
23
+ end
24
+ if record_object_changes?
25
+ data[:object_changes] = prepare_object_changes(notable_changes)
26
+ end
27
+ merge_item_subtype_into(data)
28
+ merge_metadata_into(data)
29
+ end
30
+
31
+ private
32
+
33
+ # Rails' implementation (eg. `@record.saved_changes`) returns nothing on
34
+ # destroy, so we have to build the hash we want.
35
+ #
36
+ # @override
37
+ def changes_in_latest_version
38
+ @record.attributes.transform_values { |value| [value, nil] }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "snail_trail/events/base"
4
+
5
+ module SnailTrail
6
+ module Events
7
+ # See docs in `Base`.
8
+ #
9
+ # @api private
10
+ class Update < Base
11
+ # - is_touch - [boolean] - Used in the two situations that are touch-like:
12
+ # - `after_touch` we call `RecordTrail#record_update`
13
+ # - force_changes - [Hash] - Only used by `RecordTrail#update_columns`,
14
+ # because there dirty-tracking is off, so it has to track its own changes.
15
+ #
16
+ # @api private
17
+ def initialize(record, in_after_callback, is_touch, force_changes)
18
+ super(record, in_after_callback)
19
+ @is_touch = is_touch
20
+ @force_changes = force_changes
21
+ end
22
+
23
+ # Return attributes of nascent `Version` record.
24
+ #
25
+ # @api private
26
+ def data
27
+ data = {
28
+ item: @record,
29
+ event: @record.snail_trail_event || "update",
30
+ whodunnit: SnailTrail.request.whodunnit
31
+ }
32
+ if record_object?
33
+ data[:object] = recordable_object(@is_touch)
34
+ end
35
+ merge_object_changes_into(data)
36
+ merge_item_subtype_into(data)
37
+ merge_metadata_into(data)
38
+ end
39
+
40
+ # If it is a touch event, and changed are empty, it is assumed to be
41
+ # implicit `touch` mutation, and will a version is created.
42
+ #
43
+ # See https://github.com/rails/rails/commit/dcb825902d79d0f6baba956f7c6ec5767611353e
44
+ #
45
+ # @api private
46
+ def changed_notably?
47
+ if @is_touch && changes_in_latest_version.empty?
48
+ true
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # @api private
57
+ def merge_object_changes_into(data)
58
+ if record_object_changes?
59
+ changes = @force_changes.nil? ? notable_changes : @force_changes
60
+ data[:object_changes] = prepare_object_changes(changes)
61
+ end
62
+ end
63
+
64
+ # `touch` cannot record `object_changes` because rails' `touch` does not
65
+ # perform dirty-tracking. Specifically, methods from `Dirty`, like
66
+ # `saved_changes`, return the same values before and after `touch`.
67
+ #
68
+ # See https://github.com/rails/rails/issues/33429
69
+ #
70
+ # @api private
71
+ def record_object_changes?
72
+ !@is_touch && super
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "snail_trail/version_concern"
4
+
5
+ module SnailTrail
6
+ # This is the default ActiveRecord model provided by SnailTrail. Most simple
7
+ # applications will use this model as-is, but it is possible to sub-class,
8
+ # extend, or even do without this model entirely. See documentation section
9
+ # 6.a. Custom Version Classes.
10
+ #
11
+ # The snail_trail-association_tracking gem provides a related model,
12
+ # `VersionAssociation`.
13
+ class Version < ::ActiveRecord::Base
14
+ include SnailTrail::VersionConcern
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Either ActiveRecord has already been loaded by the Lazy Load Hook in our
4
+ # Railtie, or else we load it now.
5
+ require "active_record"
6
+ SnailTrail::Compatibility.check_activerecord(ActiveRecord.gem_version)
7
+
8
+ # Now we can load the parts of ST that depend on AR.
9
+ require "snail_trail/has_snail_trail"
10
+ require "snail_trail/reifier"
11
+ require "snail_trail/frameworks/active_record/models/snail_trail/version"
12
+ ActiveRecord::Base.include SnailTrail::Model
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # before hook for Cucumber
4
+ Before do
5
+ SnailTrail.enabled = false
6
+ SnailTrail.request.enabled = true
7
+ SnailTrail.request.whodunnit = nil
8
+ SnailTrail.request.controller_info = {} if defined?(Rails)
9
+ end
10
+
11
+ module SnailTrail
12
+ module Cucumber
13
+ # Helper method for enabling ST in Cucumber features.
14
+ module Extensions
15
+ # :call-seq:
16
+ # with_versioning
17
+ #
18
+ # enable versioning for specific blocks
19
+
20
+ def with_versioning
21
+ was_enabled = ::SnailTrail.enabled?
22
+ ::SnailTrail.enabled = true
23
+ begin
24
+ yield
25
+ ensure
26
+ ::SnailTrail.enabled = was_enabled
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ World SnailTrail::Cucumber::Extensions