class SBF::Client::BaseEntity
Constants
- ELSE
Attributes
Public Class Methods
Class
method which retrieves the
# File lib/stbaldricks/entities/lib/base.rb, line 218 def self.allow_instantiation? !@disallow_instantiation end
# File lib/stbaldricks/entities/lib/base.rb, line 247 def self.attr_accessor(*vars) defined_attributes.merge(vars) super(*vars) vars.each do |attribute| define_attribute_methods attribute define_changing_attr_methods attribute, false, true end add_boolean_methods(vars, true) end
Override for the ruby built-in attribute accessors. Creates a list of attributes that can be accessed at will and adds some helper methods.
# File lib/stbaldricks/entities/lib/base.rb, line 230 def self.attr_reader(*vars) defined_attributes.merge(vars) super(*vars) add_boolean_methods(vars) end
# File lib/stbaldricks/entities/lib/base.rb, line 236 def self.attr_writer(*vars) defined_attributes.merge(vars) vars.each do |attribute| define_attribute_methods attribute define_changing_attr_methods attribute end add_boolean_methods(vars, true) end
# File lib/stbaldricks/entities/lib/base.rb, line 362 def self.collection_attributes @collection_attributes ||= Set.new end
# File lib/stbaldricks/entities/lib/base.rb, line 350 def self.defined_attributes @defined_attributes ||= Set.new end
# File lib/stbaldricks/entities/lib/base.rb, line 299 def self.entity_attr_accessor(attribute, full_class, partial_class = nil, optional = false) mapping_for_single_class = [[ELSE, full_class, partial_class]] multitype_attr_accessor(attribute, mapping_for_single_class, optional) end
Entity
attr accessors are simpler to define/easier to read but they really just create a type mapping for the given inputs and then function the same as the multitype attribute accessors do.
# File lib/stbaldricks/entities/lib/base.rb, line 289 def self.entity_attr_reader(attribute, full_class, partial_class = nil, optional = false) mapping_for_single_class = [[ELSE, full_class, partial_class]] multitype_attr_reader(attribute, mapping_for_single_class, optional) end
# File lib/stbaldricks/entities/lib/base.rb, line 294 def self.entity_attr_writer(attribute, full_class, partial_class = nil, optional = false) mapping_for_single_class = [[ELSE, full_class, partial_class]] multitype_attr_writer(attribute, mapping_for_single_class, optional) end
# File lib/stbaldricks/entities/lib/base.rb, line 358 def self.entity_attributes @entity_attributes ||= Set.new end
# File lib/stbaldricks/entities/lib/base.rb, line 344 def self.entity_collection_attr_accessor(attribute, full_class, partial_class = nil, optional = false) mapping_for_single_class = [[ELSE, full_class, partial_class]] multitype_collection_attr_accessor(attribute, mapping_for_single_class, optional) end
Entity
attr accessors are simpler to define/easier to read but they really just create a type mapping for the given inputs and then function the same as the multitype attribute accessors do.
# File lib/stbaldricks/entities/lib/base.rb, line 334 def self.entity_collection_attr_reader(attribute, full_class, partial_class = nil, optional = false) mapping_for_single_class = [[ELSE, full_class, partial_class]] multitype_collection_attr_reader(attribute, mapping_for_single_class, optional) end
# File lib/stbaldricks/entities/lib/base.rb, line 339 def self.entity_collection_attr_writer(attribute, full_class, partial_class = nil, optional = false) mapping_for_single_class = [[ELSE, full_class, partial_class]] multitype_collection_attr_writer(attribute, mapping_for_single_class, optional) end
# File lib/stbaldricks/entities/lib/base.rb, line 585 def self.inherited(base) # Add all of our attributes to the class that is inheriting us. base.defined_attributes.merge(defined_attributes) unless defined_attributes.empty? base.optional_attributes.merge(optional_attributes) unless optional_attributes.empty? base.entity_attributes.merge(entity_attributes) unless entity_attributes.empty? end
# File lib/stbaldricks/entities/lib/base.rb, line 278 def self.multitype_attr_accessor(attribute, class_mappings, optional = false) entity_attributes << attribute optional_attributes << attribute if optional attr_accessor(attribute) add_multitype_setter_method(attribute, class_mappings) end
Multitype Attribute accessors. These methods take an array of type to class mappings. Expected form is:
- [<lamda method which evals to true if this type should be used>, <full class>, <partial class>], […]
# File lib/stbaldricks/entities/lib/base.rb, line 264 def self.multitype_attr_reader(attribute, class_mappings, optional = false) entity_attributes << attribute optional_attributes << attribute if optional attr_reader(attribute) add_multitype_setter_method(attribute, class_mappings, true) end
# File lib/stbaldricks/entities/lib/base.rb, line 271 def self.multitype_attr_writer(attribute, class_mappings, optional = false) entity_attributes << attribute optional_attributes << attribute if optional attr_writer(attribute) add_multitype_setter_method(attribute, class_mappings) end
# File lib/stbaldricks/entities/lib/base.rb, line 323 def self.multitype_collection_attr_accessor(attribute, class_mappings, optional = false) collection_attributes << attribute optional_attributes << attribute if optional attr_accessor(attribute) add_multitype_collection_setter_method(attribute, class_mappings) end
Multitype Collection accessors. These methods take an array of type to class mappings. Expected form is:
- [<lamda method which evals to true if this type should be used>, <full class>, <partial class>], […]
# File lib/stbaldricks/entities/lib/base.rb, line 309 def self.multitype_collection_attr_reader(attribute, class_mappings, optional = false) collection_attributes << attribute optional_attributes << attribute if optional attr_reader(attribute) add_multitype_collection_setter_method(attribute, class_mappings, true) end
# File lib/stbaldricks/entities/lib/base.rb, line 316 def self.multitype_collection_attr_writer(attribute, class_mappings, optional = false) collection_attributes << attribute optional_attributes << attribute if optional attr_writer(attribute) add_multitype_collection_setter_method(attribute, class_mappings) end
# File lib/stbaldricks/entities/lib/base.rb, line 28 def initialize(data = {}, clear_changes = false) # If disallow instantiation has been set, raise an error if someone is trying to call initilaize raise SBF::Client::Error, 'Initialize is not valid on a base object. Use the full or partial version' unless self.class.allow_instantiation? super() @errors = SBF::Client::Entity::Errors.new(self) initialize_attributes(data) reload_recursive if clear_changes end
# File lib/stbaldricks/entities/lib/base.rb, line 354 def self.optional_attributes @optional_attributes ||= Set.new end
Private Class Methods
# File lib/stbaldricks/entities/lib/base.rb, line 366 def self.add_boolean_methods(vars, setter = false) vars.each do |attribute| split_attribute = attribute.to_s.split('is_') next if split_attribute.length <= 1 define_method(:"#{split_attribute.last}?") { send(attribute) } define_changing_attr_methods(attribute, true) if setter end end
Creates a private method which resolves the class type based on the class mappings and the value rubocop:disable Metrics/MethodLength rubocop:todo Metrics/PerceivedComplexity
# File lib/stbaldricks/entities/lib/base.rb, line 394 def self.add_class_selector_method(attribute, class_mappings) # rubocop:todo Metrics/CyclomaticComplexity method_name = :"select_#{attribute}_class" define_singleton_method(method_name) do |value| parsed_class_mappings = get_mappings(attribute, class_mappings) # No class conversion if the value is nil return nil if value.nil? if value.is_a?(Hash) # Allow setting of the value using a hash return NilClass if value.empty? # Find the first class mapping whose check returns true check, full_class, partial_class = parsed_class_mappings.find { |check, _, _| check.call(value) } raise SBF::Client::Error, "unknown #{attribute} type" if check.nil? # If a user specifies exactly the partial fields, then this should be a partial. return partial_class if partial_class && (value.keys - partial_class.defined_attributes.to_a).empty? # Otherwise return full_class if full_class else # No class conversion if the value is already an entity return nil if parsed_class_mappings.any? do |_, c1, c2| [c1, c2].compact.any? do |allowed_class| if allowed_class.instance_of?(Class) # Otherwise, return true if the value is an instance of the allowed class value.is_a?(allowed_class) elsif allowed_class.instance_of?(Module) # If we specified a module, assume this is an enum and return true if the value # is one of the valid constant values for the module allowed_class.constants.any? { |const_name| allowed_class.const_get(const_name) == value } end end end # Convert to child class if given class is a parent of the same name parsed_class_mappings.each do |_, c1, c2| [c1, c2].compact.each do |allowed_class| return allowed_class if allowed_class.ancestors.find { |a| a == value.class && a.demodulize == value.class.demodulize } end end # Don't know how to handle whatever the user entered. Raise an error. # (Nil and Hash are always valid values) valid_classes = ['NilClass', Hash] parsed_class_mappings.each do |_, class1, class2| valid_classes << class1 valid_classes << class2 unless class2.nil? end raise SBF::Client::Error, "Unsupported value for #{name}.#{attribute}: #{value}. Should be one of #{valid_classes.join(', ')}" end end private_class_method method_name end
Base collection setter. Allows for the collection to be an array types of types.
# File lib/stbaldricks/entities/lib/base.rb, line 526 def self.add_multitype_collection_setter_method(attribute, class_mappings, private_method = false) add_class_selector_method(attribute, class_mappings) define_method(:"#{attribute}=") do |values| values ||= [] raise SBF::Client::Error, "#{attribute} must be an array" unless values.is_a?(Array) converted_collection = values.map { |value| convert_value(attribute, value) } instance_variable_set(:"@#{attribute}", converted_collection) send(:"#{attribute}_will_change!") if respond_to?(:"#{attribute}_will_change!") end # Make the setter private if requested (so you can set through the constructor but not anywhere else) private :"#{attribute}=" if private_method end
Base entity setter. Allows for the property to be an array of types.
# File lib/stbaldricks/entities/lib/base.rb, line 511 def self.add_multitype_setter_method(attribute, class_mappings, private_method = false) add_class_selector_method(attribute, class_mappings) method_name = :"#{attribute}=" define_method(method_name) do |value| instance_variable_set(:"@#{attribute}", convert_value(attribute, value)) send(:"#{attribute}_will_change!") if respond_to?(:"#{attribute}_will_change!") end # Make the setter private if requested (so you can set through the constructor but not anywhere else) private method_name if private_method end
# File lib/stbaldricks/entities/lib/base.rb, line 377 def self.define_changing_attr_methods(attribute, is_boolean = false, define_reader = false) if define_reader define_method(:"#{attribute}") do instance_variable_get("@#{attribute}".to_sym) end end define_method(:"#{attribute}=") do |val| send(:"#{attribute}_will_change!") unless val == instance_variable_get("@#{attribute}".to_sym) && !val.nil? instance_variable_set("@#{attribute}".to_sym, is_boolean ? val.to_s.to_b : val) end end
So this method is meant to ensure that you don't do SBF::Client::Participant.new
and are forced, instead, to be explicit about calling either SBF::Client::FullParticipant.new
or SBF::Client::PartialParticipant.new
# File lib/stbaldricks/entities/lib/base.rb, line 212 def self.disallow_instantiation @disallow_instantiation = true end
rubocop:enable Metrics/PerceivedComplexity rubocop:enable Metrics/MethodLength
# File lib/stbaldricks/entities/lib/base.rb, line 459 def self.get_mappings(attribute, class_mappings) # Parse the mappings information and save it off mapping_var = :"@#{attribute}_class_mappings" instance_variable_set(mapping_var, parse_class_mappings(class_mappings)) unless instance_variable_defined?(mapping_var) instance_variable_get(mapping_var) end
Validates the input structure of the class mappings array and returns a new array with the class strings converted to actual classes
# File lib/stbaldricks/entities/lib/base.rb, line 470 def self.parse_class_mappings(mappings) raise SBF::Client::Error, "Invalid class mapping structure: #{mappings.class}" unless mappings.is_a?(Array) mappings.map do |check, full_class, partial_class| raise SBF::Client::Error, "Invalid class mapping check: #{check.class}" unless check.is_a?(Proc) full_class = full_class.to_class unless full_class.nil? || full_class.is_a?(NilClass) || full_class.ancestors.include?(BaseEntity) || full_class.instance_of?(Module) raise SBF::Client::Error, "Invalid class mapping full class: #{full_class}" end partial_class = partial_class.to_class unless partial_class.nil? || partial_class.is_a?(NilClass) || partial_class.ancestors.include?(BaseEntity) raise SBF::Client::Error, "Invalid class mapping partial class: #{partial_class}" end [check, full_class, partial_class] end end
Public Instance Methods
# File lib/stbaldricks/entities/lib/base.rb, line 66 def destroyed? @destroyed end
Returns a hash of changed data for the entity and its sub-entities
@param with_keys [Boolean] when true, include the keys of the current entity. (sub-entity keys will always be included if they are present) @return [Hash] the changed data
# File lib/stbaldricks/entities/lib/base.rb, line 79 def dirty_data(with_keys = false) data = {} changes.each { |k, arr| data[k.to_sym] = arr[1] } instance_variables.each do |var| attribute_symbol = var.to_s.delete('@').to_sym attribute = instance_variable_get(var) if attribute.is_a?(BaseEntity) if attribute.dirty_data(true).empty? next unless changed.map(&:to_sym).include?(attribute_symbol) data.merge!(attribute_symbol => attribute.keys_hash) else data.merge!(attribute_symbol => attribute.dirty_data(true)) end elsif attribute.is_a?(Array) next unless attribute.all? { |e| e.is_a?(BaseEntity) } && attribute.any? { |e| !e.dirty_data(true).empty? } data.merge!(attribute_symbol => attribute) end end return data if data.empty? with_keys ? keys_hash.merge(data) : data end
Attempt to return an ErrorEntity
similar to or exactly like the original
# File lib/stbaldricks/entities/lib/base.rb, line 595 def error log_deprecated('error', caller) return nil if single_active_model_error.nil? details = single_active_model_error.find { |x| x.is_a?(String) } if single_active_model_error.is_a?(Array) details ||= single_active_model_error.is_a?(String) ? single_active_model_error : nil field = single_active_model_error.is_a?(Array) ? single_active_model_error.find { |x| x.is_a?(Symbol) && x != :base } : nil fields = errors.keys.reject { |k| k == :base }.empty? ? nil : errors.keys.reject { |k| k == :base }.map(&:to_s) details = details["#{errors.type}: ".length..(details.length - 1)] if details.start_with?("#{errors.type}: ") details = details["#{field}: ".length..(details.length - 1)] if details.start_with?("#{field}: ") SBF::Client::ErrorEntity.new(code: errors.code, type: errors.type, details: details, errors: fields) end
# File lib/stbaldricks/entities/lib/base.rb, line 106 def keys_hash respond_to?(:id) && id ? {id: id} : {} end
Overridden from ActiveModel::Naming to remove namespace and Full/Partial
# File lib/stbaldricks/entities/lib/base.rb, line 40 def model_name @model_name ||= ActiveModel::Name.new(self, SBF::Client, self.class.name.gsub(/::(Full|Partial)/, '::')) end
Class
method to retrieve the not_provided_attributes
attribute
# File lib/stbaldricks/entities/lib/base.rb, line 223 def not_provided_attributes @not_provided_attributes ||= Set.new end
# File lib/stbaldricks/entities/lib/base.rb, line 44 def persisted? true end
# File lib/stbaldricks/entities/lib/base.rb, line 48 def reload! # get the values from the persistence layer clear_changes_information end
# File lib/stbaldricks/entities/lib/base.rb, line 53 def reload_recursive instance_variables.each do |var| attribute = instance_variable_get(var) attribute.reload_recursive if attribute.is_a?(BaseEntity) attribute.each { |a| a.reload_recursive if a.is_a?(BaseEntity) } if attribute.is_a?(Array) end reload! end
# File lib/stbaldricks/entities/lib/base.rb, line 62 def rollback! restore_attributes end
Recursively converts an entity into a Hash
# File lib/stbaldricks/entities/lib/base.rb, line 544 def to_hash entity_hash = {} self.class.defined_attributes.each do |key| # If the method was not retrieved, do not include it in the hash next if not_provided_attributes.include?(key) # Get the value value = send(key) # If the value is an Entity, call to_hash on it (recursively) entity_hash[key] = if value.is_a?(BaseEntity) value.to_hash # If the value is an Array, need to try to call to_hash on each item elsif value.is_a?(Array) value.map do |element| next element.to_hash if element.is_a?(BaseEntity) next element end # Collections should return empty array rather than nil elsif value.nil? && self.class.collection_attributes.include?(key) [] # Just set the value else value end end # Return the hash entity_hash end
Recursively convert an entity into json
# File lib/stbaldricks/entities/lib/base.rb, line 581 def to_json(*a) to_hash.to_json(*a) end
# File lib/stbaldricks/entities/lib/base.rb, line 70 def to_partial base_class = self.class.name.gsub(/(.*::)(Full|Partial)(.*)/, '\1') base_name = self.class.name.gsub(/(.*::)(Full|Partial)(.*)/, '\3') "#{base_class}Partial#{base_name}".to_class.new(to_hash) end
Private Instance Methods
Convert the value to an instance of the appropriate class.
# File lib/stbaldricks/entities/lib/base.rb, line 492 def convert_value(attribute, value) entity_class_selector_method = :"select_#{attribute}_class" entity_class = self.class.send(entity_class_selector_method, value) # If the entity_class is nil, then that means don't convert anything return value if entity_class.nil? # If the entity_class is the NilClass, return nil return nil if entity_class == NilClass # If we are converting to a subclass, we need to first grab the hash of data from the parent object value = value.to_hash if !value.is_a?(Hash) && value.respond_to?(:to_hash) # Convert to an instance of the appropriate entity class. entity_class.new(value) end
# File lib/stbaldricks/entities/lib/base.rb, line 205 def getter_backup_method_name(name) "_#{name}".to_sym end
# File lib/stbaldricks/entities/lib/base.rb, line 195 def getter_method_name(name) name.to_sym end
rubocop:disable Naming/MemoizedInstanceVariableName
# File lib/stbaldricks/entities/lib/base.rb, line 113 def initialize_attributes(data) data ||= {} data.each do |key, value| # Use the setter if one is available, otherwise set the variable directly setter = "#{key}=".to_sym if respond_to?(setter, true) send(setter, value) elsif respond_to?(key.to_sym) instance_variable_set("@#{key}".to_sym, value) attribute_will_change!(key.to_sym) end end # For each attribute that may be optional, call mark_attribute_not_provided # if the data set does not contain a key with the optional attribute's name self.class.optional_attributes.each { |name| mark_attribute_not_provided(name) unless data.key?(name) } @defined_attributes ||= Set.new @optional_attributes ||= Set.new @not_provided_attributes ||= Set.new @entity_attributes ||= Set.new @collection_attributes ||= Set.new end
rubocop:enable Naming/MemoizedInstanceVariableName
# File lib/stbaldricks/entities/lib/base.rb, line 139 def mark_attribute_not_provided(name) return if not_provided_attributes.include?(name) # Put the method in the list of not_provided methods not_provided_attributes << name # Back up the original version of the setter method if it doesn't already exist setter = setter_method_name(name) setter_backup = setter_backup_method_name(name) singleton_class.send(:alias_method, setter_backup, setter) unless respond_to?(setter_backup, true) # Back up the original version of the getter method if it doesn't already exist getter = getter_method_name(name) getter_backup = getter_backup_method_name(name) singleton_class.send(:alias_method, getter_backup, getter) unless respond_to?(getter_backup, true) # If we set the value in a not_provided method, that means we now have requested the data and we # should replace the original getter/setter and set the value using the original method define_singleton_method(setter) do |v| unmark_attribute_not_provided(name) super(v) end # If we try to get the value in a not_provided method, we should raise an error define_singleton_method(getter) do raise( SBF::Client::DataNotProvidedError, "Data not provided for the #{name} attribute. This is usually due to the attribute not being requested in the 'with' declaration" ) end end
# File lib/stbaldricks/entities/lib/base.rb, line 200 def setter_backup_method_name(name) "_#{name}=".to_sym end
# File lib/stbaldricks/entities/lib/base.rb, line 190 def setter_method_name(name) "#{name}=".to_sym end
# File lib/stbaldricks/entities/lib/base.rb, line 172 def unmark_attribute_not_provided(name) return unless not_provided_attributes.include?(name) # Remove the method from the list of not_provided methods not_provided_attributes.delete(name) # Replace the original version of the setter method using the backup setter = setter_method_name(name) setter_backup = setter_backup_method_name(name) singleton_class.send(:alias_method, setter, setter_backup) # Replace the original version of the getter method using the backup getter = getter_method_name(name) getter_backup = getter_backup_method_name(name) singleton_class.send(:alias_method, getter, getter_backup) end