-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrecord.rb
More file actions
167 lines (146 loc) · 4.42 KB
/
record.rb
File metadata and controls
167 lines (146 loc) · 4.42 KB
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
class Record < ApplicationRecord
has_many :record_values, dependent: :delete_all
has_and_belongs_to_many :collections
belongs_to :schema
scope :active, ->() { where(deactivated: false) }
delegate :attributes, :multiple?, to: :schema
def self.bulk_cache
@bulk_cache ||= []
end
def self.import_bulk
RecordValue.import bulk_cache, validate: false
@bulk_cache = [] # Reset cache
end
def value?(name)
schema.attributes.include?(name)
end
def value(name)
return nil unless value?(name)
record_values.where(name: name).first
end
def to_h(attributes = schema.attributes)
attributes.each { |a| fetch_value(a) }
value_cache.to_h
end
def inspect
to_h.collect{ |k,v| v.is_a?(String) ? [k, v.truncate(200)] : [k,v] }.to_h
end
def value_cache
@value_cache ||= OpenStruct.new
end
def store_value(name, *arguments)
return nil unless value?(name)
if self.multiple?(name)
values = []
if arguments.first.respond_to?(:each)
# Argument is iterable --> replace all values
arguments.first.each_with_index do |v, i|
value = check_type(name, v)
upsert_value(name, value, i)
values << value
end
record_values.with_name(name).where('index > ?', arguments.first.count).destroy_all
end
value_cache[name] = values
return values
else
value = check_type(name, arguments.first)
upsert_value(name, value)
value_cache[name] = value
return value
end
end
def bulk_store_value(name, *arguments)
return nil unless value?(name)
if self.multiple?(name)
values = []
if arguments.first.respond_to?(:each)
# Argument is iterable --> replace all values
arguments.first.each_with_index do |v, i|
value = check_type(name, v)
Record.bulk_cache << bulk_value(name, value, i)
values << value
end
record_values.with_name(name).where('index > ?', arguments.first.count).destroy_all
end
value_cache[name] = values
return values
else
value = check_type(name, arguments.first)
Record.bulk_cache << bulk_value(name, value)
value_cache[name] = value
return value
end
end
def fetch_value(name, *arguments)
return value_cache[name] if value_cache[name] # Try to get from cache
return nil unless value?(name)
load_value_from_db(name)
end
# Redirect methods that correspond to getter and setter of valid attributes
def method_missing(method_name, *arguments, &block)
method_name_s = method_name.to_s
if attributes.include?(method_name_s.gsub('=', ''))
if method_name_s =~ /^(.*)=$/
store_value(method_name_s.gsub('=', ''), *arguments)
else
fetch_value(method_name_s)
end
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
attributes.include?(method_name.to_s.gsub('=', '')) || super
end
private
# Load value from DB and safe to cache
# UNSAFE: does not check whether attribute exists!
def load_value_from_db(name)
if self.multiple?(name)
return value_cache[name] = self.record_values.where(name: name).collect(&:data)
else
return value_cache[name] = self.record_values.where(name: name).first.try(:data)
end
end
TYPE_ASSIGNMENT = { 'int': [Integer], 'string': [String], 'bool': [TrueClass, FalseClass], 'date': [Date], 'html': [String]}.freeze
def check_type(attribute, value)
t = schema.attribute_type(attribute)
return value if TYPE_ASSIGNMENT[t.to_sym].include?(value.class)
begin
case t
when 'int'
value = Integer(value)
when 'string', 'html'
value = value.to_s
when 'bool'
value = ActiveModel::Type::Boolean.new.cast(value)
when 'date'
value = (value.is_a?(String) ? Date.parse(value) : value&.to_date)
else
fail "Type #{t} is invalid!"
end
rescue StandardError => e
fail "Cannot cast value #{value} to type #{t}!"
end
value
end
def upsert_value(name, value, index = 0)
RecordValue.new(
record_id: self.id,
name: name,
index: index,
data: value,
value_type: schema.attribute_type(name)
).upsert
end
def bulk_value(name, value, index = 0)
{
record_id: self.id,
name: name,
index: index,
data: value,
value_type: schema.attribute_type(name)
}
end
end