enumy 0.6.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce0707d044a3a8c05c9d9989c190177ff349fca9440a2ac74e49522caa924948
4
- data.tar.gz: 2a4058a520e603d800936498db7a6c586a9cdd1da8ab1c550c7ef9cd7864215b
3
+ metadata.gz: c5a41d5b566db44a73694cda348ebdafd92ba493df67811989767da5ded37a32
4
+ data.tar.gz: 650fea0c0613b094a3323122fc6e95b7b0df42e6ee16a748b8f062ab2fb3f712
5
5
  SHA512:
6
- metadata.gz: 76fa6acec0c8dd849adf0924783ee2eab517c8b27673f681a32710e788089e2522a834c48e3aa013f7e42f5004028dfeb150a2af31c2c9e2ad6ddcf10caa4d06
7
- data.tar.gz: c223050367c30eba0b51ea73900ad9c903cfe2edd0e3c79d99e5094a5a435209601dba6906e481f2734d2baecae499de2fb23ccddef346c949b83be7a9be2cc3
6
+ metadata.gz: 0c0bafe3cd2eec7e47741617d0455b208d168559d3a972ca9b800efa1a8edbd1e4a7b451b95ee949d218411696a2deef5a31bcd24029e0e08d138a4faa8c8ce0
7
+ data.tar.gz: 98f2c6f8d26fcfb6f2067c97ae205807e8cab6835b5fb992fa3ec7f8e0d3f3e63b04eb8417b7ebe5b12be52cf143852007304d0b8ed9c77edff3ff8363284116
data/lib/enumy/enum.rb ADDED
@@ -0,0 +1,65 @@
1
+ require 'enumy/errors'
2
+
3
+ module Enumy
4
+ class Enum
5
+
6
+ attr_reader :key
7
+
8
+ def initialize(key)
9
+ raise Errors::KeyMissingError.new(self) if key.nil?
10
+ @key = key.to_sym
11
+ end
12
+
13
+ def to_s
14
+ key.to_s
15
+ end
16
+
17
+ def inspect
18
+ attributes = instance_variables.map do |name|
19
+ value = instance_variable_get(name)
20
+ "#{name}: #{value.inspect}"
21
+ end
22
+
23
+ "#<#{self.class.name} #{attributes.join(', ')}>"
24
+ end
25
+
26
+ class << self
27
+
28
+ def define(enum)
29
+ raise ArgumentError, "Expected an object of type `Enumy::Enum`, got #{enum.class.name}" unless enum.is_a?(Enumy::Enum)
30
+
31
+ if instances[enum.key]
32
+ raise Errors::DuplicateEnumInstanceError, "Enum `#{enum.key.to_s.upcase}` is using a duplicate key `#{enum.key}` for Enum '#{enum.class.name}'."
33
+ else
34
+ instances[enum.key] = enum
35
+ end
36
+ end
37
+
38
+ def all
39
+ instances.values
40
+ end
41
+
42
+ def find(key)
43
+ find_by(key: key.to_sym) || raise(Errors::EnumNotFoundError.new("Could not find a `#{self.name}` enum with a `key` of #{key}"))
44
+ end
45
+
46
+ def find_by(**args)
47
+ raise ArgumentError, "`find_by` requires at least one key-value pair" if args.empty?
48
+
49
+ instances.values.find do |instance|
50
+ args.all? do |attr, value|
51
+ instance.send(attr) == value
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def instances
59
+ @instances ||= {}
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
data/lib/enumy/errors.rb CHANGED
@@ -5,5 +5,11 @@ module Enumy
5
5
  class InvalidEnumClassError < NameError; end
6
6
  class DuplicateEnumInstanceError < ArgumentError; end
7
7
 
8
+ class KeyMissingError < ArgumentError
9
+ def initialize(enum)
10
+ super("#{enum.class.name.upcase} enum requires a `key`")
11
+ end
12
+ end
13
+
8
14
  end
9
15
  end
data/lib/enumy/model.rb CHANGED
@@ -1,32 +1,47 @@
1
- require_relative 'base'
2
- require_relative 'errors'
1
+ require 'enumy/enum'
2
+ require 'enumy/errors'
3
3
 
4
4
  module Enumy
5
5
  module Model
6
- def has_enum(name, class_name: name, **options)
7
- enum_class = class_name.to_s.classify.safe_constantize
8
6
 
