bitwise_attribute 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/rikas/bitwise_attribute.svg?branch=master)](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
|