attribute_guard 1.0.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a485b9e0bc322539e0ac6b7014eb40824c4d94227fc216fab8add8a4f7a201fe
4
- data.tar.gz: 792be937f2a9c6a611773d99c7e9f42c84546e3d1693046a4b7d3ab7b06d7097
3
+ metadata.gz: fa71cf702b0f01ded796adf8ba3f215d024cf7a7947d633450213266df33cd74
4
+ data.tar.gz: 96bb1d220b952c302e9c749378be645c47fa66f49a0860d8b110ce49d2d1f0cb
5
5
  SHA512:
6
- metadata.gz: 36e1b736350dba0238b811205629d6dd4e3b15ef12a60084cb56f78104d90e0cf1b2df797ef555e352e023678d7f838302a583769e876f967a4e9fa5d5306452
7
- data.tar.gz: d961953fc003bfcbada924c84a07b24aaacbca72d1d14c06d1d148ef76d5fb0926a4ecac6999758a63761c85873bdabcf5d76c1eebfda1285cd299ef3ecf6a69
6
+ metadata.gz: 574fcacc35b3a9c4029999e6e9a8c89ae8d35c1f22e6187ebafbea27079e0f812a23bbf93fb42ccd667f570fa3060fba6f3bdc6e2fc1a3411bccb4066345710e
7
+ data.tar.gz: f3b1df97332fdfeed565332fb0be50a53811e95d3661ee0ca1988406ffe690e9cca734a2b6a136d4eacaddf2b995cf9d6c8eaa18d1647aa52ce39e6d96606c49
data/CHANGELOG.md CHANGED
@@ -4,7 +4,22 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## 1.0.0 (unreleased)
7
+ ## 1.1.0
8
+
9
+ ### Added
10
+
11
+ - Added :raise mode that raises an error if a locked attribute has been changed instead of adding a validation error.
12
+
13
+ ### Changed
14
+
15
+ - Changed gem dependency from `activerecord` to `activemodel`. You can now use locked attributes with ActiveModel classes that include `ActiveModel::Validations` and `ActiveModel::Dirty and implement a `new_record?` method.
16
+
17
+ ## 1.0.1
18
+
19
+ ### Added
20
+ - Optimize object shapes for the Ruby interpreter by declaring instance variables in constructors.
21
+
22
+ ## 1.0.0
8
23
 
9
24
  ### Added
10
25
  - Initial release.
data/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # Active Record Attribute Guard
2
2
 
3
3
  [![Continuous Integration](https://github.com/bdurand/attribute_guard/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/attribute_guard/actions/workflows/continuous_integration.yml)
4
+ [![Regression Test](https://github.com/bdurand/attribute_guard/actions/workflows/regression_test.yml/badge.svg)](https://github.com/bdurand/attribute_guard/actions/workflows/regression_test.yml)
4
5
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
6
+ [![Gem Version](https://badge.fury.io/rb/attribute_guard.svg)](https://badge.fury.io/rb/attribute_guard)
5
7
 
6
- This Ruby gem provides an extension for ActiveRecord allowing you to declare certain attributes in a model to be locked. Locked attributes cannot be changed once a record is created unless you explicitly allow changes.
8
+ This Ruby gem provides an extension for ActiveRecord/ActiveModel allowing you to declare certain attributes in a model to be locked. Locked attributes cannot be changed once a record is created unless you explicitly allow changes.
7
9
 
8
10
  This feature can be used for a couple of different purposes.
9
11
 
@@ -117,7 +119,7 @@ record.update!(status: "canceled") # raises ActiveRecord::RecordInvalid error
117
119
 
118
120
  ### Modes
119
121
 
120
- The default behavior when a locked attribute is changed is to add a validation error to the record. You can change this behavior with the `mode` option when locking attributes.
122
+ The default behavior when a locked attribute is changed is to add a validation error to the record. You can change this behavior with the `mode` option when locking attributes. You still need to validate the record to trigger the locked attribute check, regardless of the mode.
121
123
 
122
124
  ```ruby
123
125
  class MyModel
@@ -125,16 +127,23 @@ class MyModel
125
127
 
126
128
  lock_attributes :email, mode: :error
127
129
  lock_attributes :name: mode: :warn
130
+ lock_attributes :updated_at, mode: :raise
128
131
  lock_attributes :created_at, mode: ->(record, attribute) { raise "Created timestamp cannot be changed" }
129
132
  end