9
- assert_valid_enum_type!(enum_class, class_name)
10
- define_enum_attributes(name, enum_class, default: options[:default])
11
- define_enum_validations(name, enum_class) unless options[:optional]
7
+ def has_enum(name, enum_class:, **options)
8
+ assert_valid_enum_type!(enum_class)
9
+ define_enum_methods(name, enum_class, default: options[:default])
10
+ end
11
+
12
+ def assert_valid_enum_value_type!(enum_class, enum)
13
+ return if enum.nil?
14
+ raise Errors::InvalidEnumClassError, "Expected instance of `#{enum_class}`, got instance of `#{enum.class}`" unless enum.is_a?(enum_class)
12
15
  end
13
16
 
14
17
  private
15
18
 
16
- def define_enum_attributes(name, enum_class, **options)
17
- attribute name, ActiveModel::Type::Enum.new(enum_class), default: -> { options[:default] }
18
- end
19
+ def define_enum_methods(name, enum_class, default: nil)
20
+ assert_valid_enum_value_type!(enum_class, default)
19
21
 
20
- def define_enum_validations(name, enum_class)
21
- validates name, presence: true, inclusion: { in: enum_class.all }
22
- end
22
+ define_method(name) do
23
+ instance_variable_set("@#{name}", default) unless instance_variable_defined?("@#{name}")
24
+ instance_variable_get("@#{name}")
25
+ end
23
26
 
24
- def assert_valid_enum_type!(enum_class, class_name)
25
- if enum_class.nil?
26
- raise Errors::InvalidEnumClassError, "Could not resolve class `#{class_name.to_s.classify}`"
27
- elsif !enum_class.ancestors.include?(Enumy::Base)
28
- raise Errors::InvalidEnumClassError, "`#{enum_class.name}` does not inherit from `#{Enumy::Base.name}`"
27
+ define_method("#{name}=") do |value|
28
+ case value
29
+ when Enumy::Enum, NilClass
30
+ self.class.assert_valid_enum_value_type!(enum_class, value)
31
+ instance_variable_set("@#{name}", value)
32
+ when String, Symbol
33
+ enum = enum_class.find(value)
34
+ instance_variable_set("@#{name}", enum)
35
+ else
36
+ raise ArgumentError
37
+ end
29
38
  end
30
39
  end
40
+
41
+ def assert_valid_enum_type!(enum_class)
42
+ raise Errors::InvalidEnumClassError, "`#{enum_class}` is not a constant" unless enum_class.is_a?(Module)
43
+ raise Errors::InvalidEnumClassError, "`#{enum_class.name}` does not inherit from `#{Enumy::Enum.name}`" unless enum_class.ancestors.include?(Enumy::Enum)
44
+ end
45
+
31
46
  end
