bitwise_attribute 0.2.2 → 0.3.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 +4 -4
- data/.rspec +1 -1
- data/.rubocop.yml +5 -6
- data/.travis.yml +3 -0
- data/Gemfile.lock +11 -2
- data/README.md +88 -7
- data/bin/console +2 -2
- data/bitwise_attribute.gemspec +2 -2
- data/lib/bitwise_attribute/active_record_methods.rb +71 -0
- data/lib/bitwise_attribute/version.rb +1 -1
- data/lib/bitwise_attribute.rb +16 -89
- metadata +23 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '09a20c421bbc1d6c4c7803c43be1b937f53725c9f9cea4be27c05c0c00b4d23a'
|
4
|
+
data.tar.gz: 15af80b8780cfa9e081c45186049ee7bbd9a96266f140fcf2294e14adadf8dcc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8bbf57d1f5e5a69a2ab6952ca9d8cf5376e28846e8ab2b7d0c858d9838bf7d6563e6ceb283b8641b0095e2dc05150a17aa60bdafaad8c872c775bf967d4b9c61
|
7
|
+
data.tar.gz: 90a8b4ca18833063b57d64279ef2e2c8bef0c2a7975788c128dbf31fca0a4b3535a6820955a6e0e42412e4afb3f98a7db8e18549d8f5cfc93f16c5b66406fb24
|
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
@@ -2,20 +2,19 @@ Metrics/AbcSize:
|
|
2
2
|
Max: 30
|
3
3
|
|
4
4
|
Style/Documentation:
|
5
|
-
|
6
|
-
- 'spec/**/*'
|
7
|
-
- 'test/**/*'
|
8
|
-
- 'lib/bitwise_attribute.rb'
|
9
|
-
- 'lib/bitwise_attribute/values_list.rb'
|
5
|
+
Enabled: false
|
10
6
|
|
11
7
|
Metrics/LineLength:
|
12
8
|
Max: 100
|
13
9
|
|
14
10
|
Metrics/MethodLength:
|
15
|
-
Max:
|
11
|
+
Max: 50
|
16
12
|
|
17
13
|
Metrics/BlockLength:
|
18
14
|
Max: 150
|
19
15
|
|
20
16
|
Metrics/ModuleLength:
|
21
17
|
Max: 200
|
18
|
+
|
19
|
+
Metrics/AbcSize:
|
20
|
+
Max: 70
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,17 +1,23 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
bitwise_attribute (0.
|
5
|
-
activesupport (~> 5.1.5)
|
4
|
+
bitwise_attribute (0.3.0)
|
6
5
|
|
7
6
|
GEM
|
8
7
|
remote: https://rubygems.org/
|
9
8
|
specs:
|
9
|
+
activemodel (5.1.5)
|
10
|
+
activesupport (= 5.1.5)
|
11
|
+
activerecord (5.1.5)
|
12
|
+
activemodel (= 5.1.5)
|
13
|
+
activesupport (= 5.1.5)
|
14
|
+
arel (~> 8.0)
|
10
15
|
activesupport (5.1.5)
|
11
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
12
17
|
i18n (~> 0.7)
|
13
18
|
minitest (~> 5.1)
|
14
19
|
tzinfo (~> 1.1)
|
20
|
+
arel (8.0.0)
|
15
21
|
ast (2.4.0)
|
16
22
|
byebug (10.0.1)
|
17
23
|
coderay (1.1.2)
|
@@ -61,6 +67,7 @@ GEM
|
|
61
67
|
json (>= 1.8, < 3)
|
62
68
|
simplecov-html (~> 0.10.0)
|
63
69
|
simplecov-html (0.10.2)
|
70
|
+
sqlite3 (1.3.13)
|
64
71
|
thread_safe (0.3.6)
|
65
72
|
tzinfo (1.2.5)
|
66
73
|
thread_safe (~> 0.1)
|
@@ -70,6 +77,7 @@ PLATFORMS
|
|
70
77
|
ruby
|
71
78
|
|
72
79
|
DEPENDENCIES
|
80
|
+
activerecord (>= 3)
|
73
81
|
bitwise_attribute!
|
74
82
|
bundler (~> 1.16)
|
75
83
|
pry-byebug
|
@@ -77,6 +85,7 @@ DEPENDENCIES
|
|
77
85
|
rspec (~> 3.0)
|
78
86
|
rubocop (~> 0.54.0)
|
79
87
|
simplecov (~> 0.15.1)
|
88
|
+
sqlite3
|
80
89
|
|
81
90
|
BUNDLED WITH
|
82
91
|
1.16.1
|
data/README.md
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
[](https://travis-ci.org/rikas/bitwise_attribute)
|
3
3
|
|
4
4
|
# BitwiseAttribute
|
5
|
+
Manipulation of bitmask attributes in your classes (typically ActiveRecord models). You can have multiple values mapped to the same column — for example when you need a User with multiple roles.
|
6
|
+
|
7
|
+
It adds a lot of helper methods so you don't have to deal with the underlying mask.
|
5
8
|
|
6
9
|
## Installation
|
7
10
|
|
@@ -24,6 +27,10 @@ Or install it yourself as:
|
|
24
27
|
Include `BitwiseAttribute` and then define your attribute with `attr_bitwise`. By default it will
|
25
28
|
use the singularized name with `_mask`.
|
26
29
|
|
30
|
+
Check the example below to see how to use the helpers and methods created automatically in your classes.
|
31
|
+
|
32
|
+
## Examples
|
33
|
+
|
27
34
|
For the `roles` attribute you need to have `role_mask` column in your model, so add the migration:
|
28
35
|
|
29
36
|
```ruby
|
@@ -37,19 +44,93 @@ end
|
|
37
44
|
```ruby
|
38
45
|
class User < ActiveRecord::Base
|
39
46
|
include BitwiseAttribute
|
40
|
-
|
47
|
+
|
48
|
+
# This line will do all the magic!
|
49
|
+
#
|
50
|
+
# By default we assume that your column will be called `role_mask`.
|
51
|
+
# You can send the `column_name` option if your column has another name.
|
52
|
+
#
|
41
53
|
attr_bitwise :roles, values: %i[user moderator admin]
|
42
54
|
end
|
43
55
|
```
|
44
56
|
|
45
|
-
|
57
|
+
### Instance manipulation
|
58
|
+
|
59
|
+
You can then access the `roles` field without having to know the underlying value of `role_mask`.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
user = User.new(roles: [:user, :admin])
|
63
|
+
|
64
|
+
user.roles
|
65
|
+
#=> [:user, :admin]
|
66
|
+
|
67
|
+
user.role_mask
|
68
|
+
#=> 5
|
69
|
+
|
70
|
+
user.roles << :moderator
|
71
|
+
user.roles
|
72
|
+
#=> [:user, :admin, :moderator]
|
73
|
+
```
|
74
|
+
|
75
|
+
You can see if a particular record has a given value:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
user.admin?
|
79
|
+
#=> true
|
80
|
+
```
|
81
|
+
|
82
|
+
### Class methods
|
83
|
+
|
84
|
+
You can get all available values and correspondent mask value:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
User.roles
|
88
|
+
#=> { :user => 1, :moderator => 2, :admin => 4 }
|
89
|
+
```
|
90
|
+
|
91
|
+
So if you need all the keys just use:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
User.roles.keys
|
95
|
+
#=> [:user, :moderator, :admin]
|
96
|
+
```
|
97
|
+
|
98
|
+
### ActiveRecord named scopes
|
99
|
+
|
100
|
+
BitwiseAttribte will add some methods for easier queries on the database:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
User.with_roles
|
104
|
+
#=> Users that have at least one role
|
105
|
+
|
106
|
+
User.with_roles(:admin)
|
107
|
+
#=> Users that have the :admin role
|
108
|
+
|
109
|
+
User.with_roles(:admin, :moderator)
|
110
|
+
#=> Users that have the admin role AND the moderator role
|
111
|
+
|
112
|
+
User.with_any_roles(:admin, :moderator)
|
113
|
+
#=> Users that have the admin role OR the moderator role
|
114
|
+
|
115
|
+
User.with_exact_roles(:moderator)
|
116
|
+
#=> Users that have ONLY the moderator role
|
117
|
+
|
118
|
+
User.with_exact_roles(:moderator, :admin)
|
119
|
+
#=> Users that have ONLY the moderator AND admin roles
|
120
|
+
|
121
|
+
User.without_roles(:admin)
|
122
|
+
#=> Users without the admin role
|
123
|
+
|
124
|
+
User.without_roles(:admin, :moderator)
|
125
|
+
#=> Users without the admin role AND without the moderator role
|
126
|
+
```
|
127
|
+
|
128
|
+
These are the same as using `with_roles`:
|
46
129
|
|
47
130
|
```ruby
|
48
|
-
|
49
|
-
user
|
50
|
-
|
51
|
-
user.save
|
52
|
-
user.role_mask #=> 5
|
131
|
+
User.admin
|
132
|
+
User.user
|
133
|
+
User.admin.moderator
|
53
134
|
```
|
54
135
|
|
55
136
|
## Development
|
data/bin/console
CHANGED
data/bitwise_attribute.gemspec
CHANGED
@@ -24,12 +24,12 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
25
|
spec.require_paths = %w[lib]
|
26
26
|
|
27
|
-
spec.
|
28
|
-
|
27
|
+
spec.add_development_dependency 'activerecord', '>= 3'
|
29
28
|
spec.add_development_dependency 'bundler', '~> 1.16'
|
30
29
|
spec.add_development_dependency 'pry-byebug'
|
31
30
|
spec.add_development_dependency 'rake', '~> 10.0'
|
32
31
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
33
32
|
spec.add_development_dependency 'rubocop', '~> 0.54.0'
|
34
33
|
spec.add_development_dependency 'simplecov', '~> 0.15.1'
|
34
|
+
spec.add_development_dependency 'sqlite3'
|
35
35
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BitwiseAttribute
|
4
|
+
module ActiveRecordMethods
|
5
|
+
def define_named_scopes(name, column_name, mapping)
|
6
|
+
define_singleton_method("with_#{name}") do |*keys|
|
7
|
+
keys = keys.flatten.uniq
|
8
|
+
|
9
|
+
return [] unless keys&.any?
|
10
|
+
|
11
|
+
records = where("#{column_name} & #{mapping[keys.first]} = #{mapping[keys.first]}")
|
12
|
+
|
13
|
+
keys[1..-1].each do |key|
|
14
|
+
records = records.where("#{column_name} & #{mapping[key]} = #{mapping[key]}")
|
15
|
+
end
|
16
|
+
|
17
|
+
records
|
18
|
+
end
|
19
|
+
|
20
|
+
define_singleton_method("with_any_#{name}") do |*keys|
|
21
|
+
keys = keys.flatten.uniq
|
22
|
+
|
23
|
+
return where.not(column_name => nil) unless keys&.any?
|
24
|
+
|
25
|
+
records = where("#{column_name} & #{mapping[keys.first]} = #{mapping[keys.first]}")
|
26
|
+
|
27
|
+
keys[1..-1].each do |key|
|
28
|
+
records = records.or(where("#{column_name} & #{mapping[key]} = #{mapping[key]}"))
|
29
|
+
end
|
30
|
+
|
31
|
+
records
|
32
|
+
end
|
33
|
+
|
34
|
+
define_singleton_method("with_exact_#{name}") do |*keys|
|
35
|
+
keys = keys.flatten.uniq
|
36
|
+
|
37
|
+
return [] unless keys&.any?
|
38
|
+
|
39
|
+
records = send("with_#{name}", keys)
|
40
|
+
|
41
|
+
(mapping.keys - keys).each do |key|
|
42
|
+
records = records.where("#{column_name} & #{mapping[key]} != #{mapping[key]}")
|
43
|
+
end
|
44
|
+
|
45
|
+
records
|
46
|
+
end
|
47
|
+
|
48
|
+
define_singleton_method("without_#{name}") do |*keys|
|
49
|
+
keys = keys.flatten.uniq
|
50
|
+
|
51
|
+
return where(column_name => nil).or(where(column_name => 0)) unless keys&.any?
|
52
|
+
|
53
|
+
records = where("#{column_name} & #{mapping[keys.first]} != #{mapping[keys.first]}")
|
54
|
+
|
55
|
+
keys[1..-1].each do |key|
|
56
|
+
records = records.where("#{column_name} & #{mapping[key]} != #{mapping[key]}")
|
57
|
+
end
|
58
|
+
|
59
|
+
records
|
60
|
+
end
|
61
|
+
|
62
|
+
# Defines a class method for each key of the mapping, returning records that have *at least*
|
63
|
+
# the corresponding value.
|
64
|
+
mapping.keys.each do |key|
|
65
|
+
define_singleton_method(key) do
|
66
|
+
send("with_#{name}", key)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/bitwise_attribute.rb
CHANGED
@@ -3,52 +3,39 @@
|
|
3
3
|
require 'active_support'
|
4
4
|
require 'active_support/concern'
|
5
5
|
require 'active_support/core_ext'
|
6
|
+
|
6
7
|
require 'bitwise_attribute/version'
|
7
8
|
require 'bitwise_attribute/values_list'
|
9
|
+
require 'bitwise_attribute/active_record_methods'
|
8
10
|
|
9
11
|
module BitwiseAttribute
|
10
|
-
|
12
|
+
def self.included(base)
|
13
|
+
base.extend ClassMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
include BitwiseAttribute::ActiveRecordMethods
|
11
18
|
|
12
|
-
class_methods do
|
13
19
|
def attr_bitwise(name, column_name: nil, values:)
|
14
20
|
column_name ||= "#{name.to_s.singularize}_mask"
|
15
21
|
|
16
|
-
# Check if the values is an array or hash with valid values. Raise ArgumentError otherwise.
|
17
|
-
validate_values!(values)
|
18
|
-
|
19
22
|
mapping = build_mapping(values)
|
20
23
|
|
21
|
-
@mapping ||= {}
|
22
|
-
@mapping[name] = mapping
|
23
|
-
|
24
24
|
define_class_methods(name, column_name, mapping)
|
25
|
-
|
25
|
+
define_instance_methods(name, column_name, mapping)
|
26
26
|
end
|
27
27
|
|
28
|
+
private
|
29
|
+
|
28
30
|
def define_class_methods(name, column_name, mapping)
|
29
|
-
# Class methods
|
30
31
|
define_singleton_method(name) do
|
31
32
|
mapping
|
32
33
|
end
|
33
34
|
|
34
|
-
|
35
|
-
where(column_name => bitwise_union(keys, name))
|
36
|
-
end
|
37
|
-
|
38
|
-
define_singleton_method("with_all_#{name}") do |*keys|
|
39
|
-
where(column_name => bitwise_intersection(keys, name))
|
40
|
-
end
|
41
|
-
|
42
|
-
# Defines a class method for each key of the mapping, returning records that have *at least*
|
43
|
-
# the corresponding value.
|
44
|
-
mapping.keys.each do |key|
|
45
|
-
define_singleton_method(key) do
|
46
|
-
send("with_#{key}")
|
47
|
-
end
|
48
|
-
end
|
35
|
+
define_named_scopes(name, column_name, mapping) if defined?(ActiveRecord)
|
49
36
|
end
|
50
37
|
|
51
|
-
def
|
38
|
+
def define_instance_methods(name, column_name, mapping)
|
52
39
|
define_method(name) do
|
53
40
|
roles = value_getter(column_name, mapping)
|
54
41
|
|
@@ -72,65 +59,9 @@ module BitwiseAttribute
|
|
72
59
|
# Each sym get a power of 2 value
|
73
60
|
def build_mapping(values)
|
74
61
|
{}.tap do |hash|
|
75
|
-
|
76
|
-
hash.merge!(values.sort_by { |_, value| value }.to_h)
|
77
|
-
else
|
78
|
-
values.each_with_index { |key, index| hash[key] = 2**index }
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# Validates the numeric values for each key. If it's not a power of 2 then raise ArgumentError.
|
84
|
-
def validate_values!(values)
|
85
|
-
return true if values.is_a?(Array)
|
86
|
-
|
87
|
-
values.reject { |_, value| (Math.log2(value) % 1.0).zero? }.tap do |invalid_options|
|
88
|
-
if invalid_options.any?
|
89
|
-
raise(ArgumentError, "Values should be a power of 2 (#{invalid_options})")
|
90
|
-
end
|
62
|
+
values.each_with_index { |key, index| hash[key] = (0b1 << index) }
|
91
63
|
end
|
92
64
|
end
|
93
|
-
|
94
|
-
def mapping_for_name(name)
|
95
|
-
@mapping[name.to_sym]
|
96
|
-
end
|
97
|
-
|
98
|
-
def values_for_column(name)
|
99
|
-
mapping_for_name(name).values
|
100
|
-
end
|
101
|
-
|
102
|
-
def map_to_values(name, keys)
|
103
|
-
mapping = mapping_for_name(name)
|
104
|
-
|
105
|
-
keys.map { |key| mapping[key.to_sym] }.compact
|
106
|
-
end
|
107
|
-
|
108
|
-
def bitwise_union(keys, name)
|
109
|
-
mask = []
|
110
|
-
|
111
|
-
mapped = map_to_values(name, keys)
|
112
|
-
|
113
|
-
mapped.each do |mv|
|
114
|
-
values_for_column(name).each do |value|
|
115
|
-
mask << (mv | value)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
mask.uniq
|
120
|
-
end
|
121
|
-
|
122
|
-
def bitwise_intersection(keys, name)
|
123
|
-
mask = []
|
124
|
-
|
125
|
-
mapped = map_to_values(name, keys)
|
126
|
-
mapped = mapped.reduce(&:|)
|
127
|
-
|
128
|
-
values_for_column(name).each do |value|
|
129
|
-
mask << (value | mapped)
|
130
|
-
end
|
131
|
-
|
132
|
-
mask.uniq
|
133
|
-
end
|
134
65
|
end
|
135
66
|
|
136
67
|
private
|
@@ -150,16 +81,12 @@ module BitwiseAttribute
|
|
150
81
|
values.each do |value|
|
151
82
|
raise(ArgumentError, "Unknown value #{value}!") unless mapping[value]
|
152
83
|
|
153
|
-
|
84
|
+
send("#{mask_column}=", send(mask_column) | mapping[value])
|
154
85
|
end
|
155
86
|
end
|
156
87
|
|
157
|
-
# Return if value
|
88
|
+
# Return if value is present in mask (raw value)
|
158
89
|
def value?(mask_column, val)
|
159
90
|
send(mask_column) & val != 0
|
160
91
|
end
|
161
|
-
|
162
|
-
def add_value(mask_column, val)
|
163
|
-
send("#{mask_column}=", send(mask_column) | val)
|
164
|
-
end
|
165
92
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitwise_attribute
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ricardo Otero
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-03-
|
11
|
+
date: 2018-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
type: :
|
19
|
+
version: '3'
|
20
|
+
type: :development
|
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:
|
26
|
+
version: '3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: 0.15.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
111
125
|
description: Bitwise attribute for ruby class and Rails model.
|
112
126
|
email:
|
113
127
|
- oterosantos@gmail.com
|
@@ -129,6 +143,7 @@ files:
|
|
129
143
|
- bin/setup
|
130
144
|
- bitwise_attribute.gemspec
|
131
145
|
- lib/bitwise_attribute.rb
|
146
|
+
- lib/bitwise_attribute/active_record_methods.rb
|
132
147
|
- lib/bitwise_attribute/values_list.rb
|
133
148
|
- lib/bitwise_attribute/version.rb
|
134
149
|
homepage: https://github.com/rikas/bitwise_attribute
|