Module: Structish::Validations::InstanceMethods

Defined in:
lib/structish/validations.rb

Instance Method Summary collapse

Instance Method Details

#[]=(key, value) ⇒ Object



137
138
139
140
# File 'lib/structish/validations.rb', line 137

def []=(key, value)
  super(key, value)
  validate_structish(self)
end

#apply_defaults(constructor) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
# File 'lib/structish/validations.rb', line 34

def apply_defaults(constructor)
  validations.select { |v| v[:optional] }.each do |attribute|
    key = attribute[:key]
    default_value = if attribute[:default].is_a?(::Array) && attribute[:default].first == :other_attribute
      constructor[attribute[:default][1]]
    else
      attribute[:default]
    end
    constructor[key] = default_value if constructor[key].nil?
  end
end

#attribute_valuesObject



166
167
168
169
170
171
172
# File 'lib/structish/validations.rb', line 166

def attribute_values
  if self.class < Array
    self.to_a.values_at(*self.class.attribute_keys)
  elsif self.class < Hash
    self.to_h.slice(*self.class.attribute_keys)
  end
end

#cast_single(value, klass) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
# File 'lib/structish/validations.rb', line 62

def cast_single(value, klass)
  if value.is_a?(klass)
    value
  else
    if cast_method = Structish::CAST_METHODS[klass.to_s]
      value.send(cast_method)
    else
      klass.new(value)
    end
  end
end

#cast_values(constructor) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/structish/validations.rb', line 46

def cast_values(constructor)
  (validations + global_attributes_for(constructor)).each do |attribute|
    key = attribute[:key]
    if attribute[:cast] && constructor[key]
      if attribute[:klass] == ::Array && attribute[:of]
        unless constructor[key].class <= ::Array
          raise(Structish::ValidationError.new("Class mismatch for #{attribute[:key]} -> #{constructor[key].class}. Should be a Array", self.class))
        end
        constructor[key] = constructor[key].map { |v| cast_single(v, attribute[:of]) }
      else
        constructor[key] = cast_single(constructor[key], attribute[:klass])
      end
    end
  end
end

#define_accessor_methods(constructor) ⇒ Object



74
75
76
77
78
79
80
81
# File 'lib/structish/validations.rb', line 74

def define_accessor_methods(constructor)
  validations.each do |attribute|
    if accessor = attribute[:accessor]
      value = attribute[:proc] ? attribute[:proc].call(constructor[attribute[:key]]) : constructor[attribute[:key]]
      instance_variable_set "@#{accessor}", value
    end
  end
end

#define_delegated_methodsObject



20
21
22
23
24
# File 'lib/structish/validations.rb', line 20

def define_delegated_methods
  self.class.delegations.each do |function, object|
    define_singleton_method(function.to_s) { self.send(object.to_sym)&.send(function.to_sym) }
  end
end

#global_attributes_for(constructor) ⇒ Object



97
98
99
100
101
102
103
104
# File 'lib/structish/validations.rb', line 97

def global_attributes_for(constructor)
  global_attributes_hash[constructor] = begin
    constructor_keys = keys_for(constructor)
    global_validations.each_with_object([]) do |validation, arr|
      constructor_keys.each { |key| arr << validation.merge(key: key) }
    end
  end
end

#global_attributes_hashObject



142
143
144
# File 'lib/structish/validations.rb', line 142

def global_attributes_hash
  @global_attributes_hash ||= {}
end

#global_validationsObject



150
151
152
# File 'lib/structish/validations.rb', line 150

def global_validations
  @global_validations ||= self.class.global_validations + parent_global_validations(self.class)
end

#keys_for(constructor) ⇒ Object



174
175
176
177
178
179
180
# File 'lib/structish/validations.rb', line 174

def keys_for(constructor)
  if constructor.class <= ::Array
    [*0..constructor.size-1]
  elsif constructor.class <= ::Hash
    constructor.keys
  end
end

#parent_attributes(klass) ⇒ Object



154
155
156
157
158
# File 'lib/structish/validations.rb', line 154

