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 +4 -4
- data/CHANGELOG.md +16 -1
- data/README.md +12 -3
- data/VERSION +1 -1
- data/attribute_guard.gemspec +2 -2
- data/lib/attribute_guard.rb +40 -10
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa71cf702b0f01ded796adf8ba3f215d024cf7a7947d633450213266df33cd74
|
4
|
+
data.tar.gz: 96bb1d220b952c302e9c749378be645c47fa66f49a0860d8b110ce49d2d1f0cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
[](https://github.com/bdurand/attribute_guard/actions/workflows/continuous_integration.yml)
|
4
|
+
[](https://github.com/bdurand/attribute_guard/actions/workflows/regression_test.yml)
|
4
5
|
[](https://github.com/testdouble/standard)
|
6
|
+
[](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.
|
1
|
+
1.1.0
|
data/attribute_guard.gemspec
CHANGED
@@ -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 "
|
34
|
+
spec.add_dependency "activemodel", ">= 5.2"
|
35
35
|
|
36
36
|
spec.add_development_dependency "bundler"
|
37
37
|
end
|
data/lib/attribute_guard.rb
CHANGED
@@ -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
|
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 [
|
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
|
140
|
-
|
141
|
-
|
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
|
-
|
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.
|
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:
|
11
|
+
date: 2024-04-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activemodel
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '5.
|
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.
|
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
|
78
|
-
updates.
|
77
|
+
summary: ActiveRecord/ActiveModel extension that allows locking attributes to prevent
|
78
|
+
unintended updates.
|
79
79
|
test_files: []
|