130
133
  ```
131
134
 
132
135
  * `:error` - Add a validation error to the record. This is the default.
133
136
 
134
- * `:warn` - Log a warning that the record was changed. This mode is useful to allow you soft deploy locked attributes to production on a mature project and give you information about where you may need to update code to unlock attributes.
137
+ * `:warn` - Log a warning that the record was changed. This mode is useful to allow you soft deploy locked attributes to production on a mature project and give you information about where you may need to update code to unlock attributes. If the model does not have a `logger` method that returns a `Logger`-like object, then the output will be sent to `STDERR`.
138
+
139
+ * `:raise` = Raise an `AttributeGuard::LockedAttributeError` error.
135
140
 
136
141
  * `Proc` - If you provide a `Proc` object, it will be called with the record and the attribute name when a locked attribute is changed.
137
142
 
143
+ ### Using with ActiveModel
144
+
145
+ The gem works out of the box with ActiveRecord. You can also use it with ActiveModel classes as long as they include the `ActiveModel::Validations` and `ActiveModel::Dirty` modules. The model also needs to implement a `new_record?` method.
146
+
138
147
  ## Installation
139
148
 
140
149
  Add this line to your application's Gemfile:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
4
4
  spec.authors = ["Brian Durand"]
5
5
  spec.email = ["bbdurand@gmail.com"]
6
6
 
7
- spec.summary = "ActiveRecord extension that allows locking attributes to prevent unintended updates."
7
+ spec.summary = "ActiveRecord/ActiveModel extension that allows locking attributes to prevent unintended updates."
8
8
 
9
9
  spec.homepage = "https://github.com/bdurand/attribute_guard"
10
10
  spec.license = "MIT"
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
 
32
32
  spec.required_ruby_version = ">= 2.5"
33
33
 
34
- spec.add_dependency "activerecord", ">= 5.0"
34
+ spec.add_dependency "activemodel", ">= 5.2"
35
35
 
36
36
  spec.add_development_dependency "bundler"
37
37
  end
@@ -36,24 +36,42 @@ end
36
36
  module AttributeGuard
37
37
  extend ActiveSupport::Concern
38
38
 
39
+ class LockedAttributeError < StandardError
40
+ end
41
+
39
42
  included do
40
43
  class_attribute :locked_attributes, default: {}, instance_accessor: false
41
44
  private_class_method :locked_attributes=
42
45
  private_class_method :locked_attributes
43
46
 
44
47
  validates_with LockedAttributesValidator
48
+
49
+ prepend Initializer
50
+ end
51
+
52
+ module Initializer
53
+ def initialize(*)
54
+ @unlocked_attributes = nil
55
+ super
56
+ end
45
57
  end
46
58
 
47
59
  # Validator that checks for changes to locked attributes.
48
60
  class LockedAttributesValidator < ActiveModel::Validator
49
61
  def validate(record)
62
+ unless record.respond_to?(:new_record?)
63
+ raise "AttributeGuard can only be used with models that respond to :new_record?"
64
+ end
65
+
50
66
  return if record.new_record?
51
67
 
52
68
  record.class.send(:locked_attributes).each do |attribute, params|
53
69
  if record.changes.include?(attribute) && record.attribute_locked?(attribute)
54
70
  message, mode = params
55
71
  if mode == :warn
56
- record&.logger&.warn("Changed locked attribute #{attribute} on #{record.class.name} with id #{record.id}")
72
+ log_warning(record, attribute)
73
+ elsif mode == :raise
74
+ raise LockedAttributeError.new(error_message(record, attribute))
57
75
  elsif mode.is_a?(Proc)
58
76
  mode.call(record, attribute)
59
77
  else
@@ -62,6 +80,21 @@ module AttributeGuard
62
80
  end
63
81
  end
64
82
  end
83
+
84
+ private
85
+
86
+ def error_message(record, attribute)
87
+ "Changed locked attribute #{attribute} on #{record.class.name} with id #{record.id}"
88
+ end
89
+
90
+ def log_warning(record, attribute)
91
+ message = error_message(record, attribute)
92
+ if record.respond_to?(:logger) && record.logger.respond_to?(:warn)
93
+ record.logger.warn(message)
94
+ else
95
+ warn(message)
96
+ end
97
+ end
65
98
  end
66
99
 
67
100
  module ClassMethods
@@ -104,12 +137,13 @@ module AttributeGuard
104
137
  # user.unlock_attributes(:email).update!(email: "user@example.com")
105
138
  #
106
139
  # @param attributes [Array<Symbol, String>] the attributes to unlock
107
- # @return [ActiveRecord::Base] the object itself
140
+ # @return [Object] the object itself
108
141
  def unlock_attributes(*attributes)
109
142
  attributes = attributes.flatten.map(&:to_s)
110
143
  return if attributes.empty?
111
144
 
112
145
  @unlocked_attributes ||= Set.new
146
+
113
147
  if block_given?
114
148
  save_val = @unlocked_attributes
115
149
  begin
@@ -136,19 +170,15 @@ module AttributeGuard
136
170
  attribute = attribute.to_s
137
171
  return false unless self.class.send(:locked_attributes).include?(attribute)
138
172
 
139
- if defined?(@unlocked_attributes)
140
- !@unlocked_attributes.include?(attribute.to_s)
141
- else
142
- true
143
- end
173
+ return true if @unlocked_attributes.nil?
174
+
175
+ !@unlocked_attributes.include?(attribute.to_s)
144
176
  end
145
177
 
146
178
  # Clears any unlocked attributes.
147
179
  #
148
180
  # @return [void]
149
181
  def clear_unlocked_attributes
150
- if defined?(@unlocked_attributes)
151
- remove_instance_variable(:@unlocked_attributes)
152
- end
182
+ @unlocked_attributes = nil
153
183
  end
154
184
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attribute_guard
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-28 00:00:00.000000000 Z
11
+ date: 2024-04-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activerecord
14
+ name: activemodel
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '5.0'
19
+ version: '5.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '5.0'
26
+ version: '5.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -74,6 +74,6 @@ requirements: []
74
74
  rubygems_version: 3.4.12
75
75
  signing_key:
76
76
  specification_version: 4
77
- summary: ActiveRecord extension that allows locking attributes to prevent unintended
78
- updates.
77
+ summary: ActiveRecord/ActiveModel extension that allows locking attributes to prevent
78
+ unintended updates.
79
79
  test_files: []