32
- end
47
+ end
@@ -0,0 +1,30 @@
1
+ require 'enumy/enum.rb'
2
+
3
+ module Enumy
4
+ module Rails
5
+ module ActiveJob
6
+ module Serializers
7
+ class EnumSerializer < ::ActiveJob::Serializers::ObjectSerializer
8
+
9
+ def serialize(enum)
10
+ super({ class: enum.class.name, key: enum.key.to_s })
11
+ end
12
+
13
+ def deserialize(hash)
14
+ hash[:class].safe_constantize&.find_by(key: hash[:key].to_sym)
15
+ end
16
+
17
+ def serialize?(value)
18
+ value.is_a?(klass)
19
+ end
20
+
21
+ private
22
+
23
+ def klass
24
+ Enumy::Enum
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ module Enumy
2
+ module Rails
3
+ module ActiveModel
4
+ module Type
5
+ class Enum < ::ActiveModel::Type::Value
6
+ def initialize(enum_class)
7
+ @enum_class = enum_class
8
+ super()
9
+ end
10
+
11
+ def cast(value)
12
+ value.is_a?(@enum_class) ? value : @enum_class.find_by(key: value&.to_sym)
13
+ end
14
+
15
+ def serialize(value)
16
+ if value.is_a?(@enum_class)
17
+ value.key.to_s
18
+ else
19
+ @enum_class.find_by(key: value&.to_sym) ? value&.to_s : nil
20
+ end
21
+ end
22
+
23
+ def deserialize(value)
24
+ value.is_a?(@enum_class) ? value : @enum_class.find_by(key: value&.to_sym)
25
+ end
26
+
27
+ def type
28
+ :enum
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ require 'enumy/rails/initializer_interceptor'
2
+
3
+ module Enumy
4
+ module Rails
5
+ class Enum < Enumy::Enum
6
+ include Enumy::Rails::InitializerInterceptor
7
+ include ::ActiveModel::Model
8
+ include ::ActiveModel::Attributes
9
+
10
+ attribute :key
11
+
12
+ def initialize(key, **attrs)
13
+ raise Errors::KeyMissingError.new(self) if key.nil?
14
+ super(key: key.to_sym, **attrs)
15
+ end
16
+
17
+ def titleize
18
+ key.to_s.titleize
19
+ end
20
+
21
+ def human_attribute_name(attribute)
22
+ I18n.t("#{self.class.i18n_scope}.attributes.models.#{self.class.model_name.i18n_key}/#{self.key}.#{attribute}")
23
+ end
24
+
25
+ def name
26
+ human_attribute_name(:key)
27
+ end
28
+
29
+ def inspect
30
+ "#<#{self.class.name} #{attributes.map { |name, value| "#{name}: #{value.inspect}" }.join(', ')}>"
31
+ end
32
+
33
+ class << self
34
+
35
+ def model_name
36
+ @model_name ||= ::ActiveModel::Name.new(self, nil, self.name.to_s)
37
+ end
38
+
39
+ def i18n_scope
40
+ :enum
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,13 @@
1
+ require "rails/generators"
2
+
3
+ module Enumy
4
+ module Generators
5
+ class InstallGenerator < ::Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ def copy_application_enum
9
+ template "app/enums/application_enum.rb", "app/enums/application_enum.rb"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationEnum < Enumy::Rails::Enum
2
+
3
+ end
@@ -0,0 +1,12 @@
1
+ module Enumy
2
+ module Rails
3
+ module InitializerInterceptor
4
+ def initialize
5
+ # Intentionally left blank to intercept the original initialize method
6
+ # We do this because we want to use ActiveModel::Attributes for attribute handling
7
+ # but ActiveModel::Attributes calls super without arguments,
8
+ # which would call the original initialize method without arguments and raise an error.
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ require 'enumy/enum'
2
+ require 'enumy/errors'
3
+
4
+ module Enumy
5
+ module Rails
6
+ module Model
7
+ def has_enum(name, class_name: name, **options)
8
+ enum_class = class_name.to_s.classify.safe_constantize
9
+
10
+ assert_valid_enum_type!(enum_class, class_name)
11
+ define_enum_attributes(name, enum_class, default: options[:default])
12
+ define_enum_validations(name, enum_class) unless options[:optional]
13
+ end
14
+
15
+ private
16
+
17
+ def define_enum_attributes(name, enum_class, **options)
18
+ attribute name, ActiveModel::Type::Enum.new(enum_class), default: -> { options[:default] }
19
+ end
20
+
21
+ def define_enum_validations(name, enum_class)
22
+ validates name, presence: true, inclusion: { in: enum_class.all }
23
+ end
24
+
25
+ def assert_valid_enum_type!(enum_class, class_name)
26
+ if enum_class.nil?
27
+ raise Errors::InvalidEnumClassError, "Could not resolve class `#{class_name.to_s.classify}`"
28
+ elsif !enum_class.ancestors.include?(Enumy::Enum)
29
+ raise Errors::InvalidEnumClassError, "`#{enum_class.name}` does not inherit from `#{Enumy::Enum.name}`"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,9 +1,9 @@
1
1
  module Enumy
2
- class Railtie < Rails::Railtie
2
+ class Railtie < ::Rails::Railtie
3
3
  initializer "enumy.register_active_job_serializer" do |app|
4
- if Rails::VERSION::MAJOR >= 6
5
- require 'enumy/active_job/serializers/enum_serializer'
6
- app.config.active_job.custom_serializers << Enumy::ActiveJob::Serializers::EnumSerializer
4
+ if ::Rails::VERSION::MAJOR >= 6
5
+ require 'enumy/rails/active_job/serializers/enum_serializer'
6
+ app.config.active_job.custom_serializers << Enumy::Rails::ActiveJob::Serializers::EnumSerializer
7
7
  end
8
8
  end
9
9
  end
data/lib/enumy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Enumy
4
- VERSION = "0.6.0"
4
+ VERSION = "0.8.0"
5
5
  end
data/lib/enumy.rb CHANGED
@@ -1,15 +1,11 @@
1
1
  require 'enumy/version'
2
-
3
- if defined?(ActiveModel)
4
- require 'enumy/base'
5
- end
2
+ require 'enumy/enum'
3
+ require 'enumy/model'
6
4
 
7
5
  if defined?(Rails)
8
- require 'enumy/generators/enumy/install/install_generator'
9
- require 'enumy/active_model/type/enum'
10
- require 'enumy/model'
11
-
12
- if defined?(Rails::Railtie)
13
- require 'enumy/rails/railtie'
14
- end
6
+ require 'enumy/rails/enum'
7
+ require 'enumy/rails/model'
8
+ require 'enumy/rails/generators/enumy/install/install_generator'
9
+ require 'enumy/rails/active_model/type/enum'
10
+ require 'enumy/rails/railtie'
15
11
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enumy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bevan Holborn
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-06-24 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake
@@ -53,28 +52,28 @@ dependencies:
53
52
  - !ruby/object:Gem::Version
54
53
  version: '7.0'
55
54
  description: Use Enumeration objects as ActiveRecord like associations
56
- email:
57
55
  executables: []
58
56
  extensions: []
59
57
  extra_rdoc_files: []
60
58
  files:
61
59
  - lib/enumy.rb
62
- - lib/enumy/active_job/serializers/enum_serializer.rb
63
- - lib/enumy/active_model/type/enum.rb
64
- - lib/enumy/base.rb
60
+ - lib/enumy/enum.rb
65
61
  - lib/enumy/errors.rb
66
- - lib/enumy/generators/enumy/install/install_generator.rb
67
- - lib/enumy/generators/enumy/install/templates/app/enums/application_enum.rb
68
62
  - lib/enumy/model.rb
63
+ - lib/enumy/rails/active_job/serializers/enum_serializer.rb
64
+ - lib/enumy/rails/active_model/type/enum.rb
65
+ - lib/enumy/rails/enum.rb
66
+ - lib/enumy/rails/generators/enumy/install/install_generator.rb
67
+ - lib/enumy/rails/generators/enumy/install/templates/app/enums/application_enum.rb
68
+ - lib/enumy/rails/initializer_interceptor.rb
69
+ - lib/enumy/rails/model.rb
69
70
  - lib/enumy/rails/railtie.rb
70
- - lib/enumy/test_helpers.rb
71
71
  - lib/enumy/version.rb
72
72
  homepage: https://rubygems.org/gems/enumy
73
73
  licenses:
74
74
  - MIT
75
75
  metadata:
76
76
  source_code_uri: https://gitlab.com/BevanHolborn/enumy
77
- post_install_message:
78
77
  rdoc_options: []
79
78
  require_paths:
80
79
  - lib
@@ -89,8 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
88
  - !ruby/object:Gem::Version
90
89
  version: '0'
91
90
  requirements: []
92
- rubygems_version: 3.3.27
93
- signing_key:
91
+ rubygems_version: 3.6.9
94
92
  specification_version: 4
95
93
  summary: ActiveRecord like enum associations
96
94
  test_files: []
