Skip to content

Commit

Permalink
Memory & performance optimizations (#51)
Browse files Browse the repository at this point in the history
* Extract reusable strings, hashes and arrays into constants

* `class` -> `module` where no instances are created

* Cosmetics

* Fix typo
  • Loading branch information
nesaulov authored Nov 25, 2017
1 parent cc3a5fa commit 343bd77
Show file tree
Hide file tree
Showing 11 changed files with 62 additions and 46 deletions.
6 changes: 3 additions & 3 deletions lib/surrealist/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ class Builder
# Struct to carry schema along
Schema = Struct.new(:key, :value).freeze

attr_reader :carrier, :instance, :schema

# @param [Carrier] carrier instance of Surrealist::Carrier
# @param [Hash] schema the schema defined in the object's class.
# @param [Object] instance the instance of the object which methods from the schema are called on.
Expand All @@ -31,7 +29,7 @@ def call(schema: @schema, instance: @instance)
if schema_value.is_a?(Hash)
check_for_ar(schema, instance, schema_key, schema_value)
else
ValueAssigner.assign(schema: Schema.new(schema_key, schema_value),
ValueAssigner.assign(schema: Schema.new(schema_key, schema_value),
instance: instance) { |coerced_value| schema[schema_key] = coerced_value }
end
end
Expand All @@ -41,6 +39,8 @@ def call(schema: @schema, instance: @instance)

private

attr_reader :carrier, :instance, :schema

# Checks if result is an instance of ActiveRecord::Relation
#
# @param [Hash] schema the schema defined in the object's class.
Expand Down
10 changes: 6 additions & 4 deletions lib/surrealist/carrier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
module Surrealist
# A data structure to carry arguments across methods.
class Carrier
BOOLEANS = [true, false].freeze

attr_reader :camelize, :include_root, :include_namespaces, :root, :namespaces_nesting_level

# Public wrapper for Carrier.
#
# @param [Boolean] camelize optional argument for converting hash to camelBack.
Expand All @@ -20,8 +24,6 @@ def self.call(camelize:, include_root:, include_namespaces:, root:, namespaces_n
new(camelize, include_root, include_namespaces, root, namespaces_nesting_level).sanitize!
end

attr_reader :camelize, :include_root, :include_namespaces, :root, :namespaces_nesting_level

def initialize(camelize, include_root, include_namespaces, root, namespaces_nesting_level)
@camelize = camelize
@include_root = include_root
Expand All @@ -47,7 +49,7 @@ def sanitize!
# @raise ArgumentError
def check_booleans!
booleans_hash.each do |key, value|
unless [true, false].include?(value)
unless BOOLEANS.include?(value)
raise ArgumentError, "Expected `#{key}` to be either true or false, got #{value}"
end
end
Expand All @@ -73,7 +75,7 @@ def check_namespaces_nesting!
# Checks if root is not nil, a non-empty string, or symbol
# @raise ArgumentError
def check_root!
unless root.nil? || (root.is_a?(String) && root.present?) || root.is_a?(Symbol)
unless root.nil? || (root.is_a?(String) && !root.strip.empty?) || root.is_a?(Symbol)
Surrealist::ExceptionRaiser.raise_invalid_root!(root)
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/surrealist/class_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def delegate_surrealization_to(klass)

Surrealist::ExceptionRaiser.raise_invalid_schema_delegation! unless Helper.surrealist?(klass)

instance_variable_set('@__surrealist_schema_parent', klass)
instance_variable_set(Surrealist::PARENT_VARIABLE, klass)
end
end
end
6 changes: 4 additions & 2 deletions lib/surrealist/copier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

module Surrealist
# A helper class for deep copying and wrapping hashes.
class Copier
module Copier
EMPTY_HASH = {}.freeze

class << self
# Deeply copies the schema hash and wraps it if there is a need to.
#
Expand Down Expand Up @@ -107,7 +109,7 @@ def wrap_schema_into_namespace(schema:, klass:, carrier:)
# @return [Hash] resulting hash.
def inject_schema(hash, sub_hash)
hash.each do |k, v|
v == {} ? hash[k] = sub_hash : inject_schema(v, sub_hash)
v == EMPTY_HASH ? hash[k] = sub_hash : inject_schema(v, sub_hash)
end
end
end
Expand Down
12 changes: 8 additions & 4 deletions lib/surrealist/exception_raiser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ class InvalidCollectionError < ArgumentError; end
class InvalidNestingLevel < ArgumentError; end

# A class that raises all Surrealist exceptions
class ExceptionRaiser
module ExceptionRaiser
CLASS_NAME_NOT_PASSED = "Can't wrap schema in root key - class name was not passed".freeze
MUST_RESPOND_TO_EACH = "Can't serialize collection - must respond to :each".freeze
CLASS_DOESNT_INCLUDE_SURREALIST = 'Class does not include Surrealist'.freeze

class << self
# Raises Surrealist::InvalidSchemaDelegation if destination of delegation does not
# include Surrealist.
#
# @raise Surrealist::InvalidSchemaDelegation
def raise_invalid_schema_delegation!
raise Surrealist::InvalidSchemaDelegation, 'Class does not include Surrealist'
raise Surrealist::InvalidSchemaDelegation, CLASS_DOESNT_INCLUDE_SURREALIST
end

# Raises Surrealist::UnknownSchemaError
Expand All @@ -50,14 +54,14 @@ def raise_unknown_schema!(instance)
#
# @raise Surrealist::UnknownRootError
def raise_unknown_root!
raise Surrealist::UnknownRootError, "Can't wrap schema in root key - class name was not passed"
raise Surrealist::UnknownRootError, CLASS_NAME_NOT_PASSED
end

# Raises Surrealist::InvalidCollectionError
#
# @raise Surrealist::InvalidCollectionError
def raise_invalid_collection!
raise Surrealist::InvalidCollectionError, "Can't serialize collection - must respond to :each"
raise Surrealist::InvalidCollectionError, MUST_RESPOND_TO_EACH
end

# Raises ArgumentError if namespaces_nesting_level is not an integer.
Expand Down
2 changes: 1 addition & 1 deletion lib/surrealist/hash_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Surrealist
# A helper class for hashes transformations.
class HashUtils
module HashUtils
class << self
# Converts hash's keys to camelBack keys.
#
Expand Down
18 changes: 8 additions & 10 deletions lib/surrealist/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@

module Surrealist
# A generic helper.
class Helper
class << self
# Determines if the class uses the Surrealist mixin.
#
# @param [Class] klass a class to be checked.
#
# @return [Boolean] if Surrealist is included in class.
def surrealist?(klass)
klass < Surrealist
end
module Helper
# Determines if the class uses the Surrealist mixin.
#
# @param [Class] klass a class to be checked.
#
# @return [Boolean] if Surrealist is included in class.
def self.surrealist?(klass)
klass < Surrealist
end
end
end
12 changes: 7 additions & 5 deletions lib/surrealist/schema_definer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

module Surrealist
# A class that defines a method on the object that stores the schema.
class SchemaDefiner
module SchemaDefiner
ROM_REGEXP = /ROM::Struct/o
SCHEMA_TYPE_ERROR = 'Schema should be defined as a hash'.freeze
# Defines an instance variable on the object that stores the schema.
#
# @param [Object] klass class of the object that needs to be surrealized.
Expand All @@ -13,12 +15,12 @@ class SchemaDefiner
#
# @raise +Surrealist::InvalidSchemaError+ if schema was defined not through a hash.
def self.call(klass, hash)
raise Surrealist::InvalidSchemaError, 'Schema should be defined as a hash' unless hash.is_a?(Hash)
raise Surrealist::InvalidSchemaError, SCHEMA_TYPE_ERROR unless hash.is_a?(Hash)

if klass.name =~ /ROM::Struct/
klass.class_variable_set('@@__surrealist_schema', hash)
if klass.name =~ ROM_REGEXP
klass.class_variable_set(Surrealist::CLASS_VARIABLE, hash)
else
klass.instance_variable_set('@__surrealist_schema', hash)
klass.instance_variable_set(Surrealist::INSTANCE_VARIABLE, hash)
end
end
end
Expand Down
34 changes: 21 additions & 13 deletions lib/surrealist/string_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@

module Surrealist
# A helper class for strings transformations.
class StringUtils
module StringUtils
DASH = '-'.freeze
UNDERSCORE = '_'.freeze
EMPTY_STRING = ''.freeze
DASH_REGEXP1 = /([A-Z]+)([A-Z][a-z])/o
DASH_REGEXP2 = /([a-z\d])([A-Z])/o
UNDERSCORE_REGEXP = /(?:^|_)([^_\s]+)/o
NAMESPACES_SEPARATOR = '::'.freeze
UNDERSCORE_SUBSTITUTE = '\1_\2'.freeze

class << self
# Converts a string to snake_case.
#
# @param [String] string a string to be underscored.
#
# @return [String] underscored string.
# @return [String] new underscored string.
def underscore(string)
string.gsub('::', '_')
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.tr('-', '_')
string.gsub(NAMESPACES_SEPARATOR, UNDERSCORE)
.gsub(DASH_REGEXP1, UNDERSCORE_SUBSTITUTE)
.gsub(DASH_REGEXP2, UNDERSCORE_SUBSTITUTE)
.tr(DASH, UNDERSCORE)
.downcase
end

Expand All @@ -25,12 +34,11 @@ def underscore(string)
# @return [String] camelized string.
def camelize(snake_string, first_upper = true)
if first_upper
snake_string.to_s
.gsub(/(?:^|_)([^_\s]+)/) { Regexp.last_match[1].capitalize }
snake_string.to_s.gsub(UNDERSCORE_REGEXP) { Regexp.last_match[1].capitalize }
else
parts = snake_string.split('_', 2)
parts = snake_string.split(UNDERSCORE, 2)
parts[0] << camelize(parts[1]) if parts.size > 1
parts[0] || ''
parts[0] || EMPTY_STRING
end
end

Expand All @@ -43,7 +51,7 @@ def camelize(snake_string, first_upper = true)
#
# @return [String] extracted class
def extract_class(string)
uncapitalize(string.split('::').last)
uncapitalize(string.split(NAMESPACES_SEPARATOR).last)
end

# Extracts n amount of classes from a namespaces and returns a nested hash.
Expand All @@ -62,8 +70,8 @@ def extract_class(string)
# @return [Hash] a nested hash.
def break_namespaces(klass, camelize:, nesting_level:)
Surrealist::ExceptionRaiser.raise_invalid_nesting!(nesting_level) unless nesting_level > 0
arr = klass.split('::')
arr.last(nesting_level).reverse.inject({}) do |a, n|

klass.split(NAMESPACES_SEPARATOR).last(nesting_level).reverse.inject({}) do |a, n|
camelize ? Hash[camelize(uncapitalize(n), false).to_sym => a] : Hash[underscore(n).to_sym => a]
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/surrealist/type_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Surrealist
# Service class for type checking
class TypeHelper
module TypeHelper
# Dry-types class matcher
DRY_TYPE_CLASS = 'Dry::Types'.freeze

Expand All @@ -18,7 +18,7 @@ def valid_type?(value:, type:)
return true if type == Any

if type == Bool
[true, false].include?(value)
Surrealist::Carrier::BOOLEANS.include?(value)
elsif dry_type?(type)
type.try(value).success?
else
Expand Down
2 changes: 1 addition & 1 deletion lib/surrealist/value_assigner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Surrealist
# A class that determines the correct value to return for serialization. May descend recursively.
class ValueAssigner
module ValueAssigner
class << self
# Assigns value returned from a method to a corresponding key in the schema hash.
#
Expand Down

0 comments on commit 343bd77

Please sign in to comment.