paper_trail 5.2.3 → 11.0.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 (198) hide show
  1. checksums.yaml +5 -5
  2. data/lib/generators/paper_trail/install/USAGE +3 -0
  3. data/lib/generators/paper_trail/install/install_generator.rb +75 -0
  4. data/lib/generators/paper_trail/{templates/add_object_changes_to_versions.rb → install/templates/add_object_changes_to_versions.rb.erb} +1 -1
  5. data/lib/generators/paper_trail/install/templates/create_versions.rb.erb +36 -0
  6. data/lib/generators/paper_trail/migration_generator.rb +38 -0
  7. data/lib/generators/paper_trail/update_item_subtype/USAGE +4 -0
  8. data/lib/generators/paper_trail/update_item_subtype/templates/update_versions_for_item_subtype.rb.erb +85 -0
  9. data/lib/generators/paper_trail/update_item_subtype/update_item_subtype_generator.rb +17 -0
  10. data/lib/paper_trail.rb +82 -130
  11. data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +27 -0
  12. data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +15 -44
  13. data/lib/paper_trail/attribute_serializers/object_attribute.rb +2 -0
  14. data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +2 -0
  15. data/lib/paper_trail/cleaner.rb +3 -1
  16. data/lib/paper_trail/compatibility.rb +51 -0
  17. data/lib/paper_trail/config.rb +11 -49
  18. data/lib/paper_trail/events/base.rb +323 -0
  19. data/lib/paper_trail/events/create.rb +32 -0
  20. data/lib/paper_trail/events/destroy.rb +42 -0
  21. data/lib/paper_trail/events/update.rb +60 -0
  22. data/lib/paper_trail/frameworks/active_record.rb +2 -1
  23. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version.rb +8 -3
  24. data/lib/paper_trail/frameworks/cucumber.rb +5 -3
  25. data/lib/paper_trail/frameworks/rails.rb +2 -0
  26. data/lib/paper_trail/frameworks/rails/controller.rb +33 -43
  27. data/lib/paper_trail/frameworks/rails/engine.rb +34 -1
  28. data/lib/paper_trail/frameworks/rspec.rb +17 -4
  29. data/lib/paper_trail/frameworks/rspec/helpers.rb +2 -0
  30. data/lib/paper_trail/has_paper_trail.rb +22 -310
  31. data/lib/paper_trail/model_config.rb +157 -109
  32. data/lib/paper_trail/queries/versions/where_object.rb +65 -0
  33. data/lib/paper_trail/queries/versions/where_object_changes.rb +75 -0
  34. data/lib/paper_trail/record_history.rb +3 -9
  35. data/lib/paper_trail/record_trail.rb +169 -319
  36. data/lib/paper_trail/reifier.rb +53 -374
  37. data/lib/paper_trail/request.rb +166 -0
  38. data/lib/paper_trail/serializers/json.rb +9 -10
  39. data/lib/paper_trail/serializers/yaml.rb +15 -28
  40. data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +48 -0
  41. data/lib/paper_trail/version_concern.rb +160 -155
  42. data/lib/paper_trail/version_number.rb +12 -4
  43. metadata +77 -372
  44. data/.github/CONTRIBUTING.md +0 -109
  45. data/.github/ISSUE_TEMPLATE.md +0 -13
  46. data/.gitignore +0 -23
  47. data/.rspec +0 -2
  48. data/.rubocop.yml +0 -99
  49. data/.rubocop_todo.yml +0 -22
  50. data/.travis.yml +0 -41
  51. data/Appraisals +0 -38
  52. data/CHANGELOG.md +0 -560
  53. data/Gemfile +0 -2
  54. data/MIT-LICENSE +0 -20
  55. data/README.md +0 -1654
  56. data/Rakefile +0 -30
  57. data/doc/bug_report_template.rb +0 -69
  58. data/doc/warning_about_not_setting_whodunnit.md +0 -32
  59. data/gemfiles/ar3.gemfile +0 -19
  60. data/gemfiles/ar4.gemfile +0 -8
  61. data/gemfiles/ar5.gemfile +0 -9
  62. data/lib/generators/paper_trail/USAGE +0 -2
  63. data/lib/generators/paper_trail/default_initializer.rb +0 -0
  64. data/lib/generators/paper_trail/install_generator.rb +0 -57
  65. data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +0 -13
  66. data/lib/generators/paper_trail/templates/create_version_associations.rb +0 -22
  67. data/lib/generators/paper_trail/templates/create_versions.rb +0 -80
  68. data/lib/paper_trail/attribute_serializers/legacy_active_record_shim.rb +0 -48
  69. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +0 -11
  70. data/lib/paper_trail/frameworks/sinatra.rb +0 -40
  71. data/lib/paper_trail/version_association_concern.rb +0 -17
  72. data/paper_trail.gemspec +0 -56
  73. data/spec/generators/install_generator_spec.rb +0 -66
  74. data/spec/generators/paper_trail/templates/create_versions_spec.rb +0 -51
  75. data/spec/models/animal_spec.rb +0 -36
  76. data/spec/models/boolit_spec.rb +0 -48
  77. data/spec/models/callback_modifier_spec.rb +0 -96
  78. data/spec/models/car_spec.rb +0 -13
  79. data/spec/models/custom_primary_key_record_spec.rb +0 -18
  80. data/spec/models/fluxor_spec.rb +0 -17
  81. data/spec/models/gadget_spec.rb +0 -68
  82. data/spec/models/joined_version_spec.rb +0 -47
  83. data/spec/models/json_version_spec.rb +0 -102
  84. data/spec/models/kitchen/banana_spec.rb +0 -14
  85. data/spec/models/not_on_update_spec.rb +0 -22
  86. data/spec/models/post_with_status_spec.rb +0 -50
  87. data/spec/models/skipper_spec.rb +0 -46
  88. data/spec/models/thing_spec.rb +0 -11
  89. data/spec/models/truck_spec.rb +0 -5
  90. data/spec/models/vehicle_spec.rb +0 -5
  91. data/spec/models/version_spec.rb +0 -272
  92. data/spec/models/widget_spec.rb +0 -343
  93. data/spec/modules/paper_trail_spec.rb +0 -27
  94. data/spec/modules/version_concern_spec.rb +0 -31
  95. data/spec/modules/version_number_spec.rb +0 -43
  96. data/spec/paper_trail/config_spec.rb +0 -33
  97. data/spec/paper_trail_spec.rb +0 -79
  98. data/spec/rails_helper.rb +0 -34
  99. data/spec/requests/articles_spec.rb +0 -34
  100. data/spec/spec_helper.rb +0 -114
  101. data/spec/support/alt_db_init.rb +0 -54
  102. data/test/custom_json_serializer.rb +0 -13
  103. data/test/dummy/Rakefile +0 -7
  104. data/test/dummy/app/controllers/application_controller.rb +0 -33
  105. data/test/dummy/app/controllers/articles_controller.rb +0 -20
  106. data/test/dummy/app/controllers/test_controller.rb +0 -5
  107. data/test/dummy/app/controllers/widgets_controller.rb +0 -32
  108. data/test/dummy/app/helpers/application_helper.rb +0 -2
  109. data/test/dummy/app/models/animal.rb +0 -6
  110. data/test/dummy/app/models/article.rb +0 -24
  111. data/test/dummy/app/models/authorship.rb +0 -5
  112. data/test/dummy/app/models/bar_habtm.rb +0 -4
  113. data/test/dummy/app/models/book.rb +0 -9
  114. data/test/dummy/app/models/boolit.rb +0 -4
  115. data/test/dummy/app/models/callback_modifier.rb +0 -45
  116. data/test/dummy/app/models/car.rb +0 -3
  117. data/test/dummy/app/models/cat.rb +0 -2
  118. data/test/dummy/app/models/chapter.rb +0 -9
  119. data/test/dummy/app/models/citation.rb +0 -5
  120. data/test/dummy/app/models/custom_primary_key_record.rb +0 -13
  121. data/test/dummy/app/models/customer.rb +0 -4
  122. data/test/dummy/app/models/document.rb +0 -4
  123. data/test/dummy/app/models/dog.rb +0 -2
  124. data/test/dummy/app/models/editor.rb +0 -4
  125. data/test/dummy/app/models/editorship.rb +0 -5
  126. data/test/dummy/app/models/elephant.rb +0 -3
  127. data/test/dummy/app/models/fluxor.rb +0 -3
  128. data/test/dummy/app/models/foo_habtm.rb +0 -5
  129. data/test/dummy/app/models/foo_widget.rb +0 -2
  130. data/test/dummy/app/models/fruit.rb +0 -5
  131. data/test/dummy/app/models/gadget.rb +0 -3
  132. data/test/dummy/app/models/kitchen/banana.rb +0 -5
  133. data/test/dummy/app/models/legacy_widget.rb +0 -4
  134. data/test/dummy/app/models/line_item.rb +0 -4
  135. data/test/dummy/app/models/not_on_update.rb +0 -4
  136. data/test/dummy/app/models/order.rb +0 -5
  137. data/test/dummy/app/models/paragraph.rb +0 -5
  138. data/test/dummy/app/models/person.rb +0 -38
  139. data/test/dummy/app/models/post.rb +0 -3
  140. data/test/dummy/app/models/post_with_status.rb +0 -8
  141. data/test/dummy/app/models/protected_widget.rb +0 -3
  142. data/test/dummy/app/models/quotation.rb +0 -5
  143. data/test/dummy/app/models/section.rb +0 -6
  144. data/test/dummy/app/models/skipper.rb +0 -6
  145. data/test/dummy/app/models/song.rb +0 -41
  146. data/test/dummy/app/models/thing.rb +0 -3
  147. data/test/dummy/app/models/translation.rb +0 -4
  148. data/test/dummy/app/models/truck.rb +0 -4
  149. data/test/dummy/app/models/vehicle.rb +0 -4
  150. data/test/dummy/app/models/whatchamajigger.rb +0 -4
  151. data/test/dummy/app/models/widget.rb +0 -16
  152. data/test/dummy/app/models/wotsit.rb +0 -8
  153. data/test/dummy/app/versions/custom_primary_key_record_version.rb +0 -3
  154. data/test/dummy/app/versions/joined_version.rb +0 -6
  155. data/test/dummy/app/versions/json_version.rb +0 -3
  156. data/test/dummy/app/versions/kitchen/banana_version.rb +0 -5
  157. data/test/dummy/app/versions/post_version.rb +0 -3
  158. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  159. data/test/dummy/config.ru +0 -4
  160. data/test/dummy/config/application.rb +0 -80
  161. data/test/dummy/config/boot.rb +0 -10
  162. data/test/dummy/config/database.mysql.yml +0 -19
  163. data/test/dummy/config/database.postgres.yml +0 -15
  164. data/test/dummy/config/database.sqlite.yml +0 -15
  165. data/test/dummy/config/environment.rb +0 -5
  166. data/test/dummy/config/environments/development.rb +0 -41
  167. data/test/dummy/config/environments/production.rb +0 -74
  168. data/test/dummy/config/environments/test.rb +0 -51
  169. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -9
  170. data/test/dummy/config/initializers/inflections.rb +0 -10
  171. data/test/dummy/config/initializers/mime_types.rb +0 -5
  172. data/test/dummy/config/initializers/paper_trail.rb +0 -9
  173. data/test/dummy/config/initializers/secret_token.rb +0 -9
  174. data/test/dummy/config/initializers/session_store.rb +0 -8
  175. data/test/dummy/config/locales/en.yml +0 -5
  176. data/test/dummy/config/routes.rb +0 -4
  177. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +0 -361
  178. data/test/dummy/db/schema.rb +0 -288
  179. data/test/dummy/script/rails +0 -8
  180. data/test/functional/controller_test.rb +0 -90
  181. data/test/functional/enabled_for_controller_test.rb +0 -28
  182. data/test/functional/modular_sinatra_test.rb +0 -46
  183. data/test/functional/sinatra_test.rb +0 -51
  184. data/test/functional/thread_safety_test.rb +0 -46
  185. data/test/test_helper.rb +0 -127
  186. data/test/time_travel_helper.rb +0 -1
  187. data/test/unit/associations_test.rb +0 -1016
  188. data/test/unit/cleaner_test.rb +0 -188
  189. data/test/unit/inheritance_column_test.rb +0 -43
  190. data/test/unit/model_test.rb +0 -1489
  191. data/test/unit/protected_attrs_test.rb +0 -52
  192. data/test/unit/serializer_test.rb +0 -119
  193. data/test/unit/serializers/json_test.rb +0 -95
  194. data/test/unit/serializers/mixin_json_test.rb +0 -37
  195. data/test/unit/serializers/mixin_yaml_test.rb +0 -53
  196. data/test/unit/serializers/yaml_test.rb +0 -54
  197. data/test/unit/timestamp_test.rb +0 -41
  198. data/test/unit/version_test.rb +0 -119
