Module: Decanter::Core::ClassMethods

Defined in:
lib/decanter/core.rb

Instance Method Summary collapse

Instance Method Details

#decant(args) ⇒ Object

Take a parameter hash, and handle it with the various decanters defined.



79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/decanter/core.rb', line 79

def decant(args)
  return handle_empty_args if args.blank?
  return empty_required_input_error unless required_input_keys_present?(args)

  if args.is_a?(ActionController::Parameters)
    args.permit!
    args = args.to_h
  end

  args = args.deep_symbolize_keys
  handled_keys(args).merge(unhandled_keys(args))
end

#decanter_for_handler(handler) ⇒ Object



160
161
162
163
164
165
166
# File 'lib/decanter/core.rb', line 160

def decanter_for_handler(handler)
  if (specified_decanter = handler[:options][:decanter])
    Decanter.decanter_from(specified_decanter)
  else
    Decanter.decanter_for(handler[:assoc])
  end
end

#empty_required_input_error(name = nil) ⇒ Object



106
107
108
# File 'lib/decanter/core.rb', line 106

def empty_required_input_error(name = nil)
  raise MissingRequiredInputValue, "No value found for required argument #{name}"
end

#handle(handler, values) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/decanter/core.rb', line 143

def handle(handler, values)
  decanter =  decanter_for_handler(handler) unless handler[:type] == :input

  val = case handler[:type]
        when :input
          parse(handler[:parsers], values, handler[:options])
        when :has_one
          decanter.decant(values)
        when :has_many
          # should sort here, really.
          values = values.values if values.is_a?(Hash)
          values.compact.map { |v| decanter.decant(v) }
        end

  { handler[:key] => val }
end

#handle_empty_argsObject



168
169
170
# File 'lib/decanter/core.rb', line 168

def handle_empty_args
  required_inputs.any? ? empty_required_input_error : {}
end

#handled_keys(args) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/decanter/core.rb', line 129

def handled_keys(args)
  handlers.reduce({}) do |m, h|
    name, handler = *h
    values = args.values_at(*name)
    values = values.length == 1 ? values.first : values

    if handler[:options][:required] && Array(values).all?(&:blank?)
      empty_required_input_error(name)
    end

    m.merge handle(handler, values)
  end
end

#handler(name, options) ⇒ Object

Add a parameter handler to the class. Takes a name, and a set of options. This is a generic method for any sort of handler, e.g.

handle :foo, type: :input, parsers: [:string], as: :bar



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/decanter/core.rb', line 38

def handler(name, options)
  name = options.fetch(:as, name)
  parsers = options.delete(:parsers)

  if Array(name).length > 1 && parsers.blank?
    raise ArgumentError,
          "#{name} no parser specified for input with multiple values."
  end

  if handlers.key?(name)
    raise ArgumentError, "Handler for #{name} already defined"
  end

  handlers[name] = {
    key:     options.fetch(:key, Array(name).first),
    assoc:   options.delete(:assoc),
    type:    options.delete(:type),
    options: options,
    parsers: Array(parsers)
  }
end

#handlersObject



190
191
192
# File 'lib/decanter/core.rb', line 190

def handlers
  @handlers ||= {}
end

#has_many(name, **options) ⇒ Object

Declare a _has many_ association for a parameter.



20
21
22
23
24
# File 'lib/decanter/core.rb', line 20

def has_many(name, **options)
  options[:type] = :has_many
  options[:assoc] = name
  handler(name, options)
end

#has_one(name, **options) ⇒ Object

Declare a _has one_ association for a parameter.



27
28
29
30
31
# File 'lib/decanter/core.rb', line 27

def has_one(name, **options)
  options[:type] = :has_one
  options[:assoc] = name
  handler(name, options)
end

#ignore(*args) ⇒ Object

List of parameters to ignore.



61
62
63
# File 'lib/decanter/core.rb', line 61

def ignore(*args)
  keys_to_ignore.push(*args)
end

#input(name, parsers = nil, **options) ⇒ Object

Declare an ordinary parameter transformation.



11
12
13
14
15
# File 'lib/decanter/core.rb', line 11

def input(name, parsers = nil, **options)
  options[:type] = :input
  options[:parsers] = parsers
  handler(name, options)
end

#keys_to_ignoreObject



194
195
196
# File 'lib/decanter/core.rb', line 194

def keys_to_ignore
  @keys_to_ignore ||= []
end

#parse(parsers, values, options) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/decanter/core.rb', line 172

def parse(parsers, values, options)
  return values if parsers.nil?

  Parser.parsers_for(parsers).each do |parser|
    unless values.is_a?(Hash)
      values = parser.parse(values, options)
      next
    end

    # For hashes, we operate the parser on each member
    values.each do |k, v|
      values[k] = parser.parse(v, options)
    end
  end

  values
end

#required_input_keys_present?(args = {}) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
101
102
103
104
# File 'lib/decanter/core.rb', line 98

def required_input_keys_present?(args = {})
  return true unless required_inputs.any?
  compact_inputs = required_inputs.compact
  compact_inputs.all? do |input|
    args.keys.map(&:to_sym).include?(input) && !args[input].nil?
  end
end

#required_inputsObject



92
93
94
95
96
# File 'lib/decanter/core.rb', line 92

def required_inputs
  handlers.map do |name, handler|
    name if handler[:options][:required]
  end
end

#strict(mode) ⇒ Object

Set a level of strictness when dealing with parameters that are present but not expected.

with_exception: Raise an exception true: Delete the parameter false: Allow the parameter through

Raises:

  • (ArgumentError)


72
73
74
75
# File 'lib/decanter/core.rb', line 72

def strict(mode)
  raise(ArgumentError, "#{name}: Unknown strict value #{mode}") unless [:with_exception, true, false].include? mode
  @strict_mode = mode
end

#strict_modeObject



198
199
200
# File 'lib/decanter/core.rb', line 198

def strict_mode
  @strict_mode.nil? ? Decanter.configuration.strict : @strict_mode
end

#unhandled_keys(args) ⇒ Object

protected



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/decanter/core.rb', line 112

def unhandled_keys(args)
  unhandled = args.keys
  unhandled -= keys_to_ignore
  unhandled -= handlers.keys.flatten

  return {} unless unhandled.any?

  case strict_mode
  when true
    {}
  when :with_exception
    raise(UnhandledKeysError, "#{name} received unhandled keys: #{unhandled.join(', ')}.")
  else
    args.select { |key| unhandled.include? key }
  end
end