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 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,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,4 @@
1
+ module MaskedAttribute
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module MaskedAttribute
2
+ VERSION = "0.1.0"
3
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :masked_attribute do
3
+ # # Task goes here
4
+ # 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: []