@@ -1,4 +1,4 @@
1
- require "active_support/core_ext"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module PaperTrail
4
4
  # Configures an ActiveRecord model, mostly at application boot time, but also
@@ -6,77 +6,120 @@ module PaperTrail
6
6
  class ModelConfig
7
7
  E_CANNOT_RECORD_AFTER_DESTROY = <<-STR.strip_heredoc.freeze
8
8
  paper_trail.on_destroy(:after) is incompatible with ActiveRecord's
9
- belongs_to_required_by_default and has no effect. Please use :before
9
+ belongs_to_required_by_default. Use on_destroy(:before)
10
10
  or disable belongs_to_required_by_default.
11
11
  STR
12
+ E_HPT_ABSTRACT_CLASS = <<~STR.squish.freeze
13
+ An application model (%s) has been configured to use PaperTrail (via
14
+ `has_paper_trail`), but the version model it has been told to use (%s) is
15
+ an `abstract_class`. This could happen when an advanced feature called
16
+ Custom Version Classes (http://bit.ly/2G4ch0G) is misconfigured. When all
17
+ version classes are custom, PaperTrail::Version is configured to be an
18
+ `abstract_class`. This is fine, but all application models must be
19
+ configured to use concrete (not abstract) version models.
20
+ STR
21
+ E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE = <<~STR.squish.freeze
22
+ To use PaperTrail's per-model limit in your %s model, you must have an
23
+ item_subtype column in your versions table. See documentation sections
24
+ 2.e.1 Per-model limit, and 4.b.1 The optional item_subtype column.
25
+ STR
26
+ DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION = <<~STR.squish
27
+ Passing versions association name as `has_paper_trail versions: %{versions_name}`
28
+ is deprecated. Use `has_paper_trail versions: {name: %{versions_name}}` instead.
29
+ The hash you pass to `versions:` is now passed directly to `has_many`.
30
+ STR
31
+ DPR_CLASS_NAME_OPTION = <<~STR.squish
32
+ Passing Version class name as `has_paper_trail class_name: %{class_name}`
33
+ is deprecated. Use `has_paper_trail versions: {class_name: %{class_name}}`
34
+ instead. The hash you pass to `versions:` is now passed directly to `has_many`.
35
+ STR
12
36
 
13
37
  def initialize(model_class)
14
38
  @model_class = model_class
15
39
  end
16
40
 
17
- # Switches PaperTrail off for this class.
18
- def disable
19
- ::PaperTrail.enabled_for_model(@model_class, false)
20
- end
21
-
22
- # Switches PaperTrail on for this class.
23
- def enable
24
- ::PaperTrail.enabled_for_model(@model_class, true)
25
- end
26
-
27
- def enabled?
28
- return false unless @model_class.include?(::PaperTrail::Model::InstanceMethods)
29
- ::PaperTrail.enabled_for_model?(@model_class)
30
- end
31
-
32
41
  # Adds a callback that records a version after a "create" event.
42
+ #
43
+ # @api public
33
44
  def on_create
34
- @model_class.after_create :record_create, if: ->(m) { m.paper_trail.save_version? }
45
+ @model_class.after_create { |r|
46
+ r.paper_trail.record_create if r.paper_trail.save_version?
47
+ }
35
48
  return if @model_class.paper_trail_options[:on].include?(:create)
36
49
  @model_class.paper_trail_options[:on] << :create
37
50
  end
38
51
 
39
52
  # Adds a callback that records a version before or after a "destroy" event.
53
+ #
54
+ # @api public
40
55
  def on_destroy(recording_order = "before")
41
- unless %w(after before).include?(recording_order.to_s)
56
+ unless %w[after before].include?(recording_order.to_s)
42
57
  raise ArgumentError, 'recording order can only be "after" or "before"'
43
58
  end
44
59
 
45
60
  if recording_order.to_s == "after" && cannot_record_after_destroy?
46
- ::ActiveSupport::Deprecation.warn(E_CANNOT_RECORD_AFTER_DESTROY)
61
+ raise E_CANNOT_RECORD_AFTER_DESTROY
47
62
  end
48
63
 
49
- @model_class.send "#{recording_order}_destroy", :record_destroy,
50
- if: ->(m) { m.paper_trail.save_version? }
64
+ @model_class.send(
65
+ "#{recording_order}_destroy",
66
+ lambda do |r|
67
+ return unless r.paper_trail.save_version?
68
+ r.paper_trail.record_destroy(recording_order)
69
+ end
70
+ )
51
71
 
52
72
  return if @model_class.paper_trail_options[:on].include?(:destroy)
53
73
  @model_class.paper_trail_options[:on] << :destroy
54
74
  end
55
75
 
56
76
  # Adds a callback that records a version after an "update" event.
77
+ #
78
+ # @api public
57
79
  def on_update
58
- @model_class.before_save :reset_timestamp_attrs_for_update_if_needed!, on: :update
59
- @model_class.after_update :record_update, if: ->(m) { m.paper_trail.save_version? }
60
- @model_class.after_update :clear_version_instance!
80
+ @model_class.before_save { |r|
81
+ r.paper_trail.reset_timestamp_attrs_for_update_if_needed
82
+ }
83
+ @model_class.after_update { |r|
84
+ if r.paper_trail.save_version?
85
+ r.paper_trail.record_update(
86
+ force: false,
87
+ in_after_callback: true,
88
+ is_touch: false
89
+ )
90
+ end
91
+ }
92
+ @model_class.after_update { |r|
93
+ r.paper_trail.clear_version_instance
94
+ }
61
95
  return if @model_class.paper_trail_options[:on].include?(:update)
62
96
  @model_class.paper_trail_options[:on] << :update
63
97
  end
64
98
 
99
+ # Adds a callback that records a version after a "touch" event.
100
+ # @api public
101
+ def on_touch
102
+ @model_class.after_touch { |r|
103
+ r.paper_trail.record_update(
104
+ force: true,
105
+ in_after_callback: true,
106
+ is_touch: true
107
+ )
108
+ }
109
+ end
110
+
65
111
  # Set up `@model_class` for PaperTrail. Installs callbacks, associations,
66
112
  # "class attributes", instance methods, and more.
67
113
  # @api private
68
114
  def setup(options = {})
69
- options[:on] ||= [:create, :update, :destroy]
115
+ options[:on] ||= %i[create update destroy touch]
70
116
  options[:on] = Array(options[:on]) # Support single symbol
71
117
  @model_class.send :include, ::PaperTrail::Model::InstanceMethods
72
- if ::ActiveRecord::VERSION::STRING < "4.2"
73
- @model_class.send :extend, AttributeSerializers::LegacyActiveRecordShim
74
- end
75
118
  setup_options(options)
76
119
  setup_associations(options)
77
- setup_transaction_callbacks
120
+ check_presence_of_item_subtype_column(options)
121
+ @model_class.after_rollback { paper_trail.clear_rolled_back_versions }
78
122
  setup_callbacks_from_options options[:on]
79
- setup_callbacks_for_habtm options[:join_tables]
80
123
  end
81
124
 
82
125
  def version_class
@@ -85,60 +128,100 @@ module PaperTrail
85
128
 
86
129
  private
87
130
 
88
- def active_record_gem_version
89
- Gem::Version.new(ActiveRecord::VERSION::STRING)
131
+ # Raises an error if the provided class is an `abstract_class`.
132
+ # @api private
133
+ def assert_concrete_activerecord_class(class_name)
134
+ if class_name.constantize.abstract_class?
135
+ raise format(E_HPT_ABSTRACT_CLASS, @model_class, class_name)
136
+ end
90
137
  end
91
138
 
92
139
  def cannot_record_after_destroy?
93
- Gem::Version.new(ActiveRecord::VERSION::STRING).release >= Gem::Version.new("5") &&
94
- ::ActiveRecord::Base.belongs_to_required_by_default
140
+ ::ActiveRecord::Base.belongs_to_required_by_default
141
+ end
142
+
143
+ # Some options require the presence of the `item_subtype` column. Currently
144
+ # only `limit`, but in the future there may be others.
145
+ #
146
+ # @api private
147
+ def check_presence_of_item_subtype_column(options)
148
+ return unless options.key?(:limit)
149
+ return if version_class.item_subtype_column_present?
150
+ raise format(E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE, @model_class.name)
95
151
  end
96
152
 
97
- def habtm_assocs_not_skipped
98
- @model_class.reflect_on_all_associations(:has_and_belongs_to_many).
99
- reject { |a| @model_class.paper_trail_options[:skip].include?(a.name.to_s) }
153
+ def check_version_class_name(options)
154
+ # @api private - `version_class_name`
155
+ @model_class.class_attribute :version_class_name
156
+ if options[:class_name]
157
+ ::ActiveSupport::Deprecation.warn(
158
+ format(
159
+ DPR_CLASS_NAME_OPTION,
160
+ class_name: options[:class_name].inspect
161
+ ),
162
+ caller(1)
163
+ )
164
+ options[:versions][:class_name] = options[:class_name]
165
+ end
166
+ @model_class.version_class_name = options[:versions][:class_name] || "PaperTrail::Version"
167
+ assert_concrete_activerecord_class(@model_class.version_class_name)
168
+ end
169
+
170
+ def check_versions_association_name(options)
171
+ # @api private - versions_association_name
172
+ @model_class.class_attribute :versions_association_name
173
+ @model_class.versions_association_name = options[:versions][:name] || :versions
174
+ end
175
+
176
+ def define_has_many_versions(options)
177
+ options = ensure_versions_option_is_hash(options)
178
+ check_version_class_name(options)
179
+ check_versions_association_name(options)
180
+ scope = get_versions_scope(options)
181
+ @model_class.has_many(
182
+ @model_class.versions_association_name,
183
+ scope,
184
+ class_name: @model_class.version_class_name,
185
+ as: :item,
186
+ **options[:versions].except(:name, :scope)
187
+ )
188
+ end
189
+
190
+ def ensure_versions_option_is_hash(options)
191
+ unless options[:versions].is_a?(Hash)
192
+ if options[:versions]
193
+ ::ActiveSupport::Deprecation.warn(
194
+ format(
195
+ DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION,
196
+ versions_name: options[:versions].inspect
197
+ ),
198
+ caller(1)
199
+ )
200
+ end
201
+ options[:versions] = {
202
+ name: options[:versions]
203
+ }
204
+ end
205
+ options
206
+ end
207
+
208
+ def get_versions_scope(options)
209
+ options[:versions][:scope] || -> { order(model.timestamp_sort_order) }
100
210
  end
101
211
 
102
212
  def setup_associations(options)
213
+ # @api private - version_association_name
103
214
  @model_class.class_attribute :version_association_name
104
215
  @model_class.version_association_name = options[:version] || :version
105
216
 
106
217
  # The version this instance was reified from.
218
+ # @api public
107
219
  @model_class.send :attr_accessor, @model_class.version_association_name
108
220
 
109
- @model_class.class_attribute :version_class_name
110
- @model_class.version_class_name = options[:class_name] || "PaperTrail::Version"
111
-
112
- @model_class.class_attribute :versions_association_name
113
- @model_class.versions_association_name = options[:versions] || :versions
114
-
221
+ # @api public - paper_trail_event
115
222
  @model_class.send :attr_accessor, :paper_trail_event
116
223
 
117
- # In rails 4, the `has_many` syntax for specifying order uses a lambda.
118
- if ::ActiveRecord::VERSION::MAJOR >= 4
119
- @model_class.has_many(
120
- @model_class.versions_association_name,
121
- -> { order(model.timestamp_sort_order) },
122
- class_name: @model_class.version_class_name,
123
- as: :item
124
- )
125
- else
126
- @model_class.has_many(
127
- @model_class.versions_association_name,
128
- class_name: @model_class.version_class_name,
129
- as: :item,
130
- order: @model_class.paper_trail.version_class.timestamp_sort_order
131
- )
132
- end
133
- end
134
-
135
- # Adds callbacks to record changes to habtm associations such that on save
136
- # the previous version of the association (if changed) can be interpreted.
137
- def setup_callbacks_for_habtm(join_tables)
138
- @model_class.send :attr_accessor, :paper_trail_habtm
139
- @model_class.class_attribute :paper_trail_save_join_tables
140
- @model_class.paper_trail_save_join_tables = Array.wrap(join_tables)
141
- habtm_assocs_not_skipped.each(&method(:setup_habtm_change_callbacks))
224
+ define_has_many_versions(options)
142
225
  end
143
226
 
144
227
  def setup_callbacks_from_options(options_on = [])
@@ -147,23 +230,15 @@ module PaperTrail
147
230
  end
148
231
  end
149
232
 
150
- def setup_habtm_change_callbacks(assoc)
151
- assoc_name = assoc.name
152
- %w(add remove).each do |verb|
153
- @model_class.send(:"before_#{verb}_for_#{assoc_name}").send(
154
- :<<,
155
- lambda do |*args|
156
- update_habtm_state(assoc_name, :"before_#{verb}", args[-2], args.last)
157
- end
158
- )
159
- end
160
- end
161
-
162
233
  def setup_options(options)
234
+ # @api public - paper_trail_options - Let's encourage plugins to use eg.
235
+ # `paper_trail_options[:versions][:class_name]` rather than
236
+ # `version_class_name` because the former is documented and the latter is
237
+ # not.
163
238
  @model_class.class_attribute :paper_trail_options
164
239
  @model_class.paper_trail_options = options.dup
165
240
 
166
- [:ignore, :skip, :only].each do |k|
241
+ %i[ignore skip only].each do |k|
167
242
  @model_class.paper_trail_options[k] = [@model_class.paper_trail_options[k]].
168
243
  flatten.
169
244
  compact.
@@ -171,33 +246,6 @@ module PaperTrail
171
246
  end
172
247
 
173
248
  @model_class.paper_trail_options[:meta] ||= {}
174
- if @model_class.paper_trail_options[:save_changes].nil?
175
- @model_class.paper_trail_options[:save_changes] = true
176
- end
177
- end
178
-
179
- # Reset the transaction id when the transaction is closed.
180
- def setup_transaction_callbacks
181
- @model_class.after_commit { PaperTrail.clear_transaction_id }
182
- @model_class.after_rollback { PaperTrail.clear_transaction_id }
183
- @model_class.after_rollback { paper_trail.clear_rolled_back_versions }
184
- end
185
-
186
- def update_habtm_state(name, callback, model, assoc)
187
- model.paper_trail_habtm ||= {}
188
- model.paper_trail_habtm[name] ||= { removed: [], added: [] }
189
- state = model.paper_trail_habtm[name]
190
- assoc_id = assoc.id
191
- case callback
192
- when :before_add
193
- state[:added] |= [assoc_id]
194
- state[:removed] -= [assoc_id]
195
- when :before_remove
196
- state[:removed] |= [assoc_id]
197
- state[:added] -= [assoc_id]
198
- else
199
- raise "Invalid callback: #{callback}"
200
- end
201
249
  end
202
250
  end
203
251
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ module Queries
5
+ module Versions
6
+ # For public API documentation, see `where_object` in
7
+ # `paper_trail/version_concern.rb`.
8
+ # @api private
9
+ class WhereObject
10
+ # - version_model_class - The class that VersionConcern was mixed into.
11
+ # - attributes - A `Hash` of attributes and values. See the public API
12
+ # documentation for details.
13
+ # @api private
14
+ def initialize(version_model_class, attributes)
15
+ @version_model_class = version_model_class
16
+ @attributes = attributes
17
+ end
18
+
19
+ # @api private
20
+ def execute
21
+ column = @version_model_class.columns_hash["object"]
22
+ raise "where_object can't be called without an object column" unless column
23
+
24
+ case column.type
25
+ when :jsonb
26
+ jsonb
27
+ when :json
28
+ json
29
+ else
30
+ text
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # @api private
37
+ def json
38
+ predicates = []
39
+ values = []
40
+ @attributes.each do |field, value|
41
+ predicates.push "object->>? = ?"
42
+ values.concat([field, value.to_s])
43
+ end
44
+ sql = predicates.join(" and ")
45
+ @version_model_class.where(sql, *values)
46
+ end
47
+
48
+ # @api private
49
+ def jsonb
50
+ @version_model_class.where("object @> ?", @attributes.to_json)
51
+ end
52
+
53
+ # @api private
54
+ def text
55
+ arel_field = @version_model_class.arel_table[:object]
56
+ where_conditions = @attributes.map { |field, value|
57
+ ::PaperTrail.serializer.where_object_condition(arel_field, field, value)
58
+ }
59
+ where_conditions = where_conditions.reduce { |a, e| a.and(e) }
60
+ @version_model_class.where(where_conditions)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ module Queries
5
+ module Versions
6
+ # For public API documentation, see `where_object_changes` in
7
+ # `paper_trail/version_concern.rb`.
8
+ # @api private
9
+ class WhereObjectChanges
10
+ # - version_model_class - The class that VersionConcern was mixed into.
11
+ # - attributes - A `Hash` of attributes and values. See the public API
12
+ # documentation for details.
13
+ # @api private
14
+ def initialize(version_model_class, attributes)
15
+ @version_model_class = version_model_class
16
+
17
+ # Currently, this `deep_dup` is necessary because the `jsonb` branch
18
+ # modifies `@attributes`, and that would be a nasty suprise for
19
+ # consumers of this class.
20
+ # TODO: Stop modifying `@attributes`, then remove `deep_dup`.
21
+ @attributes = attributes.deep_dup
22
+ end
23
+
24
+ # @api private
25
+ def execute
26
+ if PaperTrail.config.object_changes_adapter&.respond_to?(:where_object_changes)
27
+ return PaperTrail.config.object_changes_adapter.where_object_changes(
28
+ @version_model_class, @attributes
29
+ )
30
+ end
31
+ case @version_model_class.columns_hash["object_changes"].type
32
+ when :jsonb
33
+ jsonb
34
+ when :json
35
+ json
36
+ else
37
+ text
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # @api private
44
+ def json
45
+ predicates = []
46
+ values = []
47
+ @attributes.each do |field, value|
48
+ predicates.push(
49
+ "((object_changes->>? ILIKE ?) OR (object_changes->>? ILIKE ?))"
50
+ )
51
+ values.concat([field, "[#{value.to_json},%", field, "[%,#{value.to_json}]%"])
52
+ end
53
+ sql = predicates.join(" and ")
54
+ @version_model_class.where(sql, *values)
55
+ end
56
+
57
+ # @api private
58
+ def jsonb
59
+ @attributes.each { |field, value| @attributes[field] = [value] }
60
+ @version_model_class.where("object_changes @> ?", @attributes.to_json)
61
+ end
62
+
63
+ # @api private
64
+ def text
65
+ arel_field = @version_model_class.arel_table[:object_changes]
66
+ where_conditions = @attributes.map { |field, value|
67
+ ::PaperTrail.serializer.where_object_changes_condition(arel_field, field, value)
68
+ }
69
+ where_conditions = where_conditions.reduce { |a, e| a.and(e) }
70
+ @version_model_class.where(where_conditions)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end