attribute_guard 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []