careacademy-enumerize 2.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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +69 -0
  3. data/.gitignore +23 -0
  4. data/.rspec +2 -0
  5. data/CHANGELOG.md +327 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.global +12 -0
  8. data/Gemfile.mongo_mapper +6 -0
  9. data/Gemfile.rails60 +6 -0
  10. data/Gemfile.rails61 +6 -0
  11. data/Gemfile.rails70 +9 -0
  12. data/Gemfile.railsmaster +5 -0
  13. data/MIT-LICENSE +22 -0
  14. data/README.md +641 -0
  15. data/Rakefile +17 -0
  16. data/enumerize.gemspec +22 -0
  17. data/lib/enumerize/activemodel.rb +47 -0
  18. data/lib/enumerize/activerecord.rb +142 -0
  19. data/lib/enumerize/attribute.rb +192 -0
  20. data/lib/enumerize/attribute_map.rb +40 -0
  21. data/lib/enumerize/base.rb +112 -0
  22. data/lib/enumerize/hooks/formtastic.rb +27 -0
  23. data/lib/enumerize/hooks/sequel_dataset.rb +17 -0
  24. data/lib/enumerize/hooks/simple_form.rb +37 -0
  25. data/lib/enumerize/hooks/uniqueness.rb +22 -0
  26. data/lib/enumerize/integrations/rails_admin.rb +18 -0
  27. data/lib/enumerize/integrations/rspec/matcher.rb +164 -0
  28. data/lib/enumerize/integrations/rspec.rb +19 -0
  29. data/lib/enumerize/module.rb +33 -0
  30. data/lib/enumerize/module_attributes.rb +12 -0
  31. data/lib/enumerize/mongoid.rb +29 -0
  32. data/lib/enumerize/predicatable.rb +23 -0
  33. data/lib/enumerize/predicates.rb +76 -0
  34. data/lib/enumerize/scope/activerecord.rb +53 -0
  35. data/lib/enumerize/scope/mongoid.rb +50 -0
  36. data/lib/enumerize/scope/sequel.rb +56 -0
  37. data/lib/enumerize/sequel.rb +62 -0
  38. data/lib/enumerize/set.rb +81 -0
  39. data/lib/enumerize/utils.rb +12 -0
  40. data/lib/enumerize/value.rb +47 -0
  41. data/lib/enumerize/version.rb +5 -0
  42. data/lib/enumerize.rb +90 -0
  43. data/lib/sequel/plugins/enumerize.rb +18 -0
  44. data/spec/enumerize/integrations/rspec/matcher_spec.rb +261 -0
  45. data/spec/spec_helper.rb +30 -0
  46. data/test/activemodel_test.rb +114 -0
  47. data/test/activerecord_test.rb +679 -0
  48. data/test/attribute_map_test.rb +70 -0
  49. data/test/attribute_test.rb +141 -0
  50. data/test/base_test.rb +230 -0
  51. data/test/formtastic_test.rb +152 -0
  52. data/test/module_attributes_test.rb +52 -0
  53. data/test/mongo_mapper_test.rb +83 -0
  54. data/test/mongoid_test.rb +164 -0
  55. data/test/multiple_test.rb +65 -0
  56. data/test/predicates_test.rb +65 -0
  57. data/test/rails_admin_test.rb +27 -0
  58. data/test/sequel_test.rb +344 -0
  59. data/test/set_test.rb +166 -0
  60. data/test/simple_form_test.rb +156 -0
  61. data/test/support/mock_controller.rb +31 -0
  62. data/test/support/shared_enums.rb +43 -0
  63. data/test/support/view_test_helper.rb +46 -0
  64. data/test/test_helper.rb +53 -0
  65. data/test/value_test.rb +158 -0
  66. metadata +143 -0
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumerize
4
+ module Integrations
5
+ module RSpec
6
+ class Matcher
7
+
8
+ def initialize(expected_attr)
9
+ self.expected_attr = expected_attr
10
+ end
11
+
12
+ def in(*expected_values)
13
+ self.expected_values = expected_values.flatten
14
+ self
15
+ end
16
+
17
+ def with_default(expected_default)
18
+ self.expected_default = expected_default.to_s
19
+ self
20
+ end
21
+
22
+ def with_i18n_scope(expected_i18n_scope)
23
+ self.expected_i18n_scope = expected_i18n_scope
24
+ self
25
+ end
26
+
27
+ def with_predicates(expected_predicates)
28
+ self.expected_predicates = expected_predicates
29
+ self
30
+ end
31
+
32
+ def with_multiple(expected_multiple)
33
+ self.expected_multiple = expected_multiple
34
+ self
35
+ end
36
+
37
+ def with_scope(expected_scope)
38
+ self.expected_scope = expected_scope
39
+ self
40
+ end
41
+
42
+ def failure_message
43
+ "Expected #{expectation}"
44
+ end
45
+
46
+ def failure_message_when_negated
47
+ "Did not expect #{expectation}"
48
+ end
49
+
50
+ def description
51
+ description = "define enumerize :#{expected_attr}"
52
+ description += " in: #{quote_values(expected_values)}" if expected_values
53
+ description += " with #{expected_default.inspect} as default value" if expected_default
54
+ description += " i18n_scope: #{expected_i18n_scope.inspect}" if expected_i18n_scope
55
+ description += " predicates: #{expected_predicates.inspect}" if expected_predicates
56
+ description += " multiple: #{expected_multiple.inspect}" if expected_multiple
57
+ description += " scope: #{expected_scope.inspect}" if expected_scope
58
+
59
+ description
60
+ end
61
+
62
+ def matches?(subject)
63
+ self.subject = subject
64
+ matches = true
65
+
66
+ matches &= matches_attribute?
67
+ matches &= matches_values? if expected_values
68
+ matches &= matches_default_value? if expected_default
69
+ matches &= matches_i18n_scope? if expected_i18n_scope
70
+ matches &= matches_predicates? if expected_predicates
71
+ matches &= matches_multiple? if expected_multiple
72
+ matches &= matches_scope? if expected_scope
73
+
74
+ matches
75
+ end
76
+
77
+ private
78
+ attr_accessor :expected_attr, :expected_values, :subject, :expected_default,
79
+ :expected_i18n_scope, :expected_predicates, :expected_multiple,
80
+ :expected_scope
81
+
82
+ def expectation
83
+ "#{subject.class.name} to #{description}"
84
+ end
85
+
86
+ def matches_attribute?
87
+ attributes.present?
88
+ end
89
+
90
+ def matches_values?
91
+ matches_array_values? || matches_hash_values?
92
+ end
93
+
94
+ def matches_array_values?
95
+ sorted_values == enumerized_values
96
+ end
97
+
98
+ def matches_hash_values?
99
+ return unless expected_values.first.is_a?(Hash)
100
+ expected_values.first.all? { |k, v| enumerized_value_hash[k.to_s] == v; }
101
+ end
102
+
103
+ def matches_default_value?
104
+ expected_default == enumerized_default
105
+ end
106
+
107
+ def matches_i18n_scope?
108
+ attributes.i18n_scope == expected_i18n_scope
109
+ end
110
+
111
+ def matches_predicates?
112
+ if expected_predicates.is_a?(TrueClass)
113
+ subject.respond_to?("#{enumerized_values.first}?")
114
+ else
115
+ subject.respond_to?("#{expected_attr}_#{attributes.values.first}?")
116
+ end
117
+ end
118
+
119
+ def matches_multiple?
120
+ subject.public_send(expected_attr).is_a?(Enumerize::Set)
121
+ end
122
+
123
+ def matches_scope?
124
+ case expected_scope
125
+ when TrueClass
126
+ subject_class.respond_to?("with_#{expected_attr}")
127
+ when :shallow
128
+ enumerized_values.all? { |value| subject_class.respond_to?(value) }
129
+ else
130
+ subject_class.respond_to?(expected_scope[:scope])
131
+ end
132
+ end
133
+
134
+ def sorted_values
135
+ @sorted_values ||=expected_values.map(&:to_s).sort
136
+ end
137
+
138
+ def enumerized_values
139
+ @enumerized_values ||= attributes.values.sort
140
+ end
141
+
142
+ def enumerized_default
143
+ @enumerized_default ||= attributes.default_value
144
+ end
145
+
146
+ def enumerized_value_hash
147
+ @enumerized_value_hash ||= attributes.instance_variable_get('@value_hash')
148
+ end
149
+
150
+ def attributes
151
+ subject_class.enumerized_attributes.attributes[expected_attr.to_s]
152
+ end
153
+
154
+ def subject_class
155
+ @subject_class ||= subject.class
156
+ end
157
+
158
+ def quote_values(values)
159
+ sorted_values.map(&:inspect).join(', ')
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'enumerize/integrations/rspec/matcher'
4
+
5
+ module Enumerize
6
+ module Integrations
7
+ module RSpec
8
+ def enumerize(attr)
9
+ ::Enumerize::Integrations::RSpec::Matcher.new(attr)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ module RSpec
16
+ module Matchers
17
+ include Enumerize::Integrations::RSpec
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumerize
4
+ class Module < ::Module
5
+ attr_reader :_class_methods
6
+
7
+ def initialize
8
+ super
9
+
10
+ @_class_methods = ::Module.new
11
+ @_dependents = []
12
+ @_dependent_evals = []
13
+ end
14
+
15
+ def included(klass)
16
+ klass.extend _class_methods
17
+
18
+ @_dependent_evals.each do |block|
19
+ klass.instance_eval(&block)
20
+ end
21
+
22
+ @_dependents << klass
23
+ end
24
+
25
+ def dependent_eval(&block)
26
+ @_dependents.each do |klass|
27
+ klass.instance_eval(&block)
28
+ end
29
+
30
+ @_dependent_evals << block
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumerize
4
+ module ModuleAttributes
5
+ def included(base)
6
+ base.extend Enumerize
7
+ base.send :include, _enumerize_module
8
+ enumerized_attributes.add_dependant base.enumerized_attributes
9
+ super
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumerize
4
+ module MongoidSupport
5
+ def enumerize(name, options={})
6
+ super
7
+
8
+ _enumerize_module.dependent_eval do
9
+ if self < ::Mongoid::Document
10
+ include InstanceMethods
11
+
12
+ after_initialize :_set_default_value_for_enumerized_attributes
13
+ end
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+ def reload
19
+ reloaded = super
20
+
21
+ reloaded.class.enumerized_attributes.each do |attr|
22
+ reloaded.send("#{attr.name}=", reloaded[attr.name])
23
+ end
24
+
25
+ reloaded
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumerize
4
+ module Predicatable
5
+ def respond_to_missing?(method, include_private=false)
6
+ predicate_method?(method) || super
7
+ end
8
+
9
+ private
10
+
11
+ def method_missing(method, *args, &block)
12
+ if predicate_method?(method)
13
+ predicate_call(method[0..-2], *args, &block)
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ def predicate_method?(method)
20
+ method[-1] == '?' && @attr && @attr.values.include?(method[0..-2])
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/delegation'
4
+
5
+ module Enumerize
6
+ # Predicate methods.
7
+ #
8
+ # Basic usage:
9
+ #
10
+ # class User
11
+ # extend Enumerize
12
+ # enumerize :sex, in: %w(male female), predicates: true
13
+ # end
14
+ #
15
+ # user = User.new
16
+ #
17
+ # user.male? # => false
18
+ # user.female? # => false
19
+ #
20
+ # user.sex = 'male'
21
+ #
22
+ # user.male? # => true
23
+ # user.female? # => false
24
+ #
25
+ # Using prefix:
26
+ #
27
+ # class User
28
+ # extend Enumerize
29
+ # enumerize :sex, in: %w(male female), predicates: { prefix: true }
30
+ # end
31
+ #
32
+ # user = User.new
33
+ # user.sex = 'female'
34
+ # user.sex_female? # => true
35
+ #
36
+ # Use <tt>only</tt> and <tt>except</tt> options to specify what values create
37
+ # predicate methods for.
38
+ module Predicates
39
+ def enumerize(name, options={})
40
+ super
41
+
42
+ if options[:predicates]
43
+ Builder.new(enumerized_attributes[name], options[:predicates]).build(_enumerize_module)
44
+ end
45
+ end
46
+
47
+ class Builder
48
+ def initialize(attr, options)
49
+ @attr = attr
50
+ @options = options.is_a?(Hash) ? options : {}
51
+ end
52
+
53
+ def values
54
+ values = @attr.values
55
+
56
+ if @options[:only]
57
+ values &= Array(@options[:only]).map(&:to_s)
58
+ end
59
+
60
+ if @options[:except]
61
+ values -= Array(@options[:except]).map(&:to_s)
62
+ end
63
+
64
+ values
65
+ end
66
+
67
+ def names
68
+ values.map { |v| "#{v.tr('-', '_')}?" }
69
+ end
70
+
71
+ def build(klass)
72
+ klass.delegate(*names, to: @attr.name, prefix: @options[:prefix], allow_nil: true)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumerize
4
+ module Scope
5
+ module ActiveRecord
6
+ def enumerize(name, options={})
7
+ super
8
+
9
+ _enumerize_module.dependent_eval do
10
+ if self < ::ActiveRecord::Base
11
+ if options[:scope]
12
+ _define_activerecord_scope_methods!(name, options)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def _define_activerecord_scope_methods!(name, options)
21
+ return _define_activerecord_shallow_scopes!(name) if options[:scope] == :shallow
22
+
23
+ scope_name = options[:scope] == true ? "with_#{name}" : options[:scope]
24
+
25
+ define_singleton_method scope_name do |*values|
26
+ values = enumerized_attributes[name].find_values(*values).map(&:value)
27
+ values = values.first if values.size == 1
28
+
29
+ where(name => values)
30
+ end
31
+
32
+ if options[:scope] == true
33
+ define_singleton_method "without_#{name}" do |*values|
34
+ values = enumerized_attributes[name].find_values(*values).map(&:value)
35
+ where(arel_table[name].not_in(values))
36
+ end
37
+ end
38
+ end
39
+
40
+ def _define_activerecord_shallow_scopes!(attribute_name)
41
+ enumerized_attributes[attribute_name].each_value do |value_obj|
42
+ define_singleton_method(value_obj) do
43
+ where(attribute_name => value_obj.value)
44
+ end
45
+
46
+ define_singleton_method("not_#{value_obj}") do
47
+ where.not(attribute_name => value_obj.value)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumerize
4
+ module Scope
5
+ module Mongoid
6
+ def enumerize(name, options={})
7
+ super
8
+
9
+ _enumerize_module.dependent_eval do
10
+ if self < ::Mongoid::Document
11
+ if options[:scope]
12
+ _define_mongoid_scope_methods!(name, options)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def _define_mongoid_scope_methods!(name, options)
21
+ return _define_mongoid_shallow_scopes!(name) if options[:scope] == :shallow
22
+ scope_name = options[:scope] == true ? "with_#{name}" : options[:scope]
23
+
24
+ define_singleton_method scope_name do |*values|
25
+ values = enumerized_attributes[name].find_values(*values).map(&:value)
26
+ self.in(name => values)
27
+ end
28
+
29
+ if options[:scope] == true
30
+ define_singleton_method "without_#{name}" do |*values|
31
+ values = enumerized_attributes[name].find_values(*values).map(&:value)
32
+ not_in(name => values)
33
+ end
34
+ end
35
+ end
36
+
37
+ def _define_mongoid_shallow_scopes!(attribute_name)
38
+ enumerized_attributes[attribute_name].each_value do |value_obj|
39
+ define_singleton_method(value_obj) do
40
+ self.in(attribute_name => value_obj.value)
41
+ end
42
+
43
+ define_singleton_method("not_#{value_obj}") do
44
+ self.not_in(attribute_name => value_obj.value)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumerize
4
+ module Scope
5
+ module Sequel
6
+ def enumerize(name, options={})
7
+ super
8
+
9
+ _enumerize_module.dependent_eval do
10
+ if defined?(::Sequel::Model) && self < ::Sequel::Model
11
+ if options[:scope]
12
+ _define_sequel_scope_methods!(name, options)
13
+ end
14
+
15
+ require 'enumerize/hooks/sequel_dataset'
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def _define_sequel_scope_methods!(name, options)
23
+ return _define_sequel_shallow_scopes!(name) if options[:scope] == :shallow
24
+
25
+ klass = self
26
+ scope_name = options[:scope] == true ? "with_#{name}" : options[:scope]
27
+
28
+ def_dataset_method scope_name do |*values|
29
+ values = values.map { |value| klass.enumerized_attributes[name].find_value(value).value }
30
+ values = values.first if values.size == 1
31
+
32
+ where(name => values)
33
+ end
34
+
35
+ if options[:scope] == true
36
+ def_dataset_method "without_#{name}" do |*values|
37
+ values = values.map { |value| klass.enumerized_attributes[name].find_value(value).value }
38
+ exclude(name => values)
39
+ end
40
+ end
41
+ end
42
+
43
+ def _define_sequel_shallow_scopes!(attribute_name)
44
+ enumerized_attributes[attribute_name].each_value do |value_obj|
45
+ def_dataset_method(value_obj) do
46
+ where(attribute_name => value_obj.value.to_s)
47
+ end
48
+
49
+ def_dataset_method("not_#{value_obj}") do
50
+ exclude(attribute_name => value_obj.value.to_s)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumerize
4
+ module SequelSupport
5
+ def enumerize(name, options={})
6
+ super
7
+
8
+ _enumerize_module.dependent_eval do
9
+ if defined?(::Sequel::Model) && self < ::Sequel::Model
10
+ include InstanceMethods
11
+
12
+ require 'enumerize/hooks/sequel_dataset'
13
+ end
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ module InstanceMethods
20
+ def validate
21
+ super
22
+
23
+ self.class.enumerized_attributes.each do |attr|
24
+ skip_validations = Utils.call_if_callable(attr.skip_validations_value, self)
25
+ next if skip_validations
26
+
27
+ value = read_attribute_for_validation(attr.name)
28
+ next if value.blank?
29
+
30
+ if attr.kind_of? Multiple
31
+ errors.add attr.name, "is invalid" unless value.respond_to?(:all?) && value.all? { |v| v.blank? || attr.find_value(v) }
32
+ else
33
+ errors.add attr.name, "is not included in the list" unless attr.find_value(value)
34
+ end
35
+ end
36
+ end
37
+
38
+ def _set_default_value_for_enumerized_attributes
39
+ _enumerized_values_for_validation.delete_if do |k, v|
40
+ v.nil?
41
+ end
42
+
43
+ if defined?(Sequel::Plugins::Serialization::InstanceMethods)
44
+ modules = self.class.ancestors
45
+ plugin_idx = modules.index(Sequel::Plugins::Serialization::InstanceMethods)
46
+
47
+ if plugin_idx && plugin_idx < modules.index(Enumerize::SequelSupport::InstanceMethods)
48
+ abort "ERROR: You need to enable the Sequel serialization plugin before calling any enumerize methods on a model."
49
+ end
50
+
51
+ plugin_idx = modules.index(Sequel::Plugins::ValidationHelpers::InstanceMethods)
52
+
53
+ if plugin_idx && plugin_idx < modules.index(Enumerize::SequelSupport::InstanceMethods)
54
+ abort "ERROR: You need to enable the Sequel validation_helpers plugin before calling any enumerize methods on a model."
55
+ end
56
+ end
57
+
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/delegation'
4
+
5
+ module Enumerize
6
+ class Set
7
+ include Enumerable
8
+ include Predicatable
9
+
10
+ attr_reader :values
11
+
12
+ def initialize(obj, attr, values)
13
+ @obj = obj
14
+ @attr = attr
15
+ @values = []
16
+
17
+ if values.respond_to?(:each)
18
+ values.each do |input|
19
+ value = @attr.find_value(input)
20
+
21
+ if value && !@values.include?(value)
22
+ @values << value
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def <<(value)
29
+ @values << value
30
+ mutate!
31
+ end
32
+
33
+ alias_method :push, :<<
34
+
35
+ delegate :each, :empty?, :size, to: :values
36
+
37
+ def to_ary
38
+ @values.to_a
39
+ end
40
+
41
+ def texts
42
+ @values.map(&:text)
43
+ end
44
+
45
+ delegate :join, to: :to_ary
46
+
47
+ def ==(other)
48
+ return false unless other.respond_to?(:each)
49
+ other.size == size && other.all? { |v| @values.include?(@attr.find_value(v)) }
50
+ end
51
+
52
+ alias_method :eql?, :==
53
+
54
+ def include?(value)
55
+ @values.include?(@attr.find_value(value))
56
+ end
57
+
58
+ def delete(value)
59
+ @values.delete(@attr.find_value(value))
60
+ mutate!
61
+ end
62
+
63
+ def inspect
64
+ "#<Enumerize::Set {#{join(', ')}}>"
65
+ end
66
+
67
+ def encode_with(coder)
68
+ coder.represent_object(Array, @values)
69
+ end
70
+
71
+ private
72
+
73
+ def predicate_call(value)
74
+ include?(value)
75
+ end
76
+
77
+ def mutate!
78
+ @values = @obj.public_send("#{@attr.name}=", @values).values
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enumerize
4
+ module Utils
5
+ class << self
6
+ def call_if_callable(value, param = nil)
7
+ return value unless value.respond_to?(:call)
8
+ value.arity == 0 ? value.call : value.call(param)
9
+ end
10
+ end
11
+ end
12
+ end