class Class2
Constants
- CONVERSIONS
- VERSION
Public Class Methods
new(*argz, &block)
click to toggle source
# File lib/class2.rb, line 45 def new(*argz, &block) specs = argz namespace = Object if specs[0].is_a?(String) || specs[0].is_a?(Module) namespace = specs[0].is_a?(String) ? create_namespace(specs.shift) : specs.shift end specs.each do |spec| spec = [spec] unless spec.respond_to?(:each) spec.each { |klass, attributes| make_class(namespace, klass, attributes, block) } end nil end
Private Class Methods
__initialize(attributes)
click to toggle source
# File lib/class2.rb, line 239 def __initialize(attributes) return unless attributes.is_a?(Hash) assign_attributes(attributes) end
assign_attributes(attributes)
click to toggle source
# File lib/class2.rb, line 246 def assign_attributes(attributes) attributes.each do |key, value| if self.class.__nested_attributes.include?(key.respond_to?(:to_sym) ? key.to_sym : key) && (value.is_a?(Hash) || value.is_a?(Array)) name = key.to_s.classify # parent is deprecated in ActiveSupport 6 and its warning uses Strong#squish! which they don't include! parent = self.class.respond_to?(:module_parent) ? self.class.module_parent : self.class.parent next unless parent.const_defined?(name) klass = parent.const_get(name) value = value.is_a?(Hash) ? klass.new(value) : value.map { |v| klass.new(v) } end method = "#{key}=" public_send(method, value) if respond_to?(method) end end
create_namespace(str)
click to toggle source
# File lib/class2.rb, line 90 def create_namespace(str) str.split("::").inject(Object) do |parent, child| # empty? to handle "::Namespace" child.empty? ? parent : parent.const_defined?(child) ? # With 2.1 we can just say Object.const_defined?(str) but keep this around for now. parent.const_get(child) : parent.const_set(child, Module.new) end end
initialize(attributes = nil)
click to toggle source
# File lib/class2.rb, line 143 def initialize(attributes = nil) __initialize(attributes) end
make_class(namespace, name, attributes, block)
click to toggle source
# File lib/class2.rb, line 131 def make_class(namespace, name, attributes, block) nested, simple = split_and_normalize_attributes(attributes) nested.each do |object| object.each { |klass, attrs| make_class(namespace, klass, attrs, block) } end name = name.to_s.classify return if namespace.const_defined?(name, false) make_method_name = lambda { |x| x.to_s.gsub(/[^\w]+/, "_") } # good enough klass = Class.new do def initialize(attributes = nil) __initialize(attributes) end class_eval <<-CODE, __FILE__, __LINE__ def hash to_h.hash end def ==(other) return false unless other.instance_of?(self.class) to_h == other.to_h end alias eql? == def to_h hash = {} self.class.__attributes.each do |name| hash[name] = v = public_send(name) # Don't turn nil into a Hash next if v.nil? || !v.respond_to?(:to_h) # Don't turn empty Arrays into a Hash next if v.is_a?(Array) && v.empty? errors = [ ArgumentError, TypeError ] # Seems needlessly complicated, why doesn't Hash() do some of this? begin hash[name] = v.to_h # to_h is dependent on its contents rescue *errors next unless v.is_a?(Enumerable) hash[name] = v.map do |e| begin e.respond_to?(:to_h) ? e.to_h : e rescue *errors e end end end end hash end def self.__nested_attributes #{nested.map { |n| n.keys.first.to_sym }}.freeze end def self.__attributes (#{simple.map { |n| n.keys.first.to_sym }} + __nested_attributes).freeze end CODE simple.each do |cfg| method, type = cfg.first method = make_method_name[method] # Use Enum somehow? retval = if type == Array || type.is_a?(Array) "[]" elsif type == Hash || type.is_a?(Hash) "{}" else "nil" end class_eval <<-CODE, __FILE__, __LINE__ def #{method} @#{method} = #{retval} unless defined? @#{method} @#{method} end def #{method}=(v) @#{method} = #{CONVERSIONS[type]["v"]} end CODE end nested.map { |n| n.keys.first }.each do |method, _| method = make_method_name[method] attr_writer method retval = method == method.pluralize ? "[]" : "#{namespace}::#{method.classify}.new" class_eval <<-CODE def #{method} @#{method} ||= #{retval} end CODE end # Do this last to allow for overriding the methods we define class_eval(&block) unless block.nil? protected def __initialize(attributes) return unless attributes.is_a?(Hash) assign_attributes(attributes) end private def assign_attributes(attributes) attributes.each do |key, value| if self.class.__nested_attributes.include?(key.respond_to?(:to_sym) ? key.to_sym : key) && (value.is_a?(Hash) || value.is_a?(Array)) name = key.to_s.classify # parent is deprecated in ActiveSupport 6 and its warning uses Strong#squish! which they don't include! parent = self.class.respond_to?(:module_parent) ? self.class.module_parent : self.class.parent next unless parent.const_defined?(name) klass = parent.const_get(name) value = value.is_a?(Hash) ? klass.new(value) : value.map { |v| klass.new(v) } end method = "#{key}=" public_send(method, value) if respond_to?(method) end end end namespace.const_set(name, klass) end
split_and_normalize_attributes(attributes)
click to toggle source
# File lib/class2.rb, line 99 def split_and_normalize_attributes(attributes) nested = [] simple = [] attributes = [attributes] unless attributes.is_a?(Array) attributes.compact.each do |attr| # Just an attribute name, no type if !attr.is_a?(Hash) simple << { attr => nil } next end attr.each do |k, v| if v.is_a?(Hash) || v.is_a?(Array) if v.empty? # If it's empty it's not a nested spec, the attributes type is a Hash or Array simple << { k => v.class } else nested << { k => v } end else # Type can be a class name or an instance # If it's an instance, use its type v = v.class unless v.is_a?(Class) || v.is_a?(Module) simple << { k => v } end end end [ nested, simple ] end