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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 728afc2714df27029947e0f98b2c542ad8835bf44e052f720c588d22f2bcb821
4
- data.tar.gz: 5e8fe818d69624b1888f954416a15f9cf938039067a5f67fea12eef73bf76e31
3
+ metadata.gz: '09a20c421bbc1d6c4c7803c43be1b937f53725c9f9cea4be27c05c0c00b4d23a'
4
+ data.tar.gz: 15af80b8780cfa9e081c45186049ee7bbd9a96266f140fcf2294e14adadf8dcc
5
5
  SHA512:
6
- metadata.gz: 257fb003a2eb0421a5ca03b5b6cd32a2b4317c7172cb26a204e2516bc9b7aeb07989a99867bb5f33bbb6ccc6667f7bd88d5e1c116b18f6d782eee9dd469c5ece
7
- data.tar.gz: 58b7153d9e3b51898d387252a1a91ed6f1514c778eff46f1941ed78cb4ff88b9d933131e493b4dbb184aaa27720d430335df741ba9a5ea103a66305b15d73a8e
6
+ metadata.gz: 8bbf57d1f5e5a69a2ab6952ca9d8cf5376e28846e8ab2b7d0c858d9838bf7d6563e6ceb283b8641b0095e2dc05150a17aa60bdafaad8c872c775bf967d4b9c61
7
+ data.tar.gz: 90a8b4ca18833063b57d64279ef2e2c8bef0c2a7975788c128dbf31fca0a4b3535a6820955a6e0e42412e4afb3f98a7db8e18549d8f5cfc93f16c5b66406fb24
data/.rspec CHANGED
@@ -1,4 +1,4 @@
1
1
  --format documentation
2
2
  --color
3
3
  --require spec_helper
4
- --order random
4
+ --order rand
data/.rubocop.yml CHANGED
@@ -2,20 +2,19 @@ Metrics/AbcSize:
2
2
  Max: 30
3
3
 
4
4
  Style/Documentation:
5
- Exclude:
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: 30
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
@@ -3,3 +3,6 @@ language: ruby
3
3
  rvm:
4
4
  - 2.5.0
5
5
  before_install: gem install bundler -v 1.16.1
6
+ script:
7
+ - bundle exec rspec --fail-fast
8
+ - bundle exec rubocop
data/Gemfile.lock CHANGED
@@ -1,17 +1,23 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bitwise_attribute (0.2.2)
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
- You can then use
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
- user = User.first
49
- user.roles #=> []
50
- user.roles = [:user, :admin] #=> [:user, :admin]
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
@@ -1,7 +1,7 @@
1
- # frozen_string_literal: true
2
-
3
1
  #!/usr/bin/env ruby
4
2
 
3
+ # frozen_string_literal: true
4
+
5
5
  require 'bundler/setup'
6
6
  require 'bitwise_attribute'
7
7
 
@@ -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.add_dependency 'activesupport', '~> 5.1.5'
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BitwiseAttribute
4
- VERSION = '0.2.2'
4
+ VERSION = '0.3.0'
5
5
  end
@@ -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
- extend ActiveSupport::Concern
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
- define_instante_methods(name, column_name, mapping)
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
- define_singleton_method("with_#{name}") do |*keys|
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 define_instante_methods(name, column_name, mapping)
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
- if values.is_a?(Hash)
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
- add_value(mask_column, mapping[value])
84
+ send("#{mask_column}=", send(mask_column) | mapping[value])
154
85
  end
155
86
  end
156
87
 
157
- # Return if value presents in mask (raw 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.2.2
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-28 00:00:00.000000000 Z
11
+ date: 2018-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport
14
+ name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 5.1.5
20
- type: :runtime
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: 5.1.5
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