enum_ext 0.5.3 → 0.8.1
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 +4 -4
- data/CHANGELOG.md +24 -0
- data/Dockerfile +22 -0
- data/Dockerfile_rails_7 +25 -0
- data/{Gemfile → Gemfile_rails_6} +1 -0
- data/{Gemfile.lock → Gemfile_rails_6.lock} +2 -2
- data/Gemfile_rails_7 +6 -0
- data/Gemfile_rails_7.lock +102 -0
- data/README.md +141 -54
- data/docker-compose.yml +24 -0
- data/img.png +0 -0
- data/img_1.png +0 -0
- data/img_2.png +0 -0
- data/img_3.png +0 -0
- data/lib/enum_ext/annotated.rb +181 -0
- data/lib/enum_ext/basic_helpers.rb +86 -0
- data/lib/enum_ext/enum_wrapper.rb +78 -0
- data/lib/enum_ext/humanize_helpers.rb +159 -0
- data/lib/enum_ext/superset_helpers.rb +83 -0
- data/lib/enum_ext/version.rb +1 -1
- data/lib/enum_ext.rb +43 -358
- metadata +19 -5
@@ -0,0 +1,181 @@
|
|
1
|
+
# I wanted to add some quick live annotation to what's defined and how it could be used
|
2
|
+
# but have no idea how to do this in a super-neat way, so it a little bit chaotic and experimental
|
3
|
+
module EnumExt::Annotated
|
4
|
+
|
5
|
+
# call it to see what's your enum current options are
|
6
|
+
def describe_basic
|
7
|
+
puts yellow( "Basic #{enum_name} definition: \n" )
|
8
|
+
print_hash(enum_values)
|
9
|
+
end
|
10
|
+
|
11
|
+
# call it to see which enum extensions are defined.
|
12
|
+
def describe_long
|
13
|
+
puts yellow( "\n\nEnumExt extensions:" )
|
14
|
+
|
15
|
+
puts [
|
16
|
+
describe_enum_i(false),
|
17
|
+
describe_mass_assign_enum(false),
|
18
|
+
describe_multi_enum_scopes(false),
|
19
|
+
describe_supersets(false),
|
20
|
+
describe_translations(false),
|
21
|
+
describe_humanizations(false)
|
22
|
+
].join( "\n" + "-" * 100 + "\n" )
|
23
|
+
end
|
24
|
+
|
25
|
+
def describe(short = true)
|
26
|
+
describe_basic
|
27
|
+
short ? describe_short : describe_long
|
28
|
+
end
|
29
|
+
|
30
|
+
def describe_short
|
31
|
+
enabled, disabled = enabled_features.except(:key_sample).partition{!_2.blank?}.map{ |prt| prt.map(&:shift) }
|
32
|
+
puts <<~SHORT
|
33
|
+
#{yellow("EnumExt extensions:")}
|
34
|
+
#{cyan("Enabled")}: #{enabled.join(", ")}
|
35
|
+
#{red("Disabled")}: #{disabled.join(", ")}
|
36
|
+
SHORT
|
37
|
+
|
38
|
+
print_short(:supersets)
|
39
|
+
print_short(:translations)
|
40
|
+
print_short(:humanization)
|
41
|
+
end
|
42
|
+
|
43
|
+
# --------------------------------------------------------------------
|
44
|
+
# ------------- per helpers describers -------------------------------
|
45
|
+
# --------------------------------------------------------------------
|
46
|
+
def describe_enum_i(output = true)
|
47
|
+
description = basic_helpers_usage_header(:enum_i)
|
48
|
+
description << <<~ENUM_I if enabled_features[:enum_i]
|
49
|
+
#{black("instance")}.#{cyan( enabled_features[:enum_i] )}
|
50
|
+
# output will be same as #{base_class.to_s}.#{enum_name}[:#{enabled_features[:key_sample]}]
|
51
|
+
ENUM_I
|
52
|
+
|
53
|
+
output ? puts(description) : description
|
54
|
+
end
|
55
|
+
|
56
|
+
def describe_mass_assign_enum(output = true)
|
57
|
+
description = basic_helpers_usage_header(:mass_assign_enum)
|
58
|
+
description << <<~MASS_ASSIGN if enabled_features[:mass_assign_enum]
|
59
|
+
# To assign #{enabled_features[:key_sample]} to all elements of any_scope or relation call:
|
60
|
+
#{black(base_class.to_s)}.any_scope.#{cyan( enabled_features[:mass_assign_enum] )}
|
61
|
+
MASS_ASSIGN
|
62
|
+
|
63
|
+
output ? puts(description) : description
|
64
|
+
end
|
65
|
+
|
66
|
+
def describe_multi_enum_scopes(output = true)
|
67
|
+
description = basic_helpers_usage_header(:multi_enum_scopes)
|
68
|
+
description << <<~MULTI_SCOPES if enabled_features[:multi_enum_scopes]
|
69
|
+
# Two scopes: with_#{enum_name} and without_#{enum_name} are defined
|
70
|
+
# To get elements with a given enums or supersets values call:
|
71
|
+
#{black(base_class.to_s)}.#{cyan("with_#{enum_name}")}(:#{keys.sample(2).join(", :")})
|
72
|
+
\n# To get all elements except for the ones with enums or supersets values call:
|
73
|
+
#{black(base_class.to_s)}.#{cyan("without_#{enum_name}")}(:#{keys.sample(2).join(", :")})
|
74
|
+
MULTI_SCOPES
|
75
|
+
|
76
|
+
output ? puts(description) : description
|
77
|
+
end
|
78
|
+
|
79
|
+
def describe_supersets(output = true)
|
80
|
+
description = if enabled_features[:supersets].blank?
|
81
|
+
red( "\nSupersets not used!\n" )
|
82
|
+
else
|
83
|
+
superset_method = enabled_features[:supersets].keys.first
|
84
|
+
red( "\nSupersets definitions:\n" ) << inspect_hash(enabled_features[:supersets]) << <<~SUPERSETS
|
85
|
+
|
86
|
+
# Instance methods added: #{enabled_features[:supersets].keys.join("?, ")}?
|
87
|
+
# Usage:
|
88
|
+
#{black("instance")}.#{cyan(superset_method)}?
|
89
|
+
# Will be equal true if any of: #{supersets_raw[superset_method].join("?, ")}? is true
|
90
|
+
|
91
|
+
# Class level methods/scopes added: #{enabled_features[:supersets].keys.join(", ")}
|
92
|
+
# Usage:
|
93
|
+
#{black(base_class.to_s)}.#{cyan(superset_method)}
|
94
|
+
# Will be getting all instances with #{enum_name} equals to any of: #{supersets_raw[superset_method].join(", ")}
|
95
|
+
|
96
|
+
SUPERSETS
|
97
|
+
end
|
98
|
+
|
99
|
+
output ? puts(description) : description
|
100
|
+
end
|
101
|
+
|
102
|
+
def describe_translations(output = true)
|
103
|
+
description = if enabled_features[:translations].blank?
|
104
|
+
red( "\nTranslations not used!\n" )
|
105
|
+
else
|
106
|
+
red( "\nTranslations definitions (will skip instance dependent translation)\n" ) <<
|
107
|
+
inspect_hash(enabled_features[:translations])
|
108
|
+
end
|
109
|
+
|
110
|
+
output ? puts(description) : description
|
111
|
+
end
|
112
|
+
|
113
|
+
def describe_humanizations(output = true)
|
114
|
+
description = if enabled_features[:humanization].blank?
|
115
|
+
red( "\nHumanization not used!\n" )
|
116
|
+
else
|
117
|
+
red( "\nHumanization definitions (will skip instance dependent humanization)\n" ) <<
|
118
|
+
inspect_hash(enabled_features[:humanization])
|
119
|
+
end
|
120
|
+
|
121
|
+
output ? puts(description) : description
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def enabled_features
|
127
|
+
enum_sample = keys.first
|
128
|
+
{
|
129
|
+
key_sample: enum_sample,
|
130
|
+
enum_i: base_class.instance_methods.include?("#{enum_name}_i".to_sym) && "#{enum_name}_i",
|
131
|
+
mass_assign_enum: base_class.respond_to?("#{enum_sample}!") && "#{enum_sample}!",
|
132
|
+
multi_enum_scopes: base_class.respond_to?("with_#{enum_name.to_s.pluralize}") && "with_#{enum_name.to_s.pluralize}",
|
133
|
+
supersets: supersets_raw,
|
134
|
+
translations: try(:t_options),
|
135
|
+
humanization: try(:t_options)
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
def basic_helpers_usage_header(helper_name)
|
140
|
+
enabled_features[helper_name] ? "\n#{red(helper_name)} helpers enabled, usage:\n"
|
141
|
+
: "\n#{helper_name} wasn't used\n"
|
142
|
+
end
|
143
|
+
|
144
|
+
def print_hash(hsh)
|
145
|
+
defined?(ai) ? ap(hsh) : pp(hsh)
|
146
|
+
end
|
147
|
+
|
148
|
+
def inspect_hash(hsh)
|
149
|
+
defined?(ai) ? hsh.ai : hsh.inspect
|
150
|
+
end
|
151
|
+
|
152
|
+
def print_short(feature)
|
153
|
+
if enabled_features[feature].present?
|
154
|
+
puts black("#{feature.to_s.humanize}:")
|
155
|
+
print_hash(enabled_features[feature])
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def yellow(str)
|
160
|
+
# yellow ANSI color
|
161
|
+
"\e[0;33;49m#{str}\e[0m"
|
162
|
+
end
|
163
|
+
|
164
|
+
def cyan(str)
|
165
|
+
# cyan ANSI color
|
166
|
+
"\e[0;36;49m#{str}\e[0m"
|
167
|
+
end
|
168
|
+
|
169
|
+
def red(str)
|
170
|
+
# red ANSI color
|
171
|
+
"\e[0;31;49m#{str}\e[0m"
|
172
|
+
end
|
173
|
+
|
174
|
+
def black(comment)
|
175
|
+
# bright black bold ANSI color
|
176
|
+
"\e[0;90;1;49m#{comment}\e[0m"
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module EnumExt::BasicHelpers
|
2
|
+
|
3
|
+
private
|
4
|
+
# Defines instance method a shortcut for getting integer value of an enum.
|
5
|
+
# for enum named 'status' will generate:
|
6
|
+
#
|
7
|
+
# instance.status_i
|
8
|
+
#
|
9
|
+
# Rem. Will not define helper when enum values are strings, and will print warning
|
10
|
+
def enum_i( enum_name )
|
11
|
+
return puts(<<~NOTINTEGER) if columns_hash[enum_name.to_s].type != :integer
|
12
|
+
---------------------NOTINTEGER WARNING!---------------------------
|
13
|
+
#{enum_name} is not an integer column, so enum_i helper useless and method will not be defined
|
14
|
+
NOTINTEGER
|
15
|
+
|
16
|
+
define_method "#{enum_name}_i" do
|
17
|
+
self.class.send(enum_name.to_s.pluralize)[send(enum_name)].to_i
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Defines two scopes for one for an inclusion: `WHERE enum IN( enum1, enum2 )`,
|
22
|
+
# and the second for an exclusion: `WHERE enum NOT IN( enum1, enum2 )`
|
23
|
+
# works fine with supersets and basic enums
|
24
|
+
#
|
25
|
+
# Ex:
|
26
|
+
# Request.with_statuses( :payed, :delivery_set ) # >> :payed and [:ready_for_shipment, :on_delivery, :delivered] requests
|
27
|
+
# Request.without_statuses( :payed ) # >> scope for all requests with statuses not eq to :payed
|
28
|
+
# Request.without_statuses( :payed, :in_warehouse ) # >> scope all requests with statuses not eq to :payed or :ready_for_shipment
|
29
|
+
def multi_enum_scopes(enum_name)
|
30
|
+
enum_plural = enum_name.to_s.pluralize
|
31
|
+
|
32
|
+
self.instance_eval do
|
33
|
+
# EnumExt.define_superset_to_enum_method(self, enum_plural)
|
34
|
+
# EnumExt.define_summary_methods(self, enum_plural)
|
35
|
+
|
36
|
+
# with_enums scope
|
37
|
+
scope "with_#{enum_plural}", -> (*enum_list) {
|
38
|
+
enum_list.blank? ? nil : where( enum_name => send(enum_plural).superset_to_enum(*enum_list) )
|
39
|
+
} if !respond_to?("with_#{enum_plural}") && respond_to?(:scope)
|
40
|
+
|
41
|
+
# without_enums scope
|
42
|
+
scope "without_#{enum_plural}", -> (*enum_list) {
|
43
|
+
enum_list.blank? ? nil : where.not( enum_name => send(enum_plural).superset_to_enum(*enum_list) )
|
44
|
+
} if !respond_to?("without_#{enum_plural}") && respond_to?(:scope)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Ex mass_assign_enum
|
49
|
+
#
|
50
|
+
# Used for mass assigning for collection without callbacks it creates bang methods for collections using update_all.
|
51
|
+
# it's often case when you need bulk update without callbacks, so it's gets frustrating to repeat:
|
52
|
+
# some_scope.update_all(status: :new_status, update_at: Time.now)
|
53
|
+
#
|
54
|
+
# If you need callbacks you can do like this: some_scope.each(&:new_stat!) but if you don't need callbacks
|
55
|
+
# and you have lots of records to change at once you need update_all
|
56
|
+
#
|
57
|
+
# mass_assign_enum( :status )
|
58
|
+
#
|
59
|
+
# class methods:
|
60
|
+
# in_cart! paid! in_warehouse! and so
|
61
|
+
#
|
62
|
+
# Console:
|
63
|
+
# request1.in_cart!
|
64
|
+
# request2.waiting_for_payment!
|
65
|
+
# Request.with_statuses( :in_cart, :waiting_for_payment ).payed!
|
66
|
+
# request1.paid? # >> true
|
67
|
+
# request2.paid? # >> true
|
68
|
+
# request1.updated_at # >> Time.now
|
69
|
+
#
|
70
|
+
# order.requests.paid.all?(&:paid?) # >> true
|
71
|
+
# order.requests.paid.delivered!
|
72
|
+
# order.requests.map(&:status).uniq #>> [:delivered]
|
73
|
+
|
74
|
+
def mass_assign_enum( *enums_names )
|
75
|
+
enums_names.each do |enum_name|
|
76
|
+
enum_vals = self.send( enum_name.to_s.pluralize )
|
77
|
+
|
78
|
+
enum_vals.keys.each do |enum_el|
|
79
|
+
define_singleton_method( "#{enum_el}!" ) do
|
80
|
+
self.update_all( {enum_name => enum_vals[enum_el]}.merge( self.column_names.include?('updated_at') ? {updated_at: Time.now} : {} ))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
alias_method :enum_mass_assign, :mass_assign_enum
|
86
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# This is an wrapper class for a basic enum.
|
2
|
+
# Since enum values will be freezed right after the definition, we can't enrich enum directly functionality
|
3
|
+
# We can only wrap it with our own object and delegate enum base base functionality internally
|
4
|
+
class EnumExt::EnumWrapper
|
5
|
+
include EnumExt::Annotated
|
6
|
+
|
7
|
+
# supersets is storing exact definitions, if you need a raw mapping use class.statuses.superset_statuses
|
8
|
+
attr_reader :enum_values, :supersets, :supersets_raw, :t_options_raw, :localizations, :base_class, :enum_name
|
9
|
+
|
10
|
+
delegate_missing_to :enum_values
|
11
|
+
delegate :inspect, to: :enum_values
|
12
|
+
|
13
|
+
def initialize(enum_values, base_class, enum_name)
|
14
|
+
@enum_values = enum_values
|
15
|
+
@supersets = ActiveSupport::HashWithIndifferentAccess.new
|
16
|
+
@supersets_raw = ActiveSupport::HashWithIndifferentAccess.new
|
17
|
+
|
18
|
+
@t_options_raw = ActiveSupport::HashWithIndifferentAccess.new
|
19
|
+
@localizations = ActiveSupport::HashWithIndifferentAccess.new
|
20
|
+
|
21
|
+
@base_class = base_class
|
22
|
+
@enum_name = enum_name
|
23
|
+
end
|
24
|
+
|
25
|
+
# ext_sets_to_kinds( :ready_for_shipment, :delivery_set ) -->
|
26
|
+
# [:ready_for_shipment, :on_delivery, :delivered]
|
27
|
+
def superset_to_enum( *enum_or_sets )
|
28
|
+
return [] if enum_or_sets.blank?
|
29
|
+
enum_or_sets_strs = enum_or_sets.map(&:to_s)
|
30
|
+
|
31
|
+
next_level_deeper = supersets.slice( *enum_or_sets_strs ).values.flatten
|
32
|
+
(enum_or_sets_strs & enum_values.keys | send(:superset_to_enum, *next_level_deeper)).uniq
|
33
|
+
end
|
34
|
+
|
35
|
+
def all
|
36
|
+
{
|
37
|
+
**enum_values,
|
38
|
+
supersets: {
|
39
|
+
**send(:supersets_raw)
|
40
|
+
}
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def t_options_i
|
45
|
+
evaluate_localizations_to_i(localizations)
|
46
|
+
end
|
47
|
+
|
48
|
+
def t_options
|
49
|
+
evaluate_localizations(localizations)
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method :t, :localizations
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def evaluate_localizations(t_enum_set)
|
57
|
+
# { kind => kind_translator, kind2 => kind2_translator } --> [[kind_translator, kind], [kind2_translator, kind2]]
|
58
|
+
t_enum_set.invert.to_a.map do | translator, enum_key |
|
59
|
+
# since all procs in t_enum are evaluated in context of a record than it's not always possible to create select options automatically
|
60
|
+
translation = if translator.respond_to?(:call)
|
61
|
+
if translator.arity < 1
|
62
|
+
translator.call rescue "Cannot create option for #{enum_key} ( proc fails to evaluate )"
|
63
|
+
else
|
64
|
+
"Cannot create option for #{enum_key} because of a lambda"
|
65
|
+
end
|
66
|
+
end || translator
|
67
|
+
[translation, enum_key]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def evaluate_localizations_to_i(t_enum_set)
|
72
|
+
# { kind => kind_translation, kind2 => kind2_translation } --> [[kind_translation, kind_i], [kind2_translation, kind2_i]]
|
73
|
+
evaluate_localizations(t_enum_set).map do | translation, name |
|
74
|
+
[ translation, self[name] ]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module EnumExt::HumanizeHelpers
|
2
|
+
|
3
|
+
# if app doesn't need internationalization, it may use humanize_enum to make enum user friendly
|
4
|
+
#
|
5
|
+
# class Request
|
6
|
+
# humanize_enum :status, {
|
7
|
+
# #locale dependent example with pluralization and lambda:
|
8
|
+
# payed: -> (t_self) { I18n.t("request.status.payed", count: t_self.sum ) }
|
9
|
+
#
|
10
|
+
# #locale dependent example with pluralization and proc:
|
11
|
+
# payed: Proc.new{ I18n.t("request.status.payed", count: self.sum ) }
|
12
|
+
#
|
13
|
+
# #locale independent:
|
14
|
+
# ready_for_shipment: "Ready to go!"
|
15
|
+
# }
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Could be called multiple times, all humanization definitions will be merged under the hood:
|
19
|
+
# humanize_enum :status, {
|
20
|
+
# payed: I18n.t("scope.#{status}")
|
21
|
+
# }
|
22
|
+
# humanize_enum :status, {
|
23
|
+
# billed: I18n.t("scope.#{status}")
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
#
|
27
|
+
# Example with block:
|
28
|
+
#
|
29
|
+
# humanize_enum :status do
|
30
|
+
# I18n.t("scope.#{status}")
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# in views select:
|
34
|
+
# f.select :status, Request.t_statuses_options
|
35
|
+
#
|
36
|
+
# in select in Active Admin filter
|
37
|
+
# collection: Request.statuses.t_options_i
|
38
|
+
#
|
39
|
+
# Rem: select options breaks when using lambda() with params
|
40
|
+
#
|
41
|
+
# Console:
|
42
|
+
# request.sum = 3
|
43
|
+
# request.payed!
|
44
|
+
# request.status # >> payed
|
45
|
+
# request.t_status # >> "Payed 3 dollars"
|
46
|
+
# Request.t_statuses # >> { in_cart: -> { I18n.t("request.status.in_cart") }, .... }
|
47
|
+
def humanize_enum( *args, &block )
|
48
|
+
enum_name = args.shift
|
49
|
+
localization_definitions = args.pop
|
50
|
+
enum_plural = enum_name.to_s.pluralize
|
51
|
+
|
52
|
+
self.instance_eval do
|
53
|
+
# instance.t_enum
|
54
|
+
define_method "t_#{enum_name}" do
|
55
|
+
t = block || self.class.send(enum_plural).localizations[send(enum_name)]
|
56
|
+
if t.try(:lambda?)
|
57
|
+
t.try(:arity) == 1 && t.call( self ) || t.try(:call)
|
58
|
+
elsif t.is_a?(Proc)
|
59
|
+
instance_eval(&t)
|
60
|
+
else
|
61
|
+
t
|
62
|
+
end.to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
# if localization is absent than block must be given
|
66
|
+
send(enum_plural).localizations.merge!(
|
67
|
+
localization_definitions.try(:with_indifferent_access) ||
|
68
|
+
send(enum_plural).map do |k, _v|
|
69
|
+
# little bit hackerish: instantiate object just with enum setup and then call its t_.. method which
|
70
|
+
[k, Proc.new{ self.new({ enum_name => k }).send("t_#{enum_name}") }]
|
71
|
+
end.to_h.with_indifferent_access
|
72
|
+
)
|
73
|
+
|
74
|
+
# hm.. lost myself here, why did I implement this method
|
75
|
+
define_method "t_#{enum_name}=" do |new_val|
|
76
|
+
send("#{enum_name}=", new_val)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
alias localize_enum humanize_enum
|
82
|
+
|
83
|
+
# Simple way to translate enum.
|
84
|
+
# It use either given scope as second argument, or generated activerecord.attributes.model_name_underscore.enum_name
|
85
|
+
# If block is given than no scopes are taken in consider
|
86
|
+
def translate_enum( *args, &block )
|
87
|
+
enum_name = args.shift
|
88
|
+
enum_plural = enum_name.to_s.pluralize
|
89
|
+
t_scope = args.pop || "activerecord.attributes.#{self.name.underscore}.#{enum_plural}"
|
90
|
+
|
91
|
+
if block_given?
|
92
|
+
humanize_enum( enum_name, &block )
|
93
|
+
else
|
94
|
+
humanize_enum( enum_name, send(enum_plural).keys.map{|en| [ en, Proc.new{ I18n.t("#{t_scope}.#{en}") }] }.to_h )
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# human_attribute_name is redefined for automation like this:
|
99
|
+
# p #{object.class.human_attribute_name( attr_name )}:
|
100
|
+
# p object.send(attr_name)
|
101
|
+
def human_attribute_name( name, options = {} )
|
102
|
+
# if name starts from t_ and there is a column with the last part then ...
|
103
|
+
name[0..1] == 't_' && column_names.include?(name[2..-1]) ? super( name[2..-1], options ) : super( name, options )
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# t_... methods for supersets will just slice
|
108
|
+
# original enum t_.. methods output and return only superset related values from it
|
109
|
+
#
|
110
|
+
def self.define_superset_humanization_helpers(base_class, superset_name, enum_name)
|
111
|
+
enum_plural = enum_name.to_s.pluralize
|
112
|
+
|
113
|
+
base_class.send(enum_plural).define_singleton_method( "t_#{superset_name}_options" ) do
|
114
|
+
result = evaluate_localizations(send("t_#{superset_name}"))
|
115
|
+
return result unless result.blank?
|
116
|
+
|
117
|
+
[["Enum translations call missed. Did you forget to call translate #{enum_name}"]*2]
|
118
|
+
end
|
119
|
+
|
120
|
+
# enums.t_options_i
|
121
|
+
base_class.send(enum_plural).define_singleton_method( "t_#{superset_name}_options_i" ) do
|
122
|
+
result = evaluate_localizations_to_i( send("t_#{superset_name}") )
|
123
|
+
return result unless result.to_h.values.all?(&:blank?)
|
124
|
+
|
125
|
+
[["Enum translations are missing. Did you forget to translate #{enum_name}"]*2]
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# enums.t_superset ( translations or humanizations subset for a given set )
|
130
|
+
base_class.send(enum_plural).define_singleton_method( "t_#{superset_name}" ) do
|
131
|
+
return [(["Enum translations are missing. Did you forget to translate #{enum_name}"]*2)].to_h if localizations.blank?
|
132
|
+
|
133
|
+
base_class.send(enum_plural).localizations.slice( *base_class.send(enum_plural).send(superset_name) )
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# # t_... - are translation dependent methods
|
140
|
+
# # This one is a narrow case helpers just a quick subset of t_ enums options for a set
|
141
|
+
# # class.t_enums_options
|
142
|
+
# enum_obj.define_singleton_method( "t_#{superset_name}_options" ) do
|
143
|
+
# return [["Enum translations call missed. Did you forget to call translate #{enum_name}"]*2] unless respond_to?( "t_#{enum_plural}_options_raw" )
|
144
|
+
#
|
145
|
+
# send("t_#{enum_plural}_options_raw", send("t_#{superset_name}_#{enum_plural}") )
|
146
|
+
# end
|
147
|
+
#
|
148
|
+
# # class.t_enums_options_i
|
149
|
+
# enum_obj.define_singleton_method( "t_#{superset_name}_options_i" ) do
|
150
|
+
# return [["Enum translations call missed. Did you forget to call translate #{enum_name}"]*2] unless respond_to?( "t_#{enum_plural}_options_raw_i" )
|
151
|
+
#
|
152
|
+
# send("t_#{enum_plural}_options_raw_i", send("t_#{superset_name}_#{enum_plural}") )
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# enum_obj.define_singleton_method( "t_#{superset_name}_options" ) do
|
156
|
+
# return [["Enum translations call missed. Did you forget to call translate #{enum_name}"]*2] if t_options_raw.blank?
|
157
|
+
#
|
158
|
+
# t_options_raw["t_#{superset_name}"]
|
159
|
+
# end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module EnumExt::SupersetHelpers
|
2
|
+
# enum_supersets
|
3
|
+
# **Use-case** whenever you need superset of enums to behave like a super enum.
|
4
|
+
#
|
5
|
+
# You can do this with method **enum_supersets** it creates:
|
6
|
+
# - scopes for subsets,
|
7
|
+
# - instance methods with `?`
|
8
|
+
#
|
9
|
+
# For example:
|
10
|
+
# enum status: [:in_cart, :waiting_for_payment, :paid, :packing, :ready_for_shipment, :on_delivery, :delivered],
|
11
|
+
# ext: [enum_supersets: {
|
12
|
+
# around_delivery: [:ready_for_shipment, :on_delivery], # for shipping department for example
|
13
|
+
# in_warehouse: [:packing, :ready_for_shipment], # this scope is just for superposition example below
|
14
|
+
# sold: [:paid, :around_delivery, :in_warehouse, :delivered] # also you can define any superposition of already defined supersets or enum values
|
15
|
+
# }]
|
16
|
+
#
|
17
|
+
# # supersets will be stored inside enum wrapper object, and can be de-referenced to basic enum values
|
18
|
+
# # using wrapper defined methods: "superset_enum_plural", i.e. statuses.sold_statuses -> [:paid, :packing, :ready_for_shipment, :on_delivery, :delivered]
|
19
|
+
# # so new supersets could be defined using Array operations against newly defined methods
|
20
|
+
# enum_ext :status, enum_supersets: {
|
21
|
+
# outside_warehouse: ( statuses.around_delivery - statuses.in_warehouse ) #... any other array operations like &, + and so can be used
|
22
|
+
# }
|
23
|
+
#
|
24
|
+
# it will generate:
|
25
|
+
#
|
26
|
+
# instance:
|
27
|
+
# - methods: around_delivery?, in_warehouse?
|
28
|
+
#
|
29
|
+
# class:
|
30
|
+
# - named scopes: around_delivery, in_warehouse
|
31
|
+
#
|
32
|
+
# enum methods:
|
33
|
+
# - Class.statuses.supersets -- will output superset definition hash
|
34
|
+
# - Class.statuses.supersets_raw -- will output superset decompositions to basic enum types hash
|
35
|
+
#
|
36
|
+
# - Class.statuses.around_delivery (=[:ready_for_shipment, :on_delivery, :delivered] ), in_warehouse_statuses
|
37
|
+
# - around_delivery_statuses_i (= [3,4,5]), in_warehouse_statuses_i (=[3])
|
38
|
+
#
|
39
|
+
# translation helpers grouped for superset ( started with t_... ):
|
40
|
+
# - Class.statuses.t_around_delivery_options (= [['translation or humanization', :ready_for_shipment] ...] ) for select inputs purposes
|
41
|
+
# - Class.statuses.t_around_delivery_options_i (= [['translation or humanization', 3] ...]) same as above but with integer as value ( for example to use in Active admin filters )
|
42
|
+
#
|
43
|
+
# In console:
|
44
|
+
# request.on_delivery!
|
45
|
+
# request.around_delivery? # >> true
|
46
|
+
#
|
47
|
+
# Request.around_delivery.exists?(request) # >> true
|
48
|
+
# Request.in_warehouse.exists?(request) # >> false
|
49
|
+
#
|
50
|
+
# Request.statuses.around_delivery # >> ["ready_for_shipment", "on_delivery", "delivered"]
|
51
|
+
|
52
|
+
private
|
53
|
+
def enum_supersets( enum_name, options = {} )
|
54
|
+
enum_plural = enum_name.to_s.pluralize
|
55
|
+
|
56
|
+
self.instance_eval do
|
57
|
+
send(enum_plural).supersets.merge!( options.transform_values{ _1.try(:map, &:to_s) || _1.to_s } )
|
58
|
+
|
59
|
+
options.each do |superset_name, enum_vals|
|
60
|
+
raise "Can't define superset with name: #{superset_name}, #{enum_plural} already has such method!" if send(enum_plural).respond_to?(superset_name)
|
61
|
+
|
62
|
+
send(enum_plural).supersets_raw[superset_name] = send(enum_plural).superset_to_enum(*enum_vals)
|
63
|
+
|
64
|
+
# class.enum_wrapper.superset
|
65
|
+
send(enum_plural).define_singleton_method(superset_name) { base_class.send(enum_plural).superset_to_enum(*enum_vals) }
|
66
|
+
|
67
|
+
# superset_name scope
|
68
|
+
scope superset_name, -> { where( enum_name => send(enum_plural).send(superset_name) ) } if respond_to?(:scope)
|
69
|
+
|
70
|
+
# instance.superset_name?
|
71
|
+
define_method "#{superset_name}?" do
|
72
|
+
send(enum_name) && self.class.send(enum_plural).send(superset_name).include?( send(enum_name) )
|
73
|
+
end
|
74
|
+
|
75
|
+
EnumExt::HumanizeHelpers.define_superset_humanization_helpers( self, superset_name, enum_name )
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
alias_method :ext_enum_sets, :enum_supersets
|
81
|
+
end
|
82
|
+
|
83
|
+
|
data/lib/enum_ext/version.rb
CHANGED