enummer 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: 4f7599c1e54a52e62cd02fa52bda47b02b108193508575c9c7e4d46e7e67deb0
4
+ data.tar.gz: 9b26e5c4c28e8229519dd3eb1fed85c725d953472b7442b4291196740b7c6561
5
+ SHA512:
6
+ metadata.gz: 647d8fa2a18291044340ba71dc0ba0524c1e838ae1bb2187b54321c27888a0daa433cdd6ce132e7743a41b365540fdc3aa054e1f7c37c8813b4dc9bf1f1dff09
7
+ data.tar.gz: 5530474e36838b2189ad4d138520bd689e792086e8a1d5661d1b0846db5d17511e535ec7fab0c6969cdb82223ec33801fb990a843c62a099cf0527de2f287771
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 Jamie Schembri
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,41 @@
1
+ # Enummer
2
+ Enummer is a lightweight answer for adding enums with multiple values to Rails, with a similar syntax to Rails' built-in `enum`. It officially supports only PostgreSQL and recent Rails versions because I don't really care about anything else, but PRs are welcome.
3
+
4
+ ## Installation
5
+ Add `gem "enummer"` to your Gemfile and `bundle`.
6
+
7
+ ## Usage
8
+ Create a migration for a bitstring that looks something like this:
9
+
10
+ ```ruby
11
+ class CreateUsers < ActiveRecord::Migration[7.0]
12
+ def change
13
+ create_table :users do |t|
14
+ t.column :permissions, "bit(8)"
15
+ end
16
+ end
17
+ end
18
+ ```
19
+
20
+ Now set up enummer with the available values in your model:
21
+
22
+ ```ruby
23
+ enummer permissions: %i[read write execute]
24
+ ```
25
+
26
+ Note that the limit is currently **required** — do not use variable bit fields. 1 bit gets you 1 option.
27
+
28
+ *TODO*: a generator. Because.
29
+
30
+ ## Usage outside of Rails
31
+ lol stop
32
+
33
+ ## Contributing
34
+ Make an issue / PR and we'll see.
35
+
36
+ ## Alternatives
37
+ - [flag_shih_tzu](https://github.com/pboling/flag_shih_tzu)
38
+ - Lots of booleans
39
+
40
+ ## License
41
+ 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+
5
+ require "bundler/gem_tasks"
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/type"
4
+
5
+ module Enummer
6
+ class EnummerType < ::ActiveRecord::Type::Value
7
+ attr_reader :bit_pairs
8
+
9
+ def initialize(value_names:, limit:)
10
+ @value_names = value_names
11
+ @bit_pairs = determine_bit_pairs(value_names)
12
+ @limit = limit
13
+ end
14
+
15
+ def type
16
+ "enummer[#{@value_names.join("|")}]".to_sym
17
+ end
18
+
19
+ def serialize(value)
20
+ return unless value
21
+
22
+ int = Array.wrap(value).sum { |value_name| @bit_pairs.fetch(value_name, 0) }
23
+
24
+ int.to_s(2).rjust(@limit, "0")
25
+ end
26
+
27
+ def deserialize(value)
28
+ return [] unless value
29
+
30
+ value = value.to_i(2)
31
+ @bit_pairs.each_with_object([]) do |(pair_name, pair_value), value_names|
32
+ next if (value & pair_value).zero?
33
+
34
+ value_names << pair_name
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def determine_bit_pairs(value_names)
41
+ value_names.map.with_index do |name, shift|
42
+ [name, 1 << shift]
43
+ end.to_h
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enummer
4
+ module Extension
5
+ def enummer(values)
6
+ options = {}
7
+ options[:_prefix] = values.delete(:_prefix)
8
+ options[:_suffix] = values.delete(:_suffix)
9
+
10
+ name, values = values.first
11
+
12
+ limit = column_for_attribute(name).limit
13
+
14
+ attribute(name, :enummer, value_names: values, limit: limit)
15
+
16
+ singleton_class.__send__(:define_method, name) { values }
17
+
18
+ _enummer_build_with_scope(name, values, limit)
19
+ _enummer_build_values(name, values, limit, options)
20
+ end
21
+
22
+ private
23
+
24
+ def _enummer_build_values(attribute_name, value_names, limit, options)
25
+ value_names.each_with_index do |name, i|
26
+ method_name = _enummer_method_name(attribute_name, name, options)
27
+
28
+ define_method("#{method_name}?") { self[attribute_name].include?(name) }
29
+ define_method("#{method_name}=") do |new_value|
30
+ if new_value
31
+ self[attribute_name].push(name)
32
+ else
33
+ self[attribute_name].delete(name)
34
+ end
35
+ end
36
+ define_method("#{method_name}!") do
37
+ update(attribute_name => self[attribute_name] + [name])
38
+ end
39
+
40
+ bit_position = limit - i - 1
41
+
42
+ scope method_name, -> { where("get_bit(#{attribute_name}, ?) = 1", bit_position) }
43
+ scope "not_#{method_name}", -> { where("get_bit(#{attribute_name}, ?) = 0", bit_position) }
44
+ end
45
+ end
46
+
47
+ def _enummer_build_with_scope(attribute_name, value_names, limit)
48
+ scope "with_#{attribute_name}", lambda { |desired|
49
+ desired = Array.wrap(desired)
50
+
51
+ bitstring = (0..limit - 1).to_a.sum("") { |i| desired.include?(value_names[i]) ? "1" : "0" }.reverse
52
+
53
+ where("#{attribute_name} & :expected = :expected", expected: bitstring)
54
+ }
55
+ end
56
+
57
+ def _enummer_method_name(attribute_name, value_name, options)
58
+ prefix = _enummer_affix(attribute_name, options[:_prefix])
59
+ suffix = _enummer_affix(attribute_name, options[:_suffix])
60
+
61
+ [prefix, value_name, suffix].compact.join("_")
62
+ end
63
+
64
+ def _enummer_affix(attribute_name, value)
65
+ return unless value
66
+ return attribute_name if value == true
67
+
68
+ value
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enummer
4
+ class Railtie < ::Rails::Railtie
5
+ initializer "enummer" do
6
+ ActiveSupport.on_load(:active_record) do
7
+ ActiveRecord::Type.register(:enummer, EnummerType)
8
+ extend Enummer::Extension
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enummer
4
+ VERSION = "0.1.0"
5
+ end
data/lib/enummer.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enummer; end
4
+
5
+ require "enummer/version"
6
+ require "enummer/enummer_type"
7
+ require "enummer/extension"
8
+ require "enummer/railtie"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # desc "Explaining what the task does"
3
+ # task :enummer do
4
+ # # Task goes here
5
+ # end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: enummer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jamie Schembri
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-30 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.0
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.0
27
+ description: Enummer implements multi-value enums with PostgreSQL bitstrings.
28
+ email:
29
+ - jamie@schembri.me
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - MIT-LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - lib/enummer.rb
38
+ - lib/enummer/enummer_type.rb
39
+ - lib/enummer/extension.rb
40
+ - lib/enummer/railtie.rb
41
+ - lib/enummer/version.rb
42
+ - lib/tasks/enummer_tasks.rake
43
+ homepage: https://github.com/shkm/enummer
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ homepage_uri: https://github.com/shkm/enummer
48
+ source_code_uri: https://github.com/shkm/enummer
49
+ changelog_uri: https://github.com/shkm/enummer/CHANGELOG.md
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.3.3
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Multi-value enums for Rails
69
+ test_files: []