@@ -1,28 +0,0 @@
1
- require 'enumy/base.rb'
2
-
3
- module Enumy
4
- module ActiveJob
5
- module Serializers
6
- class EnumSerializer < ::ActiveJob::Serializers::ObjectSerializer
7
-
8
- def serialize(enum)
9
- super({ class: enum.class.name, key: enum.key.to_s })
10
- end
11
-
12
- def deserialize(hash)
13
- hash[:class].constantize.find_by_key(hash[:key])
14
- end
15
-
16
- def serialize?(value)
17
- value.is_a?(klass)
18
- end
19
-
20
- private
21
-
22
- def klass
23
- Enumy::Base
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,32 +0,0 @@
1
- module Enumy
2
- module ActiveModel
3
- module Type
4
- class Enum < ::ActiveModel::Type::Value
5
- def initialize(enum_class)
6
- @enum_class = enum_class
7
- super()
8
- end
9
-
10
- def cast(value)
11
- value.is_a?(@enum_class) ? value : @enum_class.find_by_key(value&.to_sym)
12
- end
13
-
14
- def serialize(value)
15
- if value.is_a?(@enum_class)
16
- value.key.to_s
17
- else
18
- @enum_class.find_by_key(value&.to_sym) ? value&.to_s : nil
19
- end
20
- end
21
-
22
- def deserialize(value)
23
- value.is_a?(@enum_class) ? value : @enum_class.find_by_key(value&.to_sym)
24
- end
25
-
26
- def type
27
- :enum
28
- end
29
- end
30
- end
31
- end
32
- end
data/lib/enumy/base.rb DELETED
@@ -1,87 +0,0 @@
1
- require_relative 'errors'
2
-
3
- module Enumy
4
- class Base
5
- include ::ActiveModel::Model
6
- include ::ActiveModel::Attributes
7
-
8
- attribute :key
9
-
10
- def initialize(key, **attrs)
11
- raise ArgumentError, "#{self.class.name.upcase} enum requires a `key`" if key.nil?
12
- key = key.to_sym
13
-
14
- super(key: key, **attrs)
15
- end
16
-
17
- def human_attribute_name(attribute)
18
- I18n.t("#{self.class.i18n_scope}.attributes.models.#{self.class.model_name.i18n_key}/#{self.key}.#{attribute}")
19
- end
20
-
21
- def name
22
- human_attribute_name(:key)
23
- end
24
-
25
- def titleize
26
- key.to_s.titleize
27
- end
28
-
29
- def to_s
30
- key.to_s
31
- end
32
-
33
- def inspect
34
- "#<#{self.class.name} #{attributes.map { |name, value| "#{name}: #{value.inspect}" }.join(', ')}>"
35
- end
36
-
37
- class << self
38
-
39
- def define(enum)
40
- raise ArgumentError, "Expected an object of type `Enumy::Base`, got #{enum.class.name}" unless enum.is_a?(Enumy::Base)
41
-
42
- if instances[enum.key]
43
- raise Errors::DuplicateEnumInstanceError, "Enum `#{enum.key.to_s.upcase}` is using a duplicate key `#{enum.key}` for Enum '#{enum.class.name}'."
44
- else
45
- instances[enum.key] = enum
46
- end
47
- end
48
-
49
- def all
50
- instances.values
51
- end
52
-
53
- def find(key)
54
- find_by_key(key) || raise(Errors::EnumNotFoundError.new("Could not find a `#{self.name}` enum with a `key` of #{key}"))
55
- end
56
-
57
- def find_by_key(key)
58
- key = key.to_sym if key.is_a?(String)
59
- instances[key]
60
- end
61
-
62
- def find_by(**args)
63
- raise ArgumentError, "`find_by` requires at least one key-value pair" if args.empty?
64
-
65
- instances.values.find do |instance|
66
- args.all? { |attr, value| instance.send(attr) == value }
67
- end
68
- end
69
-
70
- def model_name
71
- @model_name ||= ::ActiveModel::Name.new(self, nil, self.name.to_s)
72
- end
73
-
74
- def i18n_scope
75
- :enum
76
- end
77
-
78
- private
79
-
80
- def instances
81
- @instances ||= {}
82
- end
83
-
84
- end
85
-
86
- end
87
- end
@@ -1,22 +0,0 @@
1
- require "rails/generators"
2
-
3
- module Enumy
4
- module Generators
5
- class InstallGenerator < Rails::Generators::Base
6
- source_root File.expand_path("templates", __dir__)
7
-
8
- def copy_application_enum
9
- template "app/enums/application_enum.rb", "app/enums/application_enum.rb"
10
- end
11
-
12
- def add_application_record_concern
13
- inject_into_file "app/models/application_record.rb", after: /^.*primary_abstract_class.*$/ do
14
- <<~CODE
15
-
16
- \textend Enumy::Model\n
17
- CODE
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,3 +0,0 @@
1
- class ApplicationEnum < Enumy::Base
2
-
3
- end
@@ -1,28 +0,0 @@
1
- module Enumy
2
- module TestHelpers
3
- def with_enum_defined(enum_class, enum_instance)
4
- enum_class.define(enum_instance)
5
-
6
- yield
7
-
8
- enum_class.send(:instances).delete(enum_instance.key)
9
- enum_const_name = enum_class.constants.find { |const_name| enum_class.const_get(const_name) == enum_instance }
10
- remove_const(enum_const_name) if enum_const_name
11
- end
12
-
13
- def enum_model_class(&block)
14
- base_class = Class.new(ApplicationRecord) do
15
- extend Enumy::Model
16
-
17
- class << self
18
- def name
19
- "TempModel"
20
- end
21
- end
22
- end
23
-
24
- base_class.class_exec(&block) if block
25
- base_class
26
- end
27
- end
28
- end