masked_attribute 0.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +109 -0
- data/Rakefile +3 -0
- data/lib/masked_attribute/railtie.rb +4 -0
- data/lib/masked_attribute/version.rb +3 -0
- data/lib/masked_attribute.rb +106 -0
- data/lib/tasks/masked_attribute_tasks.rake +4 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8b97ca1bc7469412e6d7a070de7849835b70ceb26043ec583a8243c2f4aef82f
|
4
|
+
data.tar.gz: 7baaef798a14dffd01b8bcb779db3fe5fcbaf9f8304f979c1b3ceb800f431efa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e1f91990606ed4e137c5a5329644cefd4101a514e2aeb5263a1f1aa1ab3007e78c101bbf2f800c5f7584e64c6dc62f26406af16f66f794b953a43cbb3ed5f88a
|
7
|
+
data.tar.gz: 2e1da713675c6ab35745fbfe7334cfbe1da1993b47b35e39fef7d889857e0835855c251b35ee090babfb212b2db39879839a4414d3ba74b51495616705de9a48
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2023 candland
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# MaskedAttribute
|
2
|
+
|
3
|
+
Add methods for working with a masked attribute in models.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
### Add field to database
|
8
|
+
|
9
|
+
Create the backing field. It should be appended with `_mask`. To add `roles` to `User`, the field would be `role_mask`.
|
10
|
+
The field must be an `integer` and should be `default: 0, null: false`.
|
11
|
+
|
12
|
+
Example migration:
|
13
|
+
|
14
|
+
```bash
|
15
|
+
bin/rails generate migration add_roles_to_user role_mask:integer
|
16
|
+
```
|
17
|
+
|
18
|
+
Modify the migration to set the default to 0, and disallow NULLs.
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
class AddRolesToUsers < ActiveRecord::Migration[7.0]
|
22
|
+
def change
|
23
|
+
add_column :users, :role_mask, :integer, null: false, default: 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
```bash
|
29
|
+
bin/rails db:migrate
|
30
|
+
```
|
31
|
+
|
32
|
+
### Include in your model
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class User
|
36
|
+
include MaskedAttribute
|
37
|
+
|
38
|
+
masked_attribute :roles, %i[admin sysadmin]
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
The `masked_attribute` takes two arguments, `attribute_name` and `masks`.
|
43
|
+
|
44
|
+
`attribute_name` is the name for the masked attribute. For example
|
45
|
+
a name of `:roles` will create methods for the role_mask backing attribute.
|
46
|
+
|
47
|
+
`masks` is an array of symbols for the mask values. Order matters. You can change the values
|
48
|
+
of the masks, but if you change the order, you'll need to migrate existing data.
|
49
|
+
|
50
|
+
`masked_attribute` will add methods, scopes, and contants to `User` for working with `roles`.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
User::ROLES = masks
|
54
|
+
User::INDEXED_ROLES = {mask_value => mask, ...}
|
55
|
+
|
56
|
+
# Add attr_writer for the ATTRIBUTE_NAME. This should always be called with the full array of roles.
|
57
|
+
def roles= [array_of_masks]
|
58
|
+
|
59
|
+
# Add attr_reader for the ATTRIBUTE_NAME
|
60
|
+
def roles -> [array_of_masks]
|
61
|
+
|
62
|
+
# Add a scope with_ATTRIBUTE_NAME, which returns records that match ALL given masks
|
63
|
+
scope with_roles(*masks)
|
64
|
+
|
65
|
+
# Add a scope with_any_ATTRIBUTE_NAME, which returns records that match ANY given masks
|
66
|
+
scope with_any_roles(*masks)
|
67
|
+
|
68
|
+
# Add scopes for each MASK, to return records with the MASK in the name of the method
|
69
|
+
scope admins()
|
70
|
+
scope sysadmins()
|
71
|
+
|
72
|
+
# Add MASK? methods. Returns true if the mask is set
|
73
|
+
def admin?
|
74
|
+
def sysadmin?
|
75
|
+
|
76
|
+
# Add add_MASK! methods. Updates the backing field with the MASK value in the name.
|
77
|
+
def add_admin!
|
78
|
+
def add_sysadmin!
|
79
|
+
|
80
|
+
# Add remove_MASK! methods. Updates the backing field with the MASK value in the name.
|
81
|
+
def remove_admin!
|
82
|
+
def remove_sysadmin!
|
83
|
+
```
|
84
|
+
|
85
|
+
## Installation
|
86
|
+
|
87
|
+
Add this line to your application's Gemfile:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
gem "masked_attribute"
|
91
|
+
```
|
92
|
+
|
93
|
+
And then execute:
|
94
|
+
```bash
|
95
|
+
$ bundle
|
96
|
+
```
|
97
|
+
|
98
|
+
Or install it yourself as:
|
99
|
+
```bash
|
100
|
+
$ gem install masked_attribute
|
101
|
+
```
|
102
|
+
|
103
|
+
## Contributing
|
104
|
+
|
105
|
+
Contribution directions go here.
|
106
|
+
|
107
|
+
## License
|
108
|
+
|
109
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require "masked_attribute/version"
|
2
|
+
require "masked_attribute/railtie"
|
3
|
+
|
4
|
+
module MaskedAttribute
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
def mask_from values, check_values
|
9
|
+
(check_values & values).map { |r| 2**values.index(r) }.inject(0, :+)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# Add methods for working with a masked attribute in the model
|
15
|
+
#
|
16
|
+
# attribute_name = the name for the masked attribute. For example
|
17
|
+
# a name of :roles will create methods for the role_mask backing attribute
|
18
|
+
#
|
19
|
+
# masks = an array of symbols for the mask values. Order matters.
|
20
|
+
# %i[admin sysadmin]
|
21
|
+
#
|
22
|
+
# With those examples the follow methods & scopes will be created:
|
23
|
+
#
|
24
|
+
# scope with_roles(*masks)
|
25
|
+
# scope with_any_roles(*masks)
|
26
|
+
#
|
27
|
+
# scope admins()
|
28
|
+
# scope sysadmins()
|
29
|
+
#
|
30
|
+
# def admin?
|
31
|
+
# def sysadmin?
|
32
|
+
#
|
33
|
+
# def add_admin!
|
34
|
+
# def remove_admin!
|
35
|
+
#
|
36
|
+
# def add_sysadmin!
|
37
|
+
# def remove_sysadmin!
|
38
|
+
#
|
39
|
+
# def roles=(array of masks)
|
40
|
+
# def roles -> array of masks
|
41
|
+
#
|
42
|
+
# Class::ROLES = masks
|
43
|
+
# Class::INDEXED_ROLES = {mask_value => mask, ...}
|
44
|
+
def masked_attribute attribute_name, masks
|
45
|
+
attribute_name = attribute_name.to_s
|
46
|
+
mask_attribute_name = "#{attribute_name.singularize}_mask"
|
47
|
+
masks_const_name = attribute_name.upcase
|
48
|
+
masks = masks.freeze
|
49
|
+
|
50
|
+
indexed_masks = masks.map { |v| [2**masks.index(v), v] }.to_h.freeze
|
51
|
+
|
52
|
+
const_set masks_const_name, masks
|
53
|
+
const_set "INDEXED_#{masks_const_name}", indexed_masks
|
54
|
+
|
55
|
+
# Add a scope with_ATTRIBUTE_NAME, which returns records that match ALL given masks
|
56
|
+
self.class.define_method("with_#{attribute_name}") do |*values|
|
57
|
+
ok_mask = mask_from(masks, values)
|
58
|
+
where("role_mask & ? = ?", ok_mask, ok_mask)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Add a scope with_any_ATTRIBUTE_NAME, which returns records that match ANY given masks
|
62
|
+
self.class.define_method("with_any_#{attribute_name}") do |*values|
|
63
|
+
ok_mask = mask_from(masks, values)
|
64
|
+
where("role_mask & ? != 0", ok_mask)
|
65
|
+
end
|
66
|
+
|
67
|
+
masks.each do |value|
|
68
|
+
value_int = 2**masks.index(value)
|
69
|
+
|
70
|
+
# Add MASK? method. Returns true if the mask is set
|
71
|
+
define_method("#{value}?") do
|
72
|
+
has = __send__(mask_attribute_name)
|
73
|
+
has & value_int > 0
|
74
|
+
end
|
75
|
+
|
76
|
+
# Add add_MASK! method. Updates the MASKS_mask with the MASK in the name
|
77
|
+
define_method("add_#{value}!") do
|
78
|
+
has = __send__(mask_attribute_name)
|
79
|
+
update!("#{mask_attribute_name}": (has | value_int))
|
80
|
+
end
|
81
|
+
|
82
|
+
# Add remove_MASK! method. Updates the MASKS_mask without the MASK in the name
|
83
|
+
define_method("remove_#{value}!") do
|
84
|
+
has = __send__(mask_attribute_name)
|
85
|
+
update!("#{mask_attribute_name}": (has & ~value_int))
|
86
|
+
end
|
87
|
+
|
88
|
+
# Add scope MASK, to return records with the MASK in the name
|
89
|
+
scope value.to_s.pluralize, -> { with_roles(value) }
|
90
|
+
end
|
91
|
+
|
92
|
+
# Add attr_writer for the attribute_name
|
93
|
+
define_method("#{attribute_name}=") do |value|
|
94
|
+
value = Array.wrap(value).map(&:to_sym)
|
95
|
+
__send__("#{mask_attribute_name}=", self.class.mask_from(masks, value))
|
96
|
+
end
|
97
|
+
|
98
|
+
# Add attr_reader for the attribute_name
|
99
|
+
define_method(attribute_name.to_s) do
|
100
|
+
masks.reject do |r|
|
101
|
+
((__send__(mask_attribute_name).to_i || 0) & 2**masks.index(r)).zero?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: masked_attribute
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- candland
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-05-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 7.0.4.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 7.0.4.2
|
27
|
+
description: Adds methods for a bit masked field
|
28
|
+
email:
|
29
|
+
- candland@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- MIT-LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- lib/masked_attribute.rb
|
38
|
+
- lib/masked_attribute/railtie.rb
|
39
|
+
- lib/masked_attribute/version.rb
|
40
|
+
- lib/tasks/masked_attribute_tasks.rake
|
41
|
+
homepage: https://candland.net/masked_attribute
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata:
|
45
|
+
homepage_uri: https://candland.net/masked_attribute
|
46
|
+
source_code_uri: https://github.com/candland/masked_attribute
|
47
|
+
changelog_uri: https://github.com/candland/masked_attribute/CHANGES.md
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubygems_version: 3.3.26
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: Adds methods for a bit masked field
|
67
|
+
test_files: []
|