9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
# File 'lib/symbolic_enum.rb', line 9
def symbolic_enum(params)
raise ArgumentError.new("argument has to be a Hash of field and mapping of unique Symbols to numbers, with optional configuration params") unless params.is_a?(Hash) && params.keys.count <= 2 && params.keys.count >= 1 && params.keys.first.is_a?(Symbol) && params.values.first.is_a?(Hash)
field = params.keys.first
mapping = params[field]
options = params.reject{ |k,v| k == field }
raise ArgumentError.new("argument has to be a Hash of field and mapping of unique Symbols to numbers, with optional configuration params") unless mapping.keys.count == mapping.keys.uniq.count && mapping.values.count == mapping.values.uniq.count && mapping.keys.map(&:class).uniq == [Symbol] && (mapping.values.map(&:class).uniq == [Integer] || mapping.values.map(&:class).uniq == [Fixnum])
options.each_pair do |key, value|
case key
when :array
raise ArgumentError.new("'array' option can be only true/false") unless [true, false].include?(value)
when :disable_scopes
raise ArgumentError.new("'disable_scopes' option can be only true/false") unless [true, false].include?(value)
when :disable_setters
raise ArgumentError.new("'disable_setters' option can be only true/false") unless [true, false].include?(value)
else
raise ArgumentError.new("'#{ key }' is not a valid option")
end
end
is_array = options[:array]
disable_scopes = options[:disable_scopes]
disable_setters = options[:disable_setters]
mapping.keys.each do |enum_name|
raise ArgumentError.new("'#{enum_name}' clashes with existing methods") if self.instance_methods.include?(:"#{enum_name}!") unless disable_setters
raise ArgumentError.new("'#{enum_name}' clashes with existing methods") if self.instance_methods.include?(:"#{enum_name}?")
raise ArgumentError.new("'#{enum_name}' clashes with existing methods") if self.singleton_methods.include?(enum_name) unless disable_scopes
end
symbolic_enums = {}
begin
symbolic_enums = self.class_variable_get(:@@symbolic_enums)
rescue NameError
end
symbolic_enums ||= {}
symbolic_enums = symbolic_enums.merge(params)
self.class_variable_set(:@@symbolic_enums, symbolic_enums)
define_singleton_method(:symbolic_enums) do
self.class_variable_get(:@@symbolic_enums || {})
end
define_singleton_method("#{ field.to_s.pluralize }") do
mapping
end
reverse_mapping = mapping.map{|v| [v[1],v[0]]}.to_h
if is_array
define_method(field) do
return nil if self[field].nil?
return self[field].map{ |v| reverse_mapping[v] }
end
else
define_method(field) do
reverse_mapping[self[field]]
end
end
if is_array
define_method("#{ field }=") do |value|
raise ArgumentError.new("can only assign a valid array of enums") unless value.nil? || (value.is_a?(Array) && value.map(&:class).to_set.subset?([String, Symbol].to_set) && value.map(&:to_sym).to_set.subset?(mapping.keys.to_set))
self[field] = value.nil? ? nil : value.map{|s| mapping[s.to_sym] }
end
else
define_method("#{ field }=") do |value|
raise ArgumentError.new("can only assign a valid enum") unless value.nil? || ((value.is_a?(Symbol) || value.is_a?(String)) && mapping.keys.include?(value.to_sym))
self[field] = value.nil? ? nil : mapping[value.to_sym]
end
end
mapping.each_pair do |state_name, state_value|
unless disable_scopes
scope state_name, -> { where(field => state_value) }
end
define_method("#{ state_name }?".to_sym) do
self[field] == state_value
end
unless disable_setters
define_method("#{ state_name }!".to_sym) do
self.update_attributes!(field => state_value)
end
end
end
end
|