def parent_attributes(klass)
  if klass.superclass.respond_to?(:structish?) && klass.superclass.structish?
    klass.superclass.attributes + parent_attributes(klass.superclass)
  end || []
end

#parent_global_validations(klass) ⇒ Object



160
161
162
163
164
# File 'lib/structish/validations.rb', line 160

def parent_global_validations(klass)
  if klass.superclass.respond_to?(:structish?) && klass.superclass.structish?
    klass.superclass.global_validations + parent_global_validations(klass.superclass)
  end || []
end

#validate_class(attribute, value) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/structish/validations.rb', line 110

def validate_class(attribute, value)
  if attribute[:klass].nil?
    return
  elsif attribute[:of]
    valid = if attribute[:klass] <= ::Array
      value.class <= ::Array && value.all? { |v| v.class <= attribute[:of] }
    elsif attribute[:klass] <= ::Hash
      value.class <= ::Hash && value.values.all? { |v| v.class <= attribute[:of] }
    end
    raise(Structish::ValidationError.new("Class mismatch for #{attribute[:key]}. All values should be of type #{attribute[:of].to_s}", self.class)) unless valid
  else
    valid_klasses = [attribute[:klass]].flatten.compact
    valid = valid_klasses.any? { |klass| value.class <= klass }
    raise(Structish::ValidationError.new("Class mismatch for #{attribute[:key]} -> #{value.class}. Should be a #{valid_klasses.join(", ")}", self.class)) unless valid
  end
end

#validate_constructor(constructor) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/structish/validations.rb', line 83

def validate_constructor(constructor)
  (validations + global_attributes_for(constructor)).each do |attribute|
    value = constructor[attribute[:key]]
    if attribute[:optional] && value.nil?
      true
    else
      validate_presence(attribute, value)
      validate_class(attribute, value)
      validate_one_of(attribute, value)
      validate_custom(attribute, value, constructor)
    end
  end
end

#validate_custom(attribute, value, constructor) ⇒ Object



132
133
134
135
# File 'lib/structish/validations.rb', line 132

def validate_custom(attribute, value, constructor)
  valid = attribute[:validation] ? attribute[:validation].new(value, attribute, constructor).validate : true
  raise(Structish::ValidationError.new("Custom validation #{attribute[:validation].to_s} not met", self.class)) unless valid
end

#validate_key_restriction(constructor) ⇒ Object



26
27
28
29
30
31
32
# File 'lib/structish/validations.rb', line 26

def validate_key_restriction(constructor)
  if self.class.restrict?
    allowed_keys = validations.map { |attribute| attribute[:key] }
    valid = (keys_for(constructor) - allowed_keys).empty?
    raise(Structish::ValidationError.new("Keys are restricted to #{allowed_keys.join(", ")}", self.class)) unless valid
  end
end

#validate_one_of(attribute, value) ⇒ Object



127
128
129
130
# File 'lib/structish/validations.rb', line 127

def validate_one_of(attribute, value)
  valid = attribute[:one_of] ? attribute[:one_of].include?(value) : true
  raise(Structish::ValidationError.new("Value not one of #{attribute[:one_of].join(", ")}", self.class)) unless valid
end

#validate_presence(attribute, value) ⇒ Object



106
107
108
# File 'lib/structish/validations.rb', line 106

def validate_presence(attribute, value)
  raise(Structish::ValidationError.new("Required value #{attribute[:key]} not present", self.class)) unless !value.nil?
end

#validate_structish(constructor) ⇒ Object



11
12
13
14
15
16
17
18
# File 'lib/structish/validations.rb', line 11

def validate_structish(constructor)
  validate_key_restriction(constructor)
  apply_defaults(constructor)
  cast_values(constructor)
  validate_constructor(constructor)
  define_accessor_methods(constructor)
  define_delegated_methods
end

#validationsObject



146
147
148
# File 'lib/structish/validations.rb', line 146

def validations
  @validations ||= self.class.attributes + parent_attributes(self.class)
end