dynamic-records-meritfront 3.0.6 → 3.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +9 -9
- data/README.md +27 -13
- data/lib/dynamic-records-meritfront/version.rb +1 -1
- data/lib/dynamic-records-meritfront.rb +690 -548
- metadata +6 -6
@@ -1,24 +1,30 @@
|
|
1
1
|
require "dynamic-records-meritfront/version"
|
2
2
|
require 'hashid/rails'
|
3
3
|
|
4
|
+
#this file contains multiple classes which should honestly be split up
|
5
|
+
|
4
6
|
module DynamicRecordsMeritfront
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
# the two aliases so I dont go insane
|
10
|
+
module Hashid::Rails
|
11
|
+
alias hid hashid
|
12
|
+
end
|
13
|
+
|
14
|
+
module Hashid::Rails::ClassMethods
|
15
|
+
alias hfind find_by_hashid
|
16
|
+
end
|
17
|
+
|
18
|
+
included do
|
19
|
+
# include hash id gem
|
20
|
+
include Hashid::Rails
|
21
|
+
#should work, probably able to override by redefining in ApplicationRecord class.
|
22
|
+
#Note we defined here as it breaks early on as Rails.application returns nil
|
23
|
+
PROJECT_NAME = Rails.application.class.to_s.split("::").first.to_s.downcase
|
20
24
|
DYNAMIC_SQL_RAW = true
|
21
|
-
|
25
|
+
|
26
|
+
end
|
27
|
+
|
22
28
|
class DynamicSqlVariables
|
23
29
|
attr_accessor :sql_hash
|
24
30
|
attr_accessor :params
|
@@ -27,11 +33,17 @@ module DynamicRecordsMeritfront
|
|
27
33
|
self.params = params
|
28
34
|
end
|
29
35
|
|
36
|
+
def key_index(key)
|
37
|
+
k = sql_hash.keys.index(key)
|
38
|
+
k += 1 unless k.nil?
|
39
|
+
k
|
40
|
+
end
|
41
|
+
|
30
42
|
def add_key_value(key, value = nil)
|
31
43
|
value = params[key] if value.nil?
|
32
44
|
#tracks the variable and returns the keys sql variable number
|
33
45
|
sql_hash[key] ||= convert_to_query_attribute(key, value)
|
34
|
-
return
|
46
|
+
return key_index(key)
|
35
47
|
end
|
36
48
|
|
37
49
|
def next_sql_num
|
@@ -44,38 +56,39 @@ module DynamicRecordsMeritfront
|
|
44
56
|
end
|
45
57
|
|
46
58
|
#thank god for some stack overflow people are pretty awesome https://stackoverflow.com/questions/64894375/executing-a-raw-sql-query-in-rails-with-an-array-parameter-against-postgresql
|
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
|
-
|
59
|
+
#BigIntArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::BigInteger.new).freeze
|
60
|
+
#IntegerArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::Integer.new).freeze
|
61
|
+
|
62
|
+
#https://api.rubyonrails.org/files/activemodel/lib/active_model/type_rb.html
|
63
|
+
# active_model/type/helpers
|
64
|
+
# active_model/type/value
|
65
|
+
# active_model/type/big_integer
|
66
|
+
# active_model/type/binary
|
67
|
+
# active_model/type/boolean
|
68
|
+
# active_model/type/date
|
69
|
+
# active_model/type/date_time
|
70
|
+
# active_model/type/decimal
|
71
|
+
# active_model/type/float
|
72
|
+
# active_model/type/immutable_string
|
73
|
+
# active_model/type/integer
|
74
|
+
# active_model/type/string
|
75
|
+
# active_model/type/time
|
76
|
+
# active_model
|
77
|
+
|
78
|
+
DB_TYPE_MAPS = {
|
79
|
+
String => ActiveModel::Type::String,
|
80
|
+
Symbol => ActiveModel::Type::String,
|
81
|
+
Integer => ActiveModel::Type::BigInteger,
|
82
|
+
BigDecimal => ActiveRecord::Type::Decimal,
|
83
|
+
TrueClass => ActiveModel::Type::Boolean,
|
84
|
+
FalseClass => ActiveModel::Type::Boolean,
|
85
|
+
Date => ActiveModel::Type::Date,
|
86
|
+
DateTime => ActiveModel::Type::DateTime,
|
87
|
+
Time => ActiveModel::Type::Time,
|
88
|
+
Float => ActiveModel::Type::Float,
|
89
|
+
NilClass => ActiveModel::Type::Boolean,
|
90
|
+
Array => Proc.new{ |first_el_class| ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(DB_TYPE_MAPS[first_el_class].new) }
|
91
|
+
}
|
79
92
|
|
80
93
|
def convert_to_query_attribute(name, v)
|
81
94
|
#yes its dumb I know dont look at me look at rails
|
@@ -93,7 +106,11 @@ module DynamicRecordsMeritfront
|
|
93
106
|
|
94
107
|
type = DB_TYPE_MAPS[v.class]
|
95
108
|
if type.nil?
|
96
|
-
|
109
|
+
# if v.class == NilClass
|
110
|
+
# raise StandardError.new("")
|
111
|
+
# else
|
112
|
+
raise StandardError.new("#{name} (of value: #{v}, class: #{v.class}) unsupported class for ApplicationRecord#headache_sql")
|
113
|
+
# end
|
97
114
|
elsif type.class == Proc
|
98
115
|
a = v[0]
|
99
116
|
# if a.nil?
|
@@ -108,33 +125,33 @@ module DynamicRecordsMeritfront
|
|
108
125
|
end
|
109
126
|
end
|
110
127
|
|
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
|
-
|
128
|
+
class MultiRowExpression
|
129
|
+
#this class is meant to be used in congunction with headache_sql method
|
130
|
+
#Could be used like so in headache_sql:
|
131
|
+
|
132
|
+
#ApplicationRecord.headache_sql( "teeeest", %Q{
|
133
|
+
# INSERT INTO tests(id, username, is_awesome)
|
134
|
+
# VALUES :rows
|
135
|
+
# ON CONFLICT SET is_awesome = true
|
136
|
+
#}, rows: [[1, luke, true], [2, josh, false]])
|
137
|
+
|
138
|
+
#which would output this sql
|
139
|
+
|
140
|
+
# INSERT INTO tests(id, username, is_awesome)
|
141
|
+
# VALUES ($0,$1,$2),($3,$4,$5)
|
142
|
+
# ON CONFLICT SET is_awesome = true
|
143
|
+
|
144
|
+
attr_accessor :val
|
145
|
+
def initialize(val)
|
146
|
+
#assuming we are putting in an array of arrays.
|
147
|
+
self.val = val
|
148
|
+
end
|
149
|
+
def for_query(key, var_track)
|
150
|
+
#accepts x = current number of variables previously processed
|
151
|
+
#returns ["sql string with $# location information", variables themselves in order, new x]
|
152
|
+
x = -1
|
136
153
|
db_val = val.map{|attribute_array| "(#{
|
137
|
-
|
154
|
+
attribute_array.map{|attribute|
|
138
155
|
if attribute.kind_of? Symbol
|
139
156
|
#allow pointers to other more explicit variables through symbols
|
140
157
|
x = var_track.add_key_value(attribute, nil)
|
@@ -143,184 +160,237 @@ module DynamicRecordsMeritfront
|
|
143
160
|
x = var_track.add_key_value(k, attribute)
|
144
161
|
end
|
145
162
|
next "$" + x.to_s
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
163
|
+
}.join(",")
|
164
|
+
})"}.join(",")
|
165
|
+
return db_val
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def questionable_attribute_set(atr, value, as_default: false, push: false)
|
170
|
+
#this is needed on initalization of a new variable after the actual thing has been made already.
|
171
|
+
|
172
|
+
#note that the below is the value lookup for ActiveModel, but values seems to have all the database columns
|
173
|
+
#in it anyways
|
174
|
+
#
|
175
|
+
# def key?(name)
|
176
|
+
# (values.key?(name) || types.key?(name) || @attributes.key?(name)) && self[name].initialized?
|
177
|
+
# end
|
178
|
+
raise StandardError.new('bad options') if as_default and push
|
179
|
+
if as_default
|
180
|
+
unless self.respond_to? atr
|
181
|
+
#make sure its accesible in some way
|
182
|
+
values = @attributes.instance_variable_get(:@values)
|
183
|
+
if not values.keys.include?(atr)
|
184
|
+
values[atr] = value
|
185
|
+
end
|
186
|
+
end
|
187
|
+
else
|
188
|
+
#no getter/setter methodsout, probably catches missing methods and then redirects to attributes. Lots of magic.
|
189
|
+
# After multiple attempts, I gave up, so now we use eval. I guess I cant be too mad about magic as
|
190
|
+
# that seems to be my bread and butter. Hope eval doesnt make it go too slow. Guess everything is evaled
|
191
|
+
# on some level though?
|
192
|
+
s = self #afraid self will be a diffrent self in eval. Possibly depending on parser. IDK. Just seemed risky.
|
193
|
+
if push
|
194
|
+
eval "s.#{atr} << value"
|
195
|
+
else
|
196
|
+
eval "s.#{atr} = value"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# atr = atr.to_s
|
201
|
+
# setter = "#{atr}="
|
202
|
+
# if respond_to?(setter)
|
203
|
+
# #this allows us to attach to ActiveRecord relations and standard columns as we expect.
|
204
|
+
# #accessors etc will be triggered as expected.
|
205
|
+
# if push
|
206
|
+
# method(atr).call().push(value)
|
207
|
+
# else
|
208
|
+
# method(setter).call(value)
|
209
|
+
# end
|
210
|
+
# else
|
211
|
+
# #for non-standard columns (one that is not expected by the record),
|
212
|
+
# #this allows us to attach to the record, and access the value as we are acustomed to.
|
213
|
+
# #when you 'save!' it interestingly seems to know thats not a normal column expected by
|
214
|
+
# #the model, and will ignore it.
|
215
|
+
|
216
|
+
# values = @attributes.instance_variable_get(:@values)
|
217
|
+
# else
|
218
|
+
# if as_default
|
219
|
+
# self[atr] = value if self[atr].nil?
|
220
|
+
# else
|
221
|
+
# if push
|
222
|
+
# self[atr] << value
|
223
|
+
# else
|
224
|
+
# self[atr] = value
|
225
|
+
# end
|
226
|
+
# end
|
227
|
+
# end
|
228
|
+
# end
|
229
|
+
end
|
230
|
+
|
231
|
+
def inspect
|
232
|
+
#basically the same as the upstream active record function (as of october 25 2022 on AR V7.0.4)
|
233
|
+
#except that I changed self.class.attribute_names -> self.attribute_names to pick up our
|
234
|
+
#dynamic insanity. Was this a good idea? Well I guess its better than not doing it
|
235
|
+
inspection = if defined?(@attributes) && @attributes
|
236
|
+
self.attribute_names.filter_map do |name|
|
237
|
+
if _has_attribute?(name)
|
238
|
+
"#{name}: #{attribute_for_inspect(name)}"
|
239
|
+
end
|
240
|
+
end.join(", ")
|
241
|
+
else
|
242
|
+
"not initialized"
|
243
|
+
end
|
244
|
+
|
245
|
+
"#<#{self.class} #{inspection}>"
|
246
|
+
end
|
247
|
+
|
248
|
+
module ClassMethods
|
249
|
+
|
250
|
+
def has_run_migration?(nm)
|
251
|
+
#put in a string name of the class and it will say if it has allready run the migration.
|
252
|
+
#good during enum migrations as the code to migrate wont run if enumerate is there
|
253
|
+
#as it is not yet enumerated (causing an error when it loads the class that will have the
|
254
|
+
#enumeration in it). This can lead it to being impossible to commit clean code.
|
255
|
+
#
|
256
|
+
# example usage one: only create the record class if it currently exists in the database
|
257
|
+
# if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
|
258
|
+
# class UserImageRelation < ApplicationRecord
|
259
|
+
# belongs_to :imageable, polymorphic: true
|
260
|
+
# belongs_to :image
|
261
|
+
# end
|
262
|
+
# else
|
263
|
+
# class UserImageRelation; end
|
264
|
+
# end
|
265
|
+
# example usage two: only load relation if it exists in the database
|
266
|
+
# class UserImageRelation < ApplicationRecord
|
267
|
+
# if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
|
268
|
+
# belongs_to :imageable, polymorphic: true
|
269
|
+
# end
|
270
|
+
# end
|
271
|
+
#
|
272
|
+
#current version of migrations
|
273
|
+
cv = ActiveRecord::Base.connection.migration_context.current_version
|
274
|
+
|
275
|
+
#find the migration object for the name
|
276
|
+
migration = ActiveRecord::Base.connection.migration_context.migrations.filter!{|a|
|
277
|
+
a.name == nm
|
278
|
+
}.first
|
279
|
+
|
280
|
+
#if the migration object is nil, it has not yet been created
|
281
|
+
if migration.nil?
|
282
|
+
Rails.logger.info "No migration found for #{nm}. The migration has not yet been created, or is foreign to this database."
|
283
|
+
return false
|
284
|
+
end
|
285
|
+
|
286
|
+
#get the version number for the migration name
|
287
|
+
needed_version = migration.version
|
288
|
+
|
289
|
+
#if current version is above or equal, the migration has allready been run
|
290
|
+
migration_ran = (cv >= needed_version)
|
291
|
+
|
292
|
+
if migration_ran
|
293
|
+
Rails.logger.info "#{nm} migration was run on #{needed_version}. If old and all instances are migrated, consider removing code check."
|
294
|
+
else
|
295
|
+
Rails.logger.info "#{nm} migration has not run yet. This may lead to limited functionality"
|
296
|
+
end
|
297
|
+
|
298
|
+
return migration_ran
|
299
|
+
end
|
300
|
+
def list_associations
|
301
|
+
#lists associations (see has_association? below)
|
302
|
+
reflect_on_all_associations.map(&:name)
|
303
|
+
end
|
304
|
+
def has_association?(*args)
|
305
|
+
#checks whether current class has needed association (for example, checks it has comments)
|
306
|
+
#associations can be seen in has_many belongs_to and other similar methods
|
307
|
+
|
308
|
+
#flattens so you can pass self.has_association?(:comments, :baseable_comments) aswell as
|
309
|
+
# self.has_association?([:comments, :baseable_comments]) without issue
|
310
|
+
#
|
311
|
+
args = args.flatten.map { |a| a.to_sym }
|
312
|
+
associations = list_associations
|
313
|
+
(args.length == (associations & args).length)
|
314
|
+
end
|
315
|
+
def blind_hgid(id, tag: nil, encode: true)
|
316
|
+
# this method is to get an hgid for a class without actually calling it down from the database.
|
317
|
+
# For example Notification.blind_hgid 1 will give gid://PROJECT_NAME/Notification/69DAB69 etc.
|
318
|
+
if id.class == Integer and encode
|
319
|
+
id = self.encode_id id
|
320
|
+
end
|
321
|
+
gid = "gid://#{PROJECT_NAME}/#{self.to_s}/#{id}"
|
322
|
+
if !tag
|
323
|
+
gid
|
324
|
+
else
|
325
|
+
"#{gid}@#{tag}"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
def string_as_selector(str, attribute: 'id')
|
329
|
+
#this is needed to allow us to quey various strange characters in the id etc. (see hgids)
|
330
|
+
#also useful for querying various attributes
|
331
|
+
return "*[#{attribute}=\"#{str}\"]"
|
332
|
+
end
|
333
|
+
def locate_hgid(hgid_string, with_associations: nil, returns_nil: false)
|
334
|
+
if hgid_string == nil or hgid_string.class != String
|
335
|
+
if returns_nil
|
336
|
+
return nil
|
337
|
+
else
|
338
|
+
raise StandardError.new("non-string class passed to ApplicationRecord#locate_hgid as the hgid_string variable")
|
339
|
+
end
|
340
|
+
end
|
341
|
+
if hgid_string.include?('@')
|
342
|
+
hgid_string = hgid_string.split('@')
|
343
|
+
hgid_string.pop
|
344
|
+
hgid_string = hgid_string.join('@') # incase the model was a tag that was tagged. (few months later: Wtf? Guess ill keep it)
|
345
|
+
end
|
346
|
+
#split the thing
|
347
|
+
splitz = hgid_string.split('/')
|
348
|
+
#get the class
|
349
|
+
begin
|
350
|
+
cls = splitz[-2].constantize
|
351
|
+
rescue NameError, NoMethodError
|
352
|
+
if returns_nil
|
353
|
+
nil
|
354
|
+
else
|
355
|
+
raise StandardError.new 'Unusual or unavailable string or hgid'
|
356
|
+
end
|
357
|
+
end
|
358
|
+
#get the hash
|
359
|
+
hash = splitz[-1]
|
360
|
+
# if self == ApplicationRecord (for instance), then check that cls is a subclass
|
361
|
+
# if self is not ApplicationRecord, then check cls == this objects class
|
362
|
+
# if with_associations defined, make sure that the class has the associations given (see has_association above)
|
363
|
+
if ((self.abstract_class? and cls < self) or ( (not self.abstract_class?) and cls == self )) and
|
364
|
+
( with_associations == nil or cls.has_association?(with_associations) )
|
365
|
+
#if all is as expected, return the object with its id.
|
366
|
+
if block_given?
|
367
|
+
yield(hash)
|
368
|
+
else
|
369
|
+
cls.hfind(hash)
|
370
|
+
end
|
371
|
+
elsif returns_nil
|
372
|
+
#allows us to handle issues with input
|
373
|
+
nil
|
374
|
+
else
|
375
|
+
#stops execution as default
|
376
|
+
raise StandardError.new 'Not the expected class, or a subclass of ApplicationRecord if called on that.'
|
377
|
+
end
|
378
|
+
end
|
379
|
+
def get_hgid_tag(hgid_string)
|
380
|
+
if hgid_string.include?('@')
|
381
|
+
return hgid_string.split('@')[-1]
|
382
|
+
else
|
383
|
+
return nil
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
#allows us to preload on a list and not a active record relation. So basically from the output of headache_sql
|
388
|
+
def dynamic_preload(records, associations)
|
389
|
+
ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
|
390
|
+
end
|
391
|
+
|
392
|
+
alias headache_preload dynamic_preload
|
393
|
+
|
324
394
|
def dynamic_sql(*args) #see below for opts
|
325
395
|
# call like: dynamic_sql(name, sql, option_1: 1, option_2: 2)
|
326
396
|
# or like: dynamic_sql(sql, {option: 1, option_2: 2})
|
@@ -392,18 +462,27 @@ module DynamicRecordsMeritfront
|
|
392
462
|
#replace MultiRowExpressions
|
393
463
|
v = params[key]
|
394
464
|
#check if it looks like one
|
395
|
-
|
396
|
-
if v.class == MultiRowExpression or
|
465
|
+
looks_like_multi_row_expression = ((v.class == Array) and (not v.first.nil?) and (v.first.class == Array))
|
466
|
+
if v.class == MultiRowExpression or looks_like_multi_row_expression
|
397
467
|
#we need to substitute with the correct sql now.
|
398
|
-
v = MultiRowExpression.new(v) if
|
468
|
+
v = MultiRowExpression.new(v) if looks_like_multi_row_expression #standardize
|
399
469
|
#process into appropriate sql while keeping track of variables
|
400
470
|
sql_for_replace = v.for_query(key, var_track)
|
401
471
|
#replace the key with the sql
|
402
472
|
sql.gsub!(":#{key}", sql_for_replace)
|
403
473
|
else
|
404
|
-
|
405
|
-
|
406
|
-
|
474
|
+
#check if its currently in the sql argument list
|
475
|
+
x = var_track.key_index(key)
|
476
|
+
if x.nil?
|
477
|
+
#if not, get the next number that it will be assigned and replace the key w/ that number.
|
478
|
+
x = var_track.next_sql_num
|
479
|
+
if sql.gsub!(":#{key}", "$#{x}")
|
480
|
+
#only actually add to sql arguments when we know the attribute was used.
|
481
|
+
var_track.add_key_value(key, v)
|
482
|
+
end
|
483
|
+
else
|
484
|
+
#its already in use as a sql argument and has a number, use that number.
|
485
|
+
sql.gsub!(":#{key}", "$#{x}")
|
407
486
|
end
|
408
487
|
end
|
409
488
|
end
|
@@ -437,12 +516,6 @@ module DynamicRecordsMeritfront
|
|
437
516
|
#no I am not actually this cool see https://stackoverflow.com/questions/30826015/convert-pgresult-to-an-active-record-model
|
438
517
|
ret = ret.to_a
|
439
518
|
return ret.map{|r| dynamic_init(instantiate_class, r)}
|
440
|
-
# fields = ret.columns
|
441
|
-
# vals = ret.rows
|
442
|
-
# ret = vals.map { |v|
|
443
|
-
# dynamic_init()
|
444
|
-
# instantiate_class.instantiate(Hash[fields.zip(v)])
|
445
|
-
# }
|
446
519
|
else
|
447
520
|
if raw
|
448
521
|
return ret
|
@@ -451,39 +524,53 @@ module DynamicRecordsMeritfront
|
|
451
524
|
end
|
452
525
|
end
|
453
526
|
end
|
454
|
-
|
527
|
+
alias headache_sql dynamic_sql
|
455
528
|
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
529
|
+
def _dynamic_instaload_handle_with_statements(with_statements)
|
530
|
+
%Q{WITH #{
|
531
|
+
with_statements.map{|ws|
|
532
|
+
"#{ws[:table_name]} AS (\n#{ws[:sql]}\n)"
|
533
|
+
}.join(", \n")
|
461
534
|
}}
|
462
|
-
|
535
|
+
end
|
463
536
|
|
464
|
-
|
465
|
-
|
537
|
+
def _dynamic_instaload_union(insta_array)
|
538
|
+
insta_array.select{|insta|
|
466
539
|
not insta[:dont_return]
|
467
540
|
}.map{|insta|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
def instaload(sql, table_name: nil, relied_on: false, dont_return: false)
|
480
|
-
|
541
|
+
start = "SELECT row_to_json(#{insta[:table_name]}.*) AS row, '#{insta[:klass]}' AS _klass, '#{insta[:table_name]}' AS _table_name FROM "
|
542
|
+
if insta[:relied_on]
|
543
|
+
ending = "#{insta[:table_name]}\n"
|
544
|
+
else
|
545
|
+
ending = "(\n#{insta[:sql]}\n) AS #{insta[:table_name]}\n"
|
546
|
+
end
|
547
|
+
next start + ending
|
548
|
+
}.join(" UNION ALL \n")
|
549
|
+
#{ other_statements.map{|os| "SELECT row_to_json(#{os[:table_name]}.*) AS row, '#{os[:klass]}' AS _klass FROM (\n#{os[:sql]}\n)) AS #{os[:table_name]}\n" }.join(' UNION ALL ')}
|
550
|
+
end
|
551
|
+
|
552
|
+
def instaload(sql, table_name: nil, relied_on: false, dont_return: false, base_name: nil, base_on: nil, attach_on: nil, one_to_one: false, as: nil)
|
553
|
+
#this function just makes everything a little easier to deal with by providing defaults, making it nicer to call, and converting potential symbols to strings.
|
554
|
+
#At the end of the day it just returns a hash with the settings in it though. So dont overthink it too much.
|
555
|
+
|
556
|
+
as = as.to_s if as
|
557
|
+
base_name = base_name.to_s if base_name
|
558
|
+
|
559
|
+
if table_name
|
560
|
+
table_name = table_name.to_s
|
561
|
+
else
|
562
|
+
table_name = "_" + self.to_s.underscore.downcase.pluralize
|
563
|
+
end
|
564
|
+
|
481
565
|
klass = self.to_s
|
566
|
+
|
482
567
|
sql = "\t" + sql.strip
|
483
|
-
|
568
|
+
raise StandardError.new("base_on needs to be nil or a Proc") unless base_on.nil? or base_on.kind_of? Proc
|
569
|
+
raise StandardError.new("attach_on needs to be nil or a Proc") unless attach_on.nil? or attach_on.kind_of? Proc
|
570
|
+
return {table_name: table_name, klass: klass, sql: sql, relied_on: relied_on, dont_return: dont_return, base_name: base_name, base_on: base_on, attach_on: attach_on, one_to_one: one_to_one, as: as}
|
484
571
|
end
|
485
572
|
|
486
|
-
|
573
|
+
def instaload_sql(*args) #name, insta_array, opts = { })
|
487
574
|
args << {} unless args[-1].kind_of? Hash
|
488
575
|
if args.length == 3
|
489
576
|
name, insta_array, opts = args
|
@@ -494,269 +581,324 @@ module DynamicRecordsMeritfront
|
|
494
581
|
raise StandardError.new("bad input to DynamicRecordsMeritfront#instaload_sql method.")
|
495
582
|
end
|
496
583
|
|
497
|
-
|
498
|
-
|
584
|
+
with_statements = insta_array.select{|a| a[:relied_on]}
|
585
|
+
sql = %Q{
|
499
586
|
#{ _dynamic_instaload_handle_with_statements(with_statements) if with_statements.any? }
|
500
587
|
#{ _dynamic_instaload_union(insta_array)}
|
501
588
|
}
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
589
|
+
insta_array = insta_array.select{|ar| not ar[:dont_return]}
|
590
|
+
ret_hash = insta_array.map{|ar| [ar[:table_name].to_s, []]}.to_h
|
591
|
+
opts[:raw] = true
|
592
|
+
ApplicationRecord.headache_sql(name, sql, opts).rows.each{|row|
|
593
|
+
#need to pre-parsed as it has a non-normal output.
|
594
|
+
table_name = row[2]
|
595
|
+
klass = row[1].constantize
|
596
|
+
json = row[0]
|
597
|
+
parsed = JSON.parse(json)
|
598
|
+
|
599
|
+
ret_hash[table_name].push dynamic_init(klass, parsed)
|
600
|
+
}
|
601
|
+
|
602
|
+
insta_array = insta_array.map{|a|a.delete(:sql); next a} #better for debuggin and we dont need it anymore
|
603
|
+
|
604
|
+
#formatting options
|
605
|
+
for insta in insta_array
|
606
|
+
if insta[:base_name]
|
607
|
+
if insta[:as]
|
608
|
+
Rails.logger.debug "#{insta[:table_name]} as #{insta[:as]} -> #{insta[:base_name]}"
|
609
|
+
else
|
610
|
+
Rails.logger.debug "#{insta[:table_name]} -> #{insta[:base_name]}"
|
611
|
+
end
|
612
|
+
#in this case, 'as' is meant as to what pseudonym to dynamicly attach it as
|
613
|
+
dynamic_attach(ret_hash, insta[:base_name], insta[:table_name], base_on: insta[:base_on], attach_on: insta[:attach_on],
|
614
|
+
one_to_one: insta[:one_to_one], as: insta[:as])
|
615
|
+
elsif insta[:as]
|
616
|
+
Rails.logger.debug "#{insta[:table_name]} as #{insta[:as]}"
|
617
|
+
#in this case, the idea is more polymorphic in nature. unless they are confused and just want to rename the table (this can be done with
|
618
|
+
# table_name)
|
619
|
+
if ret_hash[insta[:as]]
|
620
|
+
ret_hash[insta[:as]] += ret_hash[insta[:table_name]]
|
621
|
+
else
|
622
|
+
ret_hash[insta[:as]] = ret_hash[insta[:table_name]].dup #only top level dup
|
623
|
+
end
|
624
|
+
else
|
625
|
+
Rails.logger.debug "#{insta[:table_name]}"
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
return ret_hash
|
630
|
+
end
|
631
|
+
alias swiss_instaload_sql instaload_sql
|
517
632
|
alias dynamic_instaload_sql instaload_sql
|
518
633
|
|
519
634
|
def test_drmf(model_with_an_id_column_and_timestamps)
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
635
|
+
m = model_with_an_id_column_and_timestamps
|
636
|
+
ar = m.superclass
|
637
|
+
mtname = m.table_name
|
638
|
+
ApplicationRecord.transaction do
|
639
|
+
puts 'test recieving columns not normally in the record.'
|
640
|
+
rec = m.dynamic_sql(%Q{
|
641
|
+
SELECT id, 5 AS random_column from #{mtname} LIMIT 10
|
642
|
+
}).first
|
643
|
+
raise StandardError.new('no id') unless rec.id
|
644
|
+
raise StandardError.new('no dynamic column') unless rec.random_column
|
645
|
+
puts 'pass 1'
|
646
|
+
|
647
|
+
puts 'test raw off with a custom name'
|
648
|
+
recs = ar.dynamic_sql('test_2', %Q{
|
649
|
+
SELECT id, 5 AS random_column from #{mtname} LIMIT 10
|
650
|
+
}, raw: false)
|
651
|
+
raise StandardError.new('not array of hashes') unless recs.first.class == Hash and recs.class == Array
|
652
|
+
rec = recs.first
|
653
|
+
raise StandardError.new('no id [raw off]') unless rec['id']
|
654
|
+
raise StandardError.new('no dynamic column [raw off]') unless rec['random_column']
|
655
|
+
puts 'pass 2'
|
656
|
+
|
657
|
+
puts 'test raw on'
|
658
|
+
recs = ar.dynamic_sql('test_3', %Q{
|
659
|
+
SELECT id, 5 AS random_column from #{mtname} LIMIT 10
|
660
|
+
}, raw: true)
|
661
|
+
raise StandardError.new('not raw') unless recs.class == ActiveRecord::Result
|
662
|
+
rec = recs.first
|
663
|
+
raise StandardError.new('no id [raw]') unless rec['id']
|
664
|
+
raise StandardError.new('no dynamic column [raw]') unless rec['random_column']
|
665
|
+
puts 'pass 3'
|
666
|
+
|
667
|
+
puts 'test when some of the variables are diffrent then the same (#see version 3.0.1 notes)'
|
668
|
+
x = Proc.new { |a, b|
|
669
|
+
recs = ar.dynamic_sql('test_4', %Q{
|
670
|
+
SELECT id, 5 AS random_column from #{mtname} WHERE id > :a LIMIT :b
|
671
|
+
}, a: a, b: b)
|
672
|
+
}
|
673
|
+
x.call(1, 2)
|
674
|
+
x.call(1, 1)
|
675
|
+
puts 'pass 4'
|
676
|
+
|
677
|
+
puts 'test MultiAttributeArrays, including symbols and duplicate values.'
|
678
|
+
time = DateTime.now
|
679
|
+
ids = m.limit(5).pluck(:id)
|
680
|
+
values = ids.map{|id|
|
681
|
+
[id, :time, time]
|
682
|
+
}
|
683
|
+
ar.dynamic_sql(%Q{
|
684
|
+
INSERT INTO #{mtname} (id, created_at, updated_at)
|
685
|
+
VALUES :values
|
686
|
+
ON CONFLICT (id) DO NOTHING
|
687
|
+
}, values: values, time: time)
|
688
|
+
puts 'pass 5'
|
689
|
+
|
690
|
+
puts 'test arrays'
|
691
|
+
recs = ar.dynamic_sql(%Q{
|
692
|
+
SELECT id from #{mtname} where id = ANY(:idz)
|
693
|
+
}, idz: ids, raw: false)
|
694
|
+
puts recs
|
695
|
+
raise StandardError.new('wrong length') if recs.length != 5
|
696
|
+
puts 'pass 6'
|
697
|
+
|
698
|
+
|
699
|
+
puts 'test instaload_sql'
|
700
|
+
out = ar.instaload_sql([
|
701
|
+
ar.instaload("SELECT id FROM users", relied_on: true, dont_return: true, table_name: "users_2"),
|
702
|
+
ar.instaload("SELECT id FROM users_2 WHERE id % 2 != 0 LIMIT :limit", table_name: 'a'),
|
703
|
+
m.instaload("SELECT id FROM users_2 WHERE id % 2 != 1 LIMIT :limit", table_name: 'b')
|
704
|
+
], limit: 2)
|
705
|
+
puts out
|
706
|
+
raise StandardError.new('Bad return') if out["users_2"]
|
707
|
+
raise StandardError.new('Bad return') unless out["a"]
|
708
|
+
raise StandardError.new('Bad return') unless out["b"]
|
709
|
+
puts 'pass 7'
|
710
|
+
|
711
|
+
puts "test dynamic_sql V3.0.6 error to do with multi_attribute_arrays which is hard to describe"
|
712
|
+
time = DateTime.now
|
713
|
+
values = [[1, :time, :time], [2, :time, :time]]
|
714
|
+
out = ar.dynamic_sql(%Q{
|
715
|
+
insert into #{mtname} (id, created_at, updated_at)
|
716
|
+
values :values
|
717
|
+
on conflict (id)
|
718
|
+
do update set updated_at = :time
|
719
|
+
}, time: time, values: values)
|
720
|
+
puts 'pass 8'
|
721
|
+
|
722
|
+
raise ActiveRecord::Rollback
|
723
|
+
#ApplicationRecord.dynamic_sql("SELECT * FROM")
|
724
|
+
end
|
725
|
+
end
|
726
|
+
|
727
|
+
def dynamic_attach(instaload_sql_output, base_name, attach_name, base_on: nil, attach_on: nil, one_to_one: false, as: nil)
|
728
|
+
#as just lets it attach us anywhere on the base class, and not just as the attach_name.
|
729
|
+
#Can be useful in polymorphic situations, otherwise may lead to confusion.
|
730
|
+
as ||= attach_name
|
731
|
+
|
732
|
+
base_arr = instaload_sql_output[base_name]
|
733
|
+
|
734
|
+
#return if there is nothing for us to attach to.
|
735
|
+
return if base_arr.nil? or not base_arr.any?
|
736
|
+
|
737
|
+
#set variables for neatness and so we dont compute each time
|
738
|
+
# base class information
|
739
|
+
base_class = base_arr.first.class
|
740
|
+
base_class_is_hash = base_class <= Hash
|
741
|
+
|
742
|
+
#variable accessors and defaults. Make sure it only sets if not defined already as
|
743
|
+
#the 'as' option allows us to override to what value it actually gets set in the end,
|
744
|
+
#and in polymorphic situations this could be called in multiple instances
|
745
|
+
base_arr.each{ |o|
|
746
|
+
if not base_class_is_hash
|
747
|
+
if one_to_one
|
748
|
+
#attach name must be a string
|
749
|
+
o.questionable_attribute_set(as, nil, as_default: true)
|
750
|
+
else
|
751
|
+
o.questionable_attribute_set(as, [], as_default: true)
|
752
|
+
end
|
753
|
+
elsif not one_to_one
|
754
|
+
o[as] ||= []
|
755
|
+
end
|
756
|
+
}
|
757
|
+
|
758
|
+
#make sure the attach class has something going on. We do this after the default stage
|
759
|
+
attach_arr = instaload_sql_output[attach_name]
|
760
|
+
return if attach_arr.nil? or not attach_arr.any?
|
761
|
+
|
762
|
+
# attach class information
|
763
|
+
attach_class = attach_arr.first.class
|
764
|
+
attach_class_is_hash = attach_class <= Hash
|
765
|
+
|
766
|
+
# default attach column info
|
767
|
+
default_attach_col = (base_class.to_s.downcase + "_id")
|
768
|
+
|
769
|
+
#decide on the method of getting the matching id for the base table
|
770
|
+
unless base_on
|
771
|
+
if base_class_is_hash
|
772
|
+
base_on = Proc.new{|x| x['id']}
|
773
|
+
else
|
774
|
+
base_on = Proc.new{|x| x.id}
|
775
|
+
end
|
776
|
+
end
|
777
|
+
|
778
|
+
#return an id->object hash for the base table for better access
|
779
|
+
h = base_arr.map{|o|
|
780
|
+
[base_on.call(o), o]
|
781
|
+
}.to_h
|
782
|
+
|
783
|
+
#decide on the method of getting the matching id for the attach table
|
784
|
+
unless attach_on
|
785
|
+
if attach_class_is_hash
|
786
|
+
attach_on = Proc.new{|x| x[default_attach_col]}
|
787
|
+
else
|
788
|
+
attach_on = Proc.new{|x|
|
789
|
+
x.attributes[default_attach_col]
|
790
|
+
}
|
791
|
+
end
|
792
|
+
end
|
793
|
+
|
794
|
+
# if debug
|
795
|
+
# Rails.logger.info(base_arr.map{|b|
|
796
|
+
# base_on.call(b)
|
797
|
+
# })
|
798
|
+
# Rails.logger.info(attach_arr.map{|a|
|
799
|
+
# attach_on.call(a)
|
800
|
+
# })
|
801
|
+
# end
|
802
|
+
|
803
|
+
#method of adding the object to the base
|
804
|
+
#(b=base, a=attach)
|
805
|
+
add_to_base = Proc.new{|b, a|
|
806
|
+
if base_class_is_hash
|
807
|
+
if one_to_one
|
808
|
+
b[as] = a
|
809
|
+
else
|
810
|
+
b[as].push a
|
811
|
+
end
|
812
|
+
else
|
813
|
+
#getting a lil tired of the meta stuff.
|
814
|
+
if one_to_one
|
815
|
+
b.questionable_attribute_set(as, a)
|
816
|
+
else
|
817
|
+
b.questionable_attribute_set(as, a, push: true)
|
818
|
+
end
|
819
|
+
end
|
820
|
+
}
|
821
|
+
|
822
|
+
#for every attachable
|
823
|
+
# 1. match base id to the attach id (both configurable)
|
824
|
+
# 2. cancel out if there is no match
|
825
|
+
# 3. otherwise add to the base object.
|
826
|
+
attach_arr.each{|attach|
|
827
|
+
out = attach_on.call(attach) #you can use null to escape the vals
|
828
|
+
if out.nil?
|
829
|
+
Rails.logger.debug "attach_on proc output (which compares to the base_on proc) is outputting nil, this could be a problem depending on your use-case."
|
830
|
+
end
|
831
|
+
if out
|
832
|
+
base = h[out] #it is also escaped if no base element is found
|
833
|
+
if base
|
834
|
+
add_to_base.call(base, attach)
|
835
|
+
end
|
836
|
+
end
|
837
|
+
}
|
838
|
+
|
839
|
+
return attach_arr
|
840
|
+
end
|
841
|
+
alias swiss_attach dynamic_attach
|
842
|
+
|
843
|
+
def zip_ar_result(x)
|
844
|
+
x.to_a
|
845
|
+
end
|
846
|
+
|
847
|
+
def dynamic_init(klass, input)
|
848
|
+
if klass.abstract_class
|
849
|
+
return input
|
850
|
+
else
|
851
|
+
record = klass.instantiate(input.stringify_keys ) #trust me they need to be stringified
|
852
|
+
# #handle attributes through ar if allowed. Throws an error on unkown variables, except apparently for devise classes? 😡
|
853
|
+
# active_record_handled = input.slice(*(klass.attribute_names & input.keys))
|
854
|
+
# record = klass.instantiate(active_record_handled)
|
855
|
+
# #set those that were not necessarily expected
|
856
|
+
# not_expected = input.slice(*(input.keys - klass.attribute_names))
|
857
|
+
# record.dynamic = OpenStruct.new(not_expected.transform_keys{|k|k.to_sym}) if not_expected.keys.any?
|
858
|
+
return record
|
859
|
+
end
|
860
|
+
end
|
861
|
+
|
862
|
+
def quick_safe_increment(id, col, val)
|
863
|
+
where(id: id).update_all("#{col} = #{col} + #{val}")
|
864
|
+
end
|
865
|
+
|
866
|
+
|
867
|
+
end
|
868
|
+
|
869
|
+
def list_associations
|
870
|
+
#lists associations (see class method above)
|
871
|
+
self.class.list_associations
|
872
|
+
end
|
873
|
+
|
874
|
+
def has_association?(*args)
|
875
|
+
#just redirects to the class method for ease of use (see class method above)
|
876
|
+
self.class.has_association?(*args)
|
877
|
+
end
|
878
|
+
|
879
|
+
# custom hash GlobalID
|
880
|
+
def hgid(tag: nil)
|
881
|
+
gid = "gid://#{PROJECT_NAME}/#{self.class.to_s}/#{self.hid}"
|
882
|
+
if !tag
|
883
|
+
gid
|
884
|
+
else
|
885
|
+
"#{gid}@#{tag}"
|
886
|
+
end
|
887
|
+
end
|
888
|
+
alias ghid hgid #its worth it trust me the amount of times i go 'is it hash global id or global hashid?'
|
889
|
+
|
890
|
+
def hgid_as_selector(tag: nil, attribute: 'id')
|
891
|
+
#https://www.javascripttutorial.net/javascript-dom/javascript-queryselector/
|
892
|
+
gidstr = hgid(tag: tag).to_s
|
893
|
+
return self.class.string_as_selector(gidstr, attribute: attribute)
|
894
|
+
end
|
895
|
+
|
896
|
+
#just for ease of use
|
897
|
+
def headache_preload(records, associations)
|
898
|
+
self.class.headache_preload(records, associations)
|
899
|
+
end
|
900
|
+
def safe_increment(col, val) #also used in follow, also used in comment#kill
|
901
|
+
self.class.where(id: self.id).update_all("#{col} = #{col} + #{val}")
|
902
|
+
end
|
761
903
|
|
762
904
|
end
|