Module: HasCustomFields
- Extended by:
- ActiveSupport::Concern
- Defined in:
- app/models/concerns/has_custom_fields.rb
Defined Under Namespace
Modules: Helpers Classes: FieldDescriptor, NotPreloadedError, PreloadedProxy
Constant Summary collapse
- DEFAULT_FIELD_DESCRIPTOR =
FieldDescriptor.new(:string, 10_000_000)
- CUSTOM_FIELDS_MAX_ITEMS =
100
Instance Attribute Summary collapse
-
#preloaded_custom_fields ⇒ Object
readonly
Returns the value of attribute preloaded_custom_fields.
Instance Method Summary collapse
- #clear_custom_fields ⇒ Object
-
#create_singular(name, value, field_type = nil) ⇒ Object
We support unique indexes on certain fields.
- #custom_field_preloaded?(name) ⇒ Boolean
- #custom_fields ⇒ Object
- #custom_fields=(data) ⇒ Object
- #custom_fields_clean? ⇒ Boolean
- #custom_fields_fk ⇒ Object
- #custom_fields_preloaded? ⇒ Boolean
- #on_custom_fields_change ⇒ Object
- #reload(options = nil) ⇒ Object
- #save_custom_fields(force = false, run_validations: true) ⇒ Object
- #set_preloaded_custom_fields(custom_fields) ⇒ Object
-
#upsert_custom_fields(fields) ⇒ Object
‘upsert_custom_fields` will only insert/update existing fields, and will not delete anything.
Instance Attribute Details
#preloaded_custom_fields ⇒ Object (readonly)
Returns the value of attribute preloaded_custom_fields.
192 193 194 |
# File 'app/models/concerns/has_custom_fields.rb', line 192 def preloaded_custom_fields @preloaded_custom_fields end |
Instance Method Details
#clear_custom_fields ⇒ Object
216 217 218 219 |
# File 'app/models/concerns/has_custom_fields.rb', line 216 def clear_custom_fields @custom_fields = nil @custom_fields_orig = nil end |
#create_singular(name, value, field_type = nil) ⇒ Object
We support unique indexes on certain fields. In the event two concurrent processes attempt to update the same custom field we should catch the error and perform an update instead.
334 335 336 337 338 339 340 341 342 343 344 |
# File 'app/models/concerns/has_custom_fields.rb', line 334 def create_singular(name, value, field_type = nil) write_value = value.is_a?(Hash) || field_type == :json ? value.to_json : value write_value = "t" if write_value.is_a?(TrueClass) write_value = "f" if write_value.is_a?(FalseClass) row_count = DB.exec(<<~SQL, name: name, value: write_value, id: id, now: Time.zone.now) INSERT INTO #{_custom_fields.table_name} (#{custom_fields_fk}, name, value, created_at, updated_at) VALUES (:id, :name, :value, :now, :now) ON CONFLICT DO NOTHING SQL _custom_fields.where(name: name).update_all(value: write_value) if row_count == 0 end |
#custom_field_preloaded?(name) ⇒ Boolean
212 213 214 |
# File 'app/models/concerns/has_custom_fields.rb', line 212 def custom_field_preloaded?(name) @preloaded_custom_fields && @preloaded_custom_fields.key?(name) end |
#custom_fields ⇒ Object
250 251 252 253 254 255 256 |
# File 'app/models/concerns/has_custom_fields.rb', line 250 def custom_fields if @preloaded_custom_fields return @preloaded_proxy ||= PreloadedProxy.new(@preloaded_custom_fields, self.class.to_s) end @custom_fields ||= refresh_custom_fields_from_db.dup end |
#custom_fields=(data) ⇒ Object
258 259 260 |
# File 'app/models/concerns/has_custom_fields.rb', line 258 def custom_fields=(data) custom_fields.replace(data) end |
#custom_fields_clean? ⇒ Boolean
262 263 264 265 |
# File 'app/models/concerns/has_custom_fields.rb', line 262 def custom_fields_clean? # Check whether the cached version has been changed on this model !@custom_fields || @custom_fields_orig == @custom_fields end |
#custom_fields_fk ⇒ Object
194 195 196 |
# File 'app/models/concerns/has_custom_fields.rb', line 194 def custom_fields_fk @custom_fields_fk ||= "#{_custom_fields.reflect_on_all_associations(:belongs_to)[0].name}_id" end |
#custom_fields_preloaded? ⇒ Boolean
208 209 210 |
# File 'app/models/concerns/has_custom_fields.rb', line 208 def custom_fields_preloaded? !!@preloaded_custom_fields end |
#on_custom_fields_change ⇒ Object
203 204 205 206 |
# File 'app/models/concerns/has_custom_fields.rb', line 203 def on_custom_fields_change # Callback when custom fields have changed # Override in model end |
#reload(options = nil) ⇒ Object
198 199 200 201 |
# File 'app/models/concerns/has_custom_fields.rb', line 198 def reload( = nil) clear_custom_fields super end |
#save_custom_fields(force = false, run_validations: true) ⇒ Object
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'app/models/concerns/has_custom_fields.rb', line 282 def save_custom_fields(force = false, run_validations: true) if run_validations custom_fields_max_items custom_fields_value_length raise_validation_error unless errors.empty? end if force || !custom_fields_clean? ActiveRecord::Base.transaction do dup = @custom_fields.dup.with_indifferent_access fields_by_key = _custom_fields.reload.group_by(&:name) (dup.keys.to_set + fields_by_key.keys.to_set).each do |key| fields = fields_by_key[key] || [] value = dup[key] descriptor = self.class.get_custom_field_descriptor(key) field_type = descriptor.type if descriptor.array_type? || (field_type != :json && Array === value) value = Array(value || []) value.compact! sub_type = field_type[0] value.map! { |v| descriptor.serialize(v) } unless value == fields.map(&:value) fields.each(&:destroy!) value.each { |subv| _custom_fields.create!(name: key, value: subv) } end else if value.nil? fields.each(&:destroy!) else value = descriptor.serialize(value) field = fields.find { |f| f.value == value } fields.select { |f| f != field }.each(&:destroy!) create_singular(key, value) if !field end end end end on_custom_fields_change refresh_custom_fields_from_db end end |
#set_preloaded_custom_fields(custom_fields) ⇒ Object
241 242 243 244 245 246 247 248 |
# File 'app/models/concerns/has_custom_fields.rb', line 241 def set_preloaded_custom_fields(custom_fields) @preloaded_custom_fields = custom_fields # we have to clear this otherwise the fields are cached inside the # already existing proxy and no new ones are added, so when we check # for custom_fields[KEY] an error is likely to occur @preloaded_proxy = nil end |
#upsert_custom_fields(fields) ⇒ Object
‘upsert_custom_fields` will only insert/update existing fields, and will not delete anything. It is safer under concurrency and is recommended when you just want to attach fields to things without maintaining a specific set of fields.
271 272 273 274 275 276 277 278 279 280 |
# File 'app/models/concerns/has_custom_fields.rb', line 271 def upsert_custom_fields(fields) fields.each do |k, v| row_count = _custom_fields.where(name: k).update_all(value: v, updated_at: Time.now) _custom_fields.create!(name: k, value: v) if row_count == 0 custom_fields[k.to_s] = v # We normalize custom_fields as strings end on_custom_fields_change end |