forked from ManageIQ/manageiq
-
Notifications
You must be signed in to change notification settings - Fork 1
/
custom_attribute_mixin.rb
131 lines (102 loc) · 5.04 KB
/
custom_attribute_mixin.rb
1
2
3
4
5
6
7
8
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
module CustomAttributeMixin
extend ActiveSupport::Concern
CUSTOM_ATTRIBUTES_PREFIX = "virtual_custom_attribute_".freeze
SECTION_SEPARATOR = ":SECTION:".freeze
DEFAULT_SECTION_NAME = 'Custom Attribute'.freeze
CUSTOM_ATTRIBUTE_INVALID_NAME_WARNING = "A custom attribute name must begin with a letter (a-z, but also letters with diacritical marks and non-Latin letters) or an underscore (_). Subsequent characters can be letters, underscores, digits (0-9), or dollar signs ($)".freeze
CUSTOM_ATTRIBUTE_VALID_NAME_REGEXP = /\A[\p{Alpha}_][\p{Alpha}_\d$]*\z/
included do
has_many :custom_attributes, :as => :resource, :dependent => :destroy
has_many :miq_custom_attributes, -> { where(:source => 'EVM') }, # rubocop:disable Rails/HasManyOrHasOneDependent
:class_name => "CustomAttribute",
:as => :resource,
:inverse_of => :resource
# This is a set of helper getter and setter methods to support the transition
# between "custom_*" fields in the model and using the custom_attributes table.
(1..9).each do |custom_id|
custom_str = "custom_#{custom_id}"
getter = custom_str.to_sym
setter = "#{custom_str}=".to_sym
define_method(getter) do
miq_custom_get(custom_str)
end
virtual_column getter, :type => :string # uses not set since miq_custom_get re-queries
define_method(setter) do |value|
miq_custom_set(custom_str, value)
end
end
def self.custom_keys
custom_attr_scope = CustomAttribute.where(:resource_type => base_class.name).where.not(:name => nil).distinct.pluck(:name, :section)
custom_attr_scope.map do |x|
"#{x[0]}#{x[1] ? SECTION_SEPARATOR + x[1] : ''}"
end
end
def self.load_custom_attributes_for(cols)
custom_attributes = CustomAttributeMixin.select_virtual_custom_attributes(cols)
custom_attributes.each { |custom_attribute| add_custom_attribute(custom_attribute) }
end
def self.invalid_custom_attribute_message(attribute)
"Invalid custom attribute: '#{attribute}'. #{CUSTOM_ATTRIBUTE_INVALID_NAME_WARNING}"
end
def self.add_custom_attribute(custom_attribute)
return if respond_to?(custom_attribute)
ActiveSupport::Deprecation.warn(invalid_custom_attribute_message(custom_attribute)) unless CUSTOM_ATTRIBUTE_VALID_NAME_REGEXP.match?(custom_attribute.to_s)
ca_sym = custom_attribute.to_sym
without_prefix = custom_attribute.sub(CUSTOM_ATTRIBUTES_PREFIX, "")
name_val, section = without_prefix.split(SECTION_SEPARATOR)
ca_arel = custom_attribute_arel(name_val, section)
virtual_column(ca_sym, :type => :string, :uses => :custom_attributes, :arel => ca_arel)
define_method(ca_sym) do
return self[custom_attribute] if has_attribute?(custom_attribute)
where_args = {}
where_args[:name] = name_val
where_args[:section] = section if section
custom_attributes.find_by(where_args).try(:value)
end
end
def self.custom_attribute_arel(name_val, section)
lambda do |t|
ca_field = CustomAttribute.arel_table
field_where = ca_field[:resource_id].eq(t[:id])
field_where = field_where.and(ca_field[:resource_type].eq(base_class.name))
field_where = field_where.and(ca_field[:name].eq(name_val))
field_where = field_where.and(ca_field[:section].eq(section)) if section
# Because there is a `find_by` in the `define_method` above, we are
# using a `take(1)` here as well, since a limit is assumed in each.
# Without it, there can be some invalid queries if more than one result
# is returned.
t.grouping(ca_field.project(:value).where(field_where).take(1))
end
end
end
def self.to_human(column)
col_name, section = column.gsub(CustomAttributeMixin::CUSTOM_ATTRIBUTES_PREFIX, '').split(SECTION_SEPARATOR)
_("%{section}: %{custom_key}") % {:custom_key => col_name, :section => section.try(:titleize) || DEFAULT_SECTION_NAME}
end
def self.column_name(custom_key)
return if custom_key.nil?
CustomAttributeMixin::CUSTOM_ATTRIBUTES_PREFIX + custom_key
end
def self.select_virtual_custom_attributes(cols)
cols.nil? ? [] : cols.select { |x| x.start_with?(CUSTOM_ATTRIBUTES_PREFIX) }
end
def miq_custom_keys
miq_custom_attributes.pluck(:name)
end
def miq_custom_get(key)
miq_custom_attributes.find_by(:name => key.to_s).try(:value)
end
def miq_custom_set(key, value)
return miq_custom_delete(key) if value.blank?
ActiveSupport::Deprecation.warn(self.class.invalid_custom_attribute_message(key)) unless self.class::CUSTOM_ATTRIBUTE_VALID_NAME_REGEXP.match?(key.to_s)
record = miq_custom_attributes.find_by(:name => key.to_s)
if record.nil?
miq_custom_attributes.create(:name => key.to_s, :value => value)
else
record.update(:value => value)
end
end
def miq_custom_delete(key)
miq_custom_attributes.find_by(:name => key.to_s).try(:delete)
end
end