masked_attribute 0.1.0

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