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
122
123
124
125
126
127
|
# File 'lib/jsonb_accessor/macro.rb', line 6
def jsonb_accessor(jsonb_attribute, field_types)
names_and_store_keys = field_types.each_with_object({}) do |(name, type), mapping|
_type, options = Array(type)
mapping[name.to_s] = (options.try(:delete, :store_key) || name).to_s
end
field_types.each do |name, type|
next attribute name, type unless type.is_a?(Array)
next attribute name, *type unless type.last.is_a?(Hash)
*args, keyword_args = type
attribute name, *args, **keyword_args
end
store_key_mapping_method_name = "jsonb_store_key_mapping_for_#{jsonb_attribute}"
class_methods = Module.new do
define_method(store_key_mapping_method_name) do
superclass_mapping = superclass.try(store_key_mapping_method_name) || {}
superclass_mapping.merge(names_and_store_keys)
end
end
extend class_methods
names_and_defaults = field_types.each_with_object({}) do |(name, type), mapping|
_type, options = Array(type)
field_default = options.try(:delete, :default)
mapping[name.to_s] = field_default unless field_default.nil?
end
store_keys_and_defaults = ::JsonbAccessor::QueryHelper.convert_keys_to_store_keys(names_and_defaults, public_send(store_key_mapping_method_name))
defaults_mapping_method_name = "jsonb_defaults_mapping_for_#{jsonb_attribute}"
class_methods.instance_eval do
define_method(defaults_mapping_method_name) do
superclass_mapping = superclass.try(defaults_mapping_method_name) || {}
superclass_mapping.merge(store_keys_and_defaults)
end
end
all_defaults_mapping = public_send(defaults_mapping_method_name)
all_defaults_mapping_proc =
if all_defaults_mapping.present?
-> { all_defaults_mapping.transform_values { |value| value.respond_to?(:call) ? value.call : value }.to_h.compact }
end
attribute jsonb_attribute, :jsonb, default: all_defaults_mapping_proc if all_defaults_mapping_proc.present?
setters = Module.new do
names_and_store_keys.each do |name, store_key|
define_method("#{name}=") do |value|
super(value)
new_values = (public_send(jsonb_attribute) || {}).merge(store_key => public_send(name))
write_attribute(jsonb_attribute, new_values)
end
end
define_method("#{jsonb_attribute}=") do |given_value|
value = given_value || {}
names_to_store_keys = self.class.public_send(store_key_mapping_method_name)
empty_store_key_attributes = names_to_store_keys.values.each_with_object({}) { |name, defaults| defaults[name] = nil }
empty_named_attributes = names_to_store_keys.keys.each_with_object({}) { |name, defaults| defaults[name] = nil }
store_key_attributes = ::JsonbAccessor::QueryHelper.convert_keys_to_store_keys(value, names_to_store_keys)
write_attribute(jsonb_attribute, empty_store_key_attributes.merge(store_key_attributes).compact)
empty_named_attributes.merge(value).each { |name, attribute_value| write_attribute(name, attribute_value) }
end
end
include setters
after_initialize do
if has_attribute?(jsonb_attribute)
jsonb_values = public_send(jsonb_attribute) || {}
jsonb_values.each do |store_key, value|
name = names_and_store_keys.key(store_key)
next unless name
write_attribute(name, value)
clear_attribute_change(name) if persisted?
end
end
end
scope("#{jsonb_attribute}_where", lambda do |attributes|
store_key_attributes = ::JsonbAccessor::QueryHelper.convert_keys_to_store_keys(attributes, all.model.public_send(store_key_mapping_method_name))
jsonb_where(jsonb_attribute, store_key_attributes)
end)
scope("#{jsonb_attribute}_where_not", lambda do |attributes|
store_key_attributes = ::JsonbAccessor::QueryHelper.convert_keys_to_store_keys(attributes, all.model.public_send(store_key_mapping_method_name))
jsonb_where_not(jsonb_attribute, store_key_attributes)
end)
scope("#{jsonb_attribute}_order", lambda do |*args|
ordering_options = args.
order_by_defaults = args.each_with_object({}) { |attribute, config| config[attribute] = :asc }
store_key_mapping = all.model.public_send(store_key_mapping_method_name)
order_by_defaults.merge(ordering_options).reduce(all) do |query, (name, direction)|
key = store_key_mapping[name.to_s]
order_query = jsonb_order(jsonb_attribute, key, direction)
query.merge(order_query)
end
end)
end
|