attribute_guard 1.0.1 → 1.1.1
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 +23 -0
- data/README.md +16 -4
- data/VERSION +1 -1
- data/attribute_guard.gemspec +9 -3
- data/lib/attribute_guard.rb +37 -6
- metadata +13 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 88ec907e7391560c5a2690875407a3caa1ba387c6b52896d222d17964babcec9
|
|
4
|
+
data.tar.gz: 909244a8bc6e31c80574f27923e88f76b94e7a16720c75878b1ab83bf2b560f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d00b0a3d6cee7b7cf3aa246129214dad28218536284a6d411aebd718711aed7f381921a6d5db196e02a89e9377f902fc096440efef218d93f3e94ed99f0e09bb
|
|
7
|
+
data.tar.gz: 0a378c08a53d9b858581739ccdcec49a06d75ec1df1dd49681ea50a979d848abebe16a6087cbe669048c69dd714ba665e50fd835978b6c6b974943b217cf0756
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ 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.1.1
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Fixed keyword arguments not being forwarded through the prepended constructor, which raised an `ArgumentError` when including `AttributeGuard` in a class whose `initialize` method takes keyword arguments.
|
|
12
|
+
- `unlock_attributes` now returns the record when called with an empty attribute list instead of `nil` so method chaining works as documented.
|
|
13
|
+
- Unlocked attributes are no longer shared between a record and its `dup` or `clone`; unlocking attributes on a copy no longer unlocks them on the original.
|
|
14
|
+
- `lock_attributes` now raises an `ArgumentError` if an invalid mode is specified instead of silently treating it as `:error`.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Minimum required Ruby version is now 2.6.
|
|
19
|
+
|
|
20
|
+
## 1.1.0
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Added :raise mode that raises an error if a locked attribute has been changed instead of adding a validation error.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- 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.
|
|
29
|
+
|
|
7
30
|
## 1.0.1
|
|
8
31
|
|
|
9
32
|
### Added
|
data/README.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
[](https://github.com/bdurand/attribute_guard/actions/workflows/continuous_integration.yml)
|
|
4
4
|
[](https://github.com/bdurand/attribute_guard/actions/workflows/regression_test.yml)
|
|
5
5
|
[](https://github.com/testdouble/standard)
|
|
6
|
+
[](https://badge.fury.io/rb/attribute_guard)
|
|
6
7
|
|
|
7
|
-
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.
|
|
8
9
|
|
|
9
10
|
This feature can be used for a couple of different purposes.
|
|
10
11
|
|
|
@@ -118,24 +119,35 @@ record.update!(status: "canceled") # raises ActiveRecord::RecordInvalid error
|
|
|
118
119
|
|
|
119
120
|
### Modes
|
|
120
121
|
|
|
121
|
-
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.
|
|
122
123
|
|
|
123
124
|
```ruby
|
|
124
125
|
class MyModel
|
|
125
126
|
include AttributeGuard
|
|
126
127
|
|
|
127
128
|
lock_attributes :email, mode: :error
|
|
128
|
-
lock_attributes :name
|
|
129
|
+
lock_attributes :name, mode: :warn
|
|
130
|
+
lock_attributes :updated_at, mode: :raise
|
|
129
131
|
lock_attributes :created_at, mode: ->(record, attribute) { raise "Created timestamp cannot be changed" }
|
|
130
132
|
end
|
|
131
133
|
```
|
|
132
134
|
|
|
133
135
|
* `:error` - Add a validation error to the record. This is the default.
|
|
134
136
|
|
|
135
|
-
* `: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.
|
|
136
140
|
|
|
137
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.
|
|
138
142
|
|
|
143
|
+
### Thread Safety
|
|
144
|
+
|
|
145
|
+
Unlocked attributes are tracked per model instance. Like ActiveRecord models in general, individual record instances are not thread safe, so you should not share a record between threads while unlocking attributes on it.
|
|
146
|
+
|
|
147
|
+
### Using with ActiveModel
|
|
148
|
+
|
|
149
|
+
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.
|
|
150
|
+
|
|
139
151
|
## Installation
|
|
140
152
|
|
|
141
153
|
Add this line to your application's Gemfile:
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.1.1
|
data/attribute_guard.gemspec
CHANGED
|
@@ -4,11 +4,17 @@ 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"
|
|
11
11
|
|
|
12
|
+
spec.metadata = {
|
|
13
|
+
"homepage_uri" => spec.homepage,
|
|
14
|
+
"source_code_uri" => spec.homepage,
|
|
15
|
+
"changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
# Specify which files should be added to the gem when it is released.
|
|
13
19
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
14
20
|
ignore_files = %w[
|
|
@@ -29,9 +35,9 @@ Gem::Specification.new do |spec|
|
|
|
29
35
|
|
|
30
36
|
spec.require_paths = ["lib"]
|
|
31
37
|
|
|
32
|
-
spec.required_ruby_version = ">= 2.
|
|
38
|
+
spec.required_ruby_version = ">= 2.6"
|
|
33
39
|
|
|
34
|
-
spec.add_dependency "
|
|
40
|
+
spec.add_dependency "activemodel", ">= 5.2"
|
|
35
41
|
|
|
36
42
|
spec.add_development_dependency "bundler"
|
|
37
43
|
end
|
data/lib/attribute_guard.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
3
5
|
require "active_support/concern"
|
|
4
6
|
require "active_support/lazy_load_hooks"
|
|
5
7
|
require "active_model/validator"
|
|
@@ -36,6 +38,9 @@ end
|
|
|
36
38
|
module AttributeGuard
|
|
37
39
|
extend ActiveSupport::Concern
|
|
38
40
|
|
|
41
|
+
class LockedAttributeError < StandardError
|
|
42
|
+
end
|
|
43
|
+
|
|
39
44
|
included do
|
|
40
45
|
class_attribute :locked_attributes, default: {}, instance_accessor: false
|
|
41
46
|
private_class_method :locked_attributes=
|
|
@@ -47,7 +52,7 @@ module AttributeGuard
|
|
|
47
52
|
end
|
|
48
53
|
|
|
49
54
|
module Initializer
|
|
50
|
-
def initialize(*)
|
|
55
|
+
def initialize(*args, **kwargs)
|
|
51
56
|
@unlocked_attributes = nil
|
|
52
57
|
super
|
|
53
58
|
end
|
|
@@ -56,13 +61,20 @@ module AttributeGuard
|
|
|
56
61
|
# Validator that checks for changes to locked attributes.
|
|
57
62
|
class LockedAttributesValidator < ActiveModel::Validator
|
|
58
63
|
def validate(record)
|
|
64
|
+
unless record.respond_to?(:new_record?)
|
|
65
|
+
raise "AttributeGuard can only be used with models that respond to :new_record?"
|
|
66
|
+
end
|
|
67
|
+
|
|
59
68
|
return if record.new_record?
|
|
60
69
|
|
|
70
|
+
changes = record.changes
|
|
61
71
|
record.class.send(:locked_attributes).each do |attribute, params|
|
|
62
|
-
if
|
|
72
|
+
if changes.include?(attribute) && record.attribute_locked?(attribute)
|
|
63
73
|
message, mode = params
|
|
64
74
|
if mode == :warn
|
|
65
|
-
record
|
|
75
|
+
log_warning(record, attribute)
|
|
76
|
+
elsif mode == :raise
|
|
77
|
+
raise LockedAttributeError.new(error_message(record, attribute))
|
|
66
78
|
elsif mode.is_a?(Proc)
|
|
67
79
|
mode.call(record, attribute)
|
|
68
80
|
else
|
|
@@ -71,6 +83,21 @@ module AttributeGuard
|
|
|
71
83
|
end
|
|
72
84
|
end
|
|
73
85
|
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def error_message(record, attribute)
|
|
90
|
+
"Changed locked attribute #{attribute} on #{record.class.name} with id #{record.id}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def log_warning(record, attribute)
|
|
94
|
+
message = error_message(record, attribute)
|
|
95
|
+
if record.respond_to?(:logger) && record.logger.respond_to?(:warn)
|
|
96
|
+
record.logger.warn(message)
|
|
97
|
+
else
|
|
98
|
+
warn(message)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
74
101
|
end
|
|
75
102
|
|
|
76
103
|
module ClassMethods
|
|
@@ -86,6 +113,10 @@ module AttributeGuard
|
|
|
86
113
|
# @param mode [Symbol, Proc] mode to use when a locked attribute is changed
|
|
87
114
|
# @return [void]
|
|
88
115
|
def lock_attributes(*attributes, error: :locked, mode: :error)
|
|
116
|
+
unless mode == :error || mode == :warn || mode == :raise || mode.respond_to?(:call)
|
|
117
|
+
raise ArgumentError.new("Invalid mode: #{mode.inspect}")
|
|
118
|
+
end
|
|
119
|
+
|
|
89
120
|
locked = locked_attributes.dup
|
|
90
121
|
error = error.dup.freeze if error.is_a?(String)
|
|
91
122
|
|
|
@@ -113,10 +144,10 @@ module AttributeGuard
|
|
|
113
144
|
# user.unlock_attributes(:email).update!(email: "user@example.com")
|
|
114
145
|
#
|
|
115
146
|
# @param attributes [Array<Symbol, String>] the attributes to unlock
|
|
116
|
-
# @return [
|
|
147
|
+
# @return [Object] the object itself
|
|
117
148
|
def unlock_attributes(*attributes)
|
|
118
149
|
attributes = attributes.flatten.map(&:to_s)
|
|
119
|
-
return if attributes.empty?
|
|
150
|
+
return self if attributes.empty?
|
|
120
151
|
|
|
121
152
|
@unlocked_attributes ||= Set.new
|
|
122
153
|
|
|
@@ -130,7 +161,7 @@ module AttributeGuard
|
|
|
130
161
|
clear_unlocked_attributes if @unlocked_attributes.empty?
|
|
131
162
|
end
|
|
132
163
|
else
|
|
133
|
-
@unlocked_attributes.merge(attributes)
|
|
164
|
+
@unlocked_attributes = @unlocked_attributes.dup.merge(attributes)
|
|
134
165
|
end
|
|
135
166
|
|
|
136
167
|
self
|
metadata
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
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.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Durand
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
13
|
+
name: activemodel
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '5.
|
|
18
|
+
version: '5.2'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '5.
|
|
25
|
+
version: '5.2'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: bundler
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -38,7 +37,6 @@ dependencies:
|
|
|
38
37
|
- - ">="
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
39
|
version: '0'
|
|
41
|
-
description:
|
|
42
40
|
email:
|
|
43
41
|
- bbdurand@gmail.com
|
|
44
42
|
executables: []
|
|
@@ -55,8 +53,10 @@ files:
|
|
|
55
53
|
homepage: https://github.com/bdurand/attribute_guard
|
|
56
54
|
licenses:
|
|
57
55
|
- MIT
|
|
58
|
-
metadata:
|
|
59
|
-
|
|
56
|
+
metadata:
|
|
57
|
+
homepage_uri: https://github.com/bdurand/attribute_guard
|
|
58
|
+
source_code_uri: https://github.com/bdurand/attribute_guard
|
|
59
|
+
changelog_uri: https://github.com/bdurand/attribute_guard/blob/main/CHANGELOG.md
|
|
60
60
|
rdoc_options: []
|
|
61
61
|
require_paths:
|
|
62
62
|
- lib
|
|
@@ -64,16 +64,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
64
64
|
requirements:
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '2.
|
|
67
|
+
version: '2.6'
|
|
68
68
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
69
|
requirements:
|
|
70
70
|
- - ">="
|
|
71
71
|
- !ruby/object:Gem::Version
|
|
72
72
|
version: '0'
|
|
73
73
|
requirements: []
|
|
74
|
-
rubygems_version:
|
|
75
|
-
signing_key:
|
|
74
|
+
rubygems_version: 4.0.3
|
|
76
75
|
specification_version: 4
|
|
77
|
-
summary: ActiveRecord extension that allows locking attributes to prevent
|
|
78
|
-
updates.
|
|
76
|
+
summary: ActiveRecord/ActiveModel extension that allows locking attributes to prevent
|
|
77
|
+
unintended updates.
|
|
79
78
|
test_files: []
|