forked from hashie/hashie
/
trash.rb
121 lines (104 loc) · 3.77 KB
/
trash.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
require 'hashie/dash'
module Hashie
# A Trash is a 'translated' Dash where the keys can be remapped from a source
# hash.
#
# Trashes are useful when you need to read data from another application,
# such as a Java api, where the keys are named differently from how we would
# in Ruby.
class Trash < Dash
# Defines a property on the Trash. Options are as follows:
#
# * <tt>:default</tt> - Specify a default value for this property, to be
# returned before a value is set on the property in a new Dash.
# * <tt>:from</tt> - Specify the original key name that will be write only.
# * <tt>:with</tt> - Specify a lambda to be used to convert value.
# * <tt>:transform_with</tt> - Specify a lambda to be used to convert value
# without using the :from option. It transform the property itself.
def self.property(property_name, options = {})
super
options[:from] = options[:from] if options[:from]
if options[:from]
if property_name == options[:from]
fail ArgumentError, "Property name (#{property_name}) and :from option must not be the same"
end
translations[options[:from]] ||= []
translations[options[:from]] << { property_name: property_name, with: options[:with] || options[:transform_with] }
define_method "#{options[:from]}=" do |val|
self.class.translations[options[:from]].each do |translation|
property_name = translation[:property_name]
with = translation[:with]
self[property_name] = with.respond_to?(:call) ? with.call(val) : val
end
end
else
if options[:transform_with].respond_to? :call
transforms[property_name] = options[:transform_with]
end
end
end
class << self
attr_reader :transforms, :translations
end
instance_variable_set('@transforms', {})
instance_variable_set('@translations', {})
def self.inherited(klass)
super
klass.instance_variable_set('@transforms', transforms.dup)
klass.instance_variable_set('@translations', translations.dup)
end
# Set a value on the Dash in a Hash-like way. Only works
# on pre-existing properties.
def []=(property, value)
if self.class.translation_exists? property
send("#{property}=", value)
elsif self.class.transformation_exists? property
super property, self.class.transformed_property(property, value)
elsif property_exists? property
super
end
end
def self.transformed_property(property_name, value)
transforms[property_name].call(value)
end
def self.translation_exists?(name)
translations.key? name
end
def self.transformation_exists?(name)
transforms.key? name
end
def self.permitted_input_keys
@permitted_input_keys ||= properties.map { |property| inverse_translations.fetch property, property }
end
private
def self.inverse_translations
@inverse_translations ||= begin
h = {}
translations.each do |property_name, property_translations|
property_translations.each do |property_translation|
h[property_translation[:property_name]] = property_name
end
end
h
end
end
# Raises an NoMethodError if the property doesn't exist
#
def property_exists?(property)
fail_no_property_error!(property) unless self.class.property?(property)
true
end
private
# Deletes any keys that have a translation
def initialize_attributes(attributes)
return unless attributes
attributes_copy = attributes.dup.delete_if do |k, v|
if self.class.translations.include?(k)
self[k] = v
true
end
end
super attributes_copy
end
end
end