dynamic-records-meritfront 3.0.11 → 3.0.24
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 +9 -2
- data/lib/dynamic-records-meritfront/version.rb +1 -1
- data/lib/dynamic-records-meritfront.rb +712 -533
- metadata +2 -2
@@ -4,23 +4,27 @@ require 'hashid/rails'
|
|
4
4
|
#this file contains multiple classes which should honestly be split up
|
5
5
|
|
6
6
|
module DynamicRecordsMeritfront
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
22
24
|
DYNAMIC_SQL_RAW = true
|
23
|
-
|
25
|
+
attr_accessor :dynamic_reflections
|
26
|
+
end
|
27
|
+
|
24
28
|
class DynamicSqlVariables
|
25
29
|
attr_accessor :sql_hash
|
26
30
|
attr_accessor :params
|
@@ -52,38 +56,39 @@ module DynamicRecordsMeritfront
|
|
52
56
|
end
|
53
57
|
|
54
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
|
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
|
-
|
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
|
+
}
|
87
92
|
|
88
93
|
def convert_to_query_attribute(name, v)
|
89
94
|
#yes its dumb I know dont look at me look at rails
|
@@ -116,33 +121,33 @@ module DynamicRecordsMeritfront
|
|
116
121
|
end
|
117
122
|
end
|
118
123
|
|
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
|
-
|
124
|
+
class MultiRowExpression
|
125
|
+
#this class is meant to be used in congunction with headache_sql method
|
126
|
+
#Could be used like so in headache_sql:
|
127
|
+
|
128
|
+
#ApplicationRecord.headache_sql( "teeeest", %Q{
|
129
|
+
# INSERT INTO tests(id, username, is_awesome)
|
130
|
+
# VALUES :rows
|
131
|
+
# ON CONFLICT SET is_awesome = true
|
132
|
+
#}, rows: [[1, luke, true], [2, josh, false]])
|
133
|
+
|
134
|
+
#which would output this sql
|
135
|
+
|
136
|
+
# INSERT INTO tests(id, username, is_awesome)
|
137
|
+
# VALUES ($0,$1,$2),($3,$4,$5)
|
138
|
+
# ON CONFLICT SET is_awesome = true
|
139
|
+
|
140
|
+
attr_accessor :val
|
141
|
+
def initialize(val)
|
142
|
+
#assuming we are putting in an array of arrays.
|
143
|
+
self.val = val
|
144
|
+
end
|
145
|
+
def for_query(key, var_track)
|
146
|
+
#accepts x = current number of variables previously processed
|
147
|
+
#returns ["sql string with $# location information", variables themselves in order, new x]
|
148
|
+
x = -1
|
144
149
|
db_val = val.map{|attribute_array| "(#{
|
145
|
-
|
150
|
+
attribute_array.map{|attribute|
|
146
151
|
if attribute.kind_of? Symbol
|
147
152
|
#allow pointers to other more explicit variables through symbols
|
148
153
|
x = var_track.add_key_value(attribute, nil)
|
@@ -151,184 +156,300 @@ module DynamicRecordsMeritfront
|
|
151
156
|
x = var_track.add_key_value(k, attribute)
|
152
157
|
end
|
153
158
|
next "$" + x.to_s
|
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
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
159
|
+
}.join(",")
|
160
|
+
})"}.join(",")
|
161
|
+
return db_val
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def questionable_attribute_set(atr, value, as_default: false, push: false)
|
166
|
+
#this is needed on initalization of a new variable after the actual thing has been made already.
|
167
|
+
#this is used for attaching records to other records in one-to-one or one-to-many
|
168
|
+
|
169
|
+
#basically the way this works is by using singletons to paper over the fact that normal reflections
|
170
|
+
#even exist. We dont integrate at all with their patterns as they use some crazy delegation stuff
|
171
|
+
#that messes just about everything up.
|
172
|
+
|
173
|
+
#man i thought i was meta coding, these association people just want to see the world burn.
|
174
|
+
|
175
|
+
#keeping the old code commented for a while because this area keeps breaking and i want a log of what i have tried.
|
176
|
+
|
177
|
+
self.dynamic_reflections ||= []
|
178
|
+
|
179
|
+
unless dynamic_reflections.include?(atr.to_s)
|
180
|
+
self.dynamic_reflections << atr.to_s
|
181
|
+
singleton_class.instance_eval do
|
182
|
+
attr_accessor atr.to_sym
|
183
|
+
end
|
184
|
+
end
|
185
|
+
# # if _reflections.keys.include? atr.to_s
|
186
|
+
# has_method = methods.include?(atr.to_sym)
|
187
|
+
#
|
188
|
+
# DevScript.ping(has_method)
|
189
|
+
# override = (not(has_method) or (
|
190
|
+
# _reflections.keys.include? atr.to_s
|
191
|
+
# and not
|
192
|
+
|
193
|
+
# )
|
194
|
+
# DevScript.ping(override)
|
195
|
+
|
196
|
+
# if override
|
197
|
+
|
198
|
+
|
199
|
+
# end
|
200
|
+
#elsif
|
201
|
+
|
202
|
+
#end
|
203
|
+
|
204
|
+
if as_default
|
205
|
+
if self.method(atr.to_sym).call().nil?
|
206
|
+
self.method("#{atr}=".to_sym).call(value)
|
207
|
+
# DevScript.ping("atr #{atr} def #{value}")
|
208
|
+
end
|
209
|
+
elsif push
|
210
|
+
self.method(atr.to_sym).call().push value
|
211
|
+
# DevScript.ping("atr #{atr} push #{value}")
|
212
|
+
else
|
213
|
+
self.method("#{atr}=".to_sym).call(value)
|
214
|
+
# DevScript.ping("atr #{atr} set #{value}")
|
215
|
+
end
|
216
|
+
|
217
|
+
# raise StandardError.new('bad options') if as_default and push
|
218
|
+
# if as_default
|
219
|
+
# unless self.respond_to? atr
|
220
|
+
# #make sure its accesible in some way
|
221
|
+
# values = @attributes.instance_variable_get(:@values)
|
222
|
+
# if not values.keys.include?(atr)
|
223
|
+
# values[atr] = value
|
224
|
+
# end
|
225
|
+
# end
|
226
|
+
# else
|
227
|
+
# if self.reflections.keys.include? atr.to_s
|
228
|
+
#
|
229
|
+
# else
|
230
|
+
# values ||= @attributes.instance_variable_get(:@values)
|
231
|
+
# values[atr] << value
|
232
|
+
#
|
233
|
+
#
|
234
|
+
# end
|
235
|
+
# #no getter/setter methodsout, probably catches missing methods and then redirects to attributes. Lots of magic.
|
236
|
+
# # After multiple attempts, I gave up, so now we use eval. I guess I cant be too mad about magic as
|
237
|
+
# # that seems to be my bread and butter. Hope eval doesnt make it go too slow. Guess everything is evaled
|
238
|
+
# # on some level though?
|
239
|
+
# s = self #afraid self will be a diffrent self in eval. Possibly depending on parser. IDK. Just seemed risky.
|
240
|
+
# if push
|
241
|
+
# eval "s.#{atr} << value"
|
242
|
+
# else
|
243
|
+
# eval "s.#{atr} = value"
|
244
|
+
# end
|
245
|
+
# end
|
246
|
+
|
247
|
+
# atr = atr.to_s
|
248
|
+
# setter = "#{atr}="
|
249
|
+
# if respond_to?(setter)
|
250
|
+
# #this allows us to attach to ActiveRecord relations and standard columns as we expect.
|
251
|
+
# #accessors etc will be triggered as expected.
|
252
|
+
# if push
|
253
|
+
# method(atr).call().push(value)
|
254
|
+
# else
|
255
|
+
# method(setter).call(value)
|
256
|
+
# end
|
257
|
+
# else
|
258
|
+
# #for non-standard columns (one that is not expected by the record),
|
259
|
+
# #this allows us to attach to the record, and access the value as we are acustomed to.
|
260
|
+
# #when you 'save!' it interestingly seems to know thats not a normal column expected by
|
261
|
+
# #the model, and will ignore it.
|
262
|
+
|
263
|
+
# values = @attributes.instance_variable_get(:@values)
|
264
|
+
# else
|
265
|
+
# if as_default
|
266
|
+
# self[atr] = value if self[atr].nil?
|
267
|
+
# else
|
268
|
+
# if push
|
269
|
+
# self[atr] << value
|
270
|
+
# else
|
271
|
+
# self[atr] = value
|
272
|
+
# end
|
273
|
+
# end
|
274
|
+
# end
|
275
|
+
# end
|
276
|
+
end
|
277
|
+
|
278
|
+
def inspect
|
279
|
+
#basically the same as the upstream active record function (as of october 25 2022 on AR V7.0.4)
|
280
|
+
#except that I changed self.class.attribute_names -> self.attribute_names to pick up our
|
281
|
+
#dynamic insanity. Was this a good idea? Well I guess its better than not doing it
|
282
|
+
|
283
|
+
#I also added dynamic_reflections
|
284
|
+
|
285
|
+
inspection = if defined?(@attributes) && @attributes
|
286
|
+
self.attribute_names.filter_map do |name|
|
287
|
+
if _has_attribute?(name)
|
288
|
+
"#{name}: #{attribute_for_inspect(name)}"
|
289
|
+
end
|
290
|
+
end.join(", ")
|
291
|
+
else
|
292
|
+
"not initialized"
|
293
|
+
end
|
294
|
+
|
295
|
+
self.dynamic_reflections ||= []
|
296
|
+
dyna = dynamic_reflections.map{|dr|
|
297
|
+
self.method(dr.to_sym).call()
|
298
|
+
}
|
299
|
+
|
300
|
+
if dyna.any?
|
301
|
+
"#<#{self.class} #{inspection} | #{dyna.to_s}>"
|
302
|
+
else
|
303
|
+
"#<#{self.class} #{inspection} >"
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
module ClassMethods
|
308
|
+
|
309
|
+
def has_run_migration?(nm)
|
310
|
+
#put in a string name of the class and it will say if it has allready run the migration.
|
311
|
+
#good during enum migrations as the code to migrate wont run if enumerate is there
|
312
|
+
#as it is not yet enumerated (causing an error when it loads the class that will have the
|
313
|
+
#enumeration in it). This can lead it to being impossible to commit clean code.
|
314
|
+
#
|
315
|
+
# example usage one: only create the record class if it currently exists in the database
|
316
|
+
# if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
|
317
|
+
# class UserImageRelation < ApplicationRecord
|
318
|
+
# belongs_to :imageable, polymorphic: true
|
319
|
+
# belongs_to :image
|
320
|
+
# end
|
321
|
+
# else
|
322
|
+
# class UserImageRelation; end
|
323
|
+
# end
|
324
|
+
# example usage two: only load relation if it exists in the database
|
325
|
+
# class UserImageRelation < ApplicationRecord
|
326
|
+
# if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
|
327
|
+
# belongs_to :imageable, polymorphic: true
|
328
|
+
# end
|
329
|
+
# end
|
330
|
+
#
|
331
|
+
#current version of migrations
|
332
|
+
cv = ActiveRecord::Base.connection.migration_context.current_version
|
333
|
+
|
334
|
+
#find the migration object for the name
|
335
|
+
migration = ActiveRecord::Base.connection.migration_context.migrations.filter!{|a|
|
336
|
+
a.name == nm
|
337
|
+
}.first
|
338
|
+
|
339
|
+
#if the migration object is nil, it has not yet been created
|
340
|
+
if migration.nil?
|
341
|
+
Rails.logger.info "No migration found for #{nm}. The migration has not yet been created, or is foreign to this database."
|
342
|
+
return false
|
343
|
+
end
|
344
|
+
|
345
|
+
#get the version number for the migration name
|
346
|
+
needed_version = migration.version
|
347
|
+
|
348
|
+
#if current version is above or equal, the migration has allready been run
|
349
|
+
migration_ran = (cv >= needed_version)
|
350
|
+
|
351
|
+
if migration_ran
|
352
|
+
Rails.logger.info "#{nm} migration was run on #{needed_version}. If old and all instances are migrated, consider removing code check."
|
353
|
+
else
|
354
|
+
Rails.logger.info "#{nm} migration has not run yet. This may lead to limited functionality"
|
355
|
+
end
|
356
|
+
|
357
|
+
return migration_ran
|
358
|
+
end
|
359
|
+
def list_associations
|
360
|
+
#lists associations (see has_association? below)
|
361
|
+
reflect_on_all_associations.map(&:name)
|
362
|
+
end
|
363
|
+
def has_association?(*args)
|
364
|
+
#checks whether current class has needed association (for example, checks it has comments)
|
365
|
+
#associations can be seen in has_many belongs_to and other similar methods
|
366
|
+
|
367
|
+
#flattens so you can pass self.has_association?(:comments, :baseable_comments) aswell as
|
368
|
+
# self.has_association?([:comments, :baseable_comments]) without issue
|
369
|
+
#
|
370
|
+
args = args.flatten.map { |a| a.to_sym }
|
371
|
+
associations = list_associations
|
372
|
+
(args.length == (associations & args).length)
|
373
|
+
end
|
374
|
+
def blind_hgid(id, tag: nil, encode: true)
|
375
|
+
# this method is to get an hgid for a class without actually calling it down from the database.
|
376
|
+
# For example Notification.blind_hgid 1 will give gid://PROJECT_NAME/Notification/69DAB69 etc.
|
377
|
+
if id.class == Integer and encode
|
378
|
+
id = self.encode_id id
|
379
|
+
end
|
380
|
+
gid = "gid://#{PROJECT_NAME}/#{self.to_s}/#{id}"
|
381
|
+
if !tag
|
382
|
+
gid
|
383
|
+
else
|
384
|
+
"#{gid}@#{tag}"
|
385
|
+
end
|
386
|
+
end
|
387
|
+
def string_as_selector(str, attribute: 'id')
|
388
|
+
#this is needed to allow us to quey various strange characters in the id etc. (see hgids)
|
389
|
+
#also useful for querying various attributes
|
390
|
+
return "*[#{attribute}=\"#{str}\"]"
|
391
|
+
end
|
392
|
+
def locate_hgid(hgid_string, with_associations: nil, returns_nil: false)
|
393
|
+
if hgid_string == nil or hgid_string.class != String
|
394
|
+
if returns_nil
|
395
|
+
return nil
|
396
|
+
else
|
397
|
+
raise StandardError.new("non-string class passed to ApplicationRecord#locate_hgid as the hgid_string variable")
|
398
|
+
end
|
399
|
+
end
|
400
|
+
if hgid_string.include?('@')
|
401
|
+
hgid_string = hgid_string.split('@')
|
402
|
+
hgid_string.pop
|
403
|
+
hgid_string = hgid_string.join('@') # incase the model was a tag that was tagged. (few months later: Wtf? Guess ill keep it)
|
404
|
+
end
|
405
|
+
#split the thing
|
406
|
+
splitz = hgid_string.split('/')
|
407
|
+
#get the class
|
408
|
+
begin
|
409
|
+
cls = splitz[-2].constantize
|
410
|
+
rescue NameError, NoMethodError
|
411
|
+
if returns_nil
|
412
|
+
nil
|
413
|
+
else
|
414
|
+
raise StandardError.new 'Unusual or unavailable string or hgid'
|
415
|
+
end
|
416
|
+
end
|
417
|
+
#get the hash
|
418
|
+
hash = splitz[-1]
|
419
|
+
# if self == ApplicationRecord (for instance), then check that cls is a subclass
|
420
|
+
# if self is not ApplicationRecord, then check cls == this objects class
|
421
|
+
# if with_associations defined, make sure that the class has the associations given (see has_association above)
|
422
|
+
if ((self.abstract_class? and cls < self) or ( (not self.abstract_class?) and cls == self )) and
|
423
|
+
( with_associations == nil or cls.has_association?(with_associations) )
|
424
|
+
#if all is as expected, return the object with its id.
|
425
|
+
if block_given?
|
426
|
+
yield(hash)
|
427
|
+
else
|
428
|
+
cls.hfind(hash)
|
429
|
+
end
|
430
|
+
elsif returns_nil
|
431
|
+
#allows us to handle issues with input
|
432
|
+
nil
|
433
|
+
else
|
434
|
+
#stops execution as default
|
435
|
+
raise StandardError.new 'Not the expected class, or a subclass of ApplicationRecord if called on that.'
|
436
|
+
end
|
437
|
+
end
|
438
|
+
def get_hgid_tag(hgid_string)
|
439
|
+
if hgid_string.include?('@')
|
440
|
+
return hgid_string.split('@')[-1]
|
441
|
+
else
|
442
|
+
return nil
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
#allows us to preload on a list and not a active record relation. So basically from the output of headache_sql
|
447
|
+
def dynamic_preload(records, associations)
|
448
|
+
ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
|
449
|
+
end
|
450
|
+
|
451
|
+
alias headache_preload dynamic_preload
|
452
|
+
|
332
453
|
def dynamic_sql(*args) #see below for opts
|
333
454
|
# call like: dynamic_sql(name, sql, option_1: 1, option_2: 2)
|
334
455
|
# or like: dynamic_sql(sql, {option: 1, option_2: 2})
|
@@ -462,39 +583,53 @@ module DynamicRecordsMeritfront
|
|
462
583
|
end
|
463
584
|
end
|
464
585
|
end
|
465
|
-
|
586
|
+
alias headache_sql dynamic_sql
|
466
587
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
588
|
+
def _dynamic_instaload_handle_with_statements(with_statements)
|
589
|
+
%Q{WITH #{
|
590
|
+
with_statements.map{|ws|
|
591
|
+
"#{ws[:table_name]} AS (\n#{ws[:sql]}\n)"
|
592
|
+
}.join(", \n")
|
472
593
|
}}
|
473
|
-
|
594
|
+
end
|
474
595
|
|
475
|
-
|
476
|
-
|
596
|
+
def _dynamic_instaload_union(insta_array)
|
597
|
+
insta_array.select{|insta|
|
477
598
|
not insta[:dont_return]
|
478
599
|
}.map{|insta|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
def instaload(sql, table_name: nil, relied_on: false, dont_return: false)
|
491
|
-
|
600
|
+
start = "SELECT row_to_json(#{insta[:table_name]}.*) AS row, '#{insta[:klass]}' AS _klass, '#{insta[:table_name]}' AS _table_name FROM "
|
601
|
+
if insta[:relied_on]
|
602
|
+
ending = "#{insta[:table_name]}\n"
|
603
|
+
else
|
604
|
+
ending = "(\n#{insta[:sql]}\n) AS #{insta[:table_name]}\n"
|
605
|
+
end
|
606
|
+
next start + ending
|
607
|
+
}.join(" UNION ALL \n")
|
608
|
+
#{ 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 ')}
|
609
|
+
end
|
610
|
+
|
611
|
+
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)
|
612
|
+
#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.
|
613
|
+
#At the end of the day it just returns a hash with the settings in it though. So dont overthink it too much.
|
614
|
+
|
615
|
+
as = as.to_s if as
|
616
|
+
base_name = base_name.to_s if base_name
|
617
|
+
|
618
|
+
if table_name
|
619
|
+
table_name = table_name.to_s
|
620
|
+
else
|
621
|
+
table_name = "_" + self.to_s.underscore.downcase.pluralize
|
622
|
+
end
|
623
|
+
|
492
624
|
klass = self.to_s
|
625
|
+
|
493
626
|
sql = "\t" + sql.strip
|
494
|
-
|
627
|
+
raise StandardError.new("base_on needs to be nil or a Proc") unless base_on.nil? or base_on.kind_of? Proc
|
628
|
+
raise StandardError.new("attach_on needs to be nil or a Proc") unless attach_on.nil? or attach_on.kind_of? Proc
|
629
|
+
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}
|
495
630
|
end
|
496
631
|
|
497
|
-
|
632
|
+
def instaload_sql(*args) #name, insta_array, opts = { })
|
498
633
|
args << {} unless args[-1].kind_of? Hash
|
499
634
|
if args.length == 3
|
500
635
|
name, insta_array, opts = args
|
@@ -505,104 +640,132 @@ module DynamicRecordsMeritfront
|
|
505
640
|
raise StandardError.new("bad input to DynamicRecordsMeritfront#instaload_sql method.")
|
506
641
|
end
|
507
642
|
|
508
|
-
|
509
|
-
|
643
|
+
with_statements = insta_array.select{|a| a[:relied_on]}
|
644
|
+
sql = %Q{
|
510
645
|
#{ _dynamic_instaload_handle_with_statements(with_statements) if with_statements.any? }
|
511
646
|
#{ _dynamic_instaload_union(insta_array)}
|
512
647
|
}
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
648
|
+
insta_array = insta_array.select{|ar| not ar[:dont_return]}
|
649
|
+
ret_hash = insta_array.map{|ar| [ar[:table_name].to_s, []]}.to_h
|
650
|
+
opts[:raw] = true
|
651
|
+
ApplicationRecord.headache_sql(name, sql, opts).rows.each{|row|
|
652
|
+
#need to pre-parsed as it has a non-normal output.
|
653
|
+
table_name = row[2]
|
654
|
+
klass = row[1].constantize
|
655
|
+
json = row[0]
|
656
|
+
parsed = JSON.parse(json)
|
657
|
+
|
658
|
+
ret_hash[table_name].push dynamic_init(klass, parsed)
|
659
|
+
}
|
660
|
+
|
661
|
+
insta_array = insta_array.map{|a|a.delete(:sql); next a} #better for debuggin and we dont need it anymore
|
662
|
+
|
663
|
+
#formatting options
|
664
|
+
for insta in insta_array
|
665
|
+
if insta[:base_name]
|
666
|
+
if insta[:as]
|
667
|
+
Rails.logger.debug "#{insta[:table_name]} as #{insta[:as]} -> #{insta[:base_name]}"
|
668
|
+
else
|
669
|
+
Rails.logger.debug "#{insta[:table_name]} -> #{insta[:base_name]}"
|
670
|
+
end
|
671
|
+
#in this case, 'as' is meant as to what pseudonym to dynamicly attach it as
|
672
|
+
dynamic_attach(ret_hash, insta[:base_name], insta[:table_name], base_on: insta[:base_on], attach_on: insta[:attach_on],
|
673
|
+
one_to_one: insta[:one_to_one], as: insta[:as])
|
674
|
+
elsif insta[:as]
|
675
|
+
Rails.logger.debug "#{insta[:table_name]} as #{insta[:as]}"
|
676
|
+
#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
|
677
|
+
# table_name)
|
678
|
+
if ret_hash[insta[:as]]
|
679
|
+
ret_hash[insta[:as]] += ret_hash[insta[:table_name]]
|
680
|
+
else
|
681
|
+
ret_hash[insta[:as]] = ret_hash[insta[:table_name]].dup #only top level dup
|
682
|
+
end
|
683
|
+
else
|
684
|
+
Rails.logger.debug "#{insta[:table_name]}"
|
685
|
+
end
|
686
|
+
end
|
687
|
+
|
688
|
+
return ret_hash
|
689
|
+
end
|
690
|
+
alias swiss_instaload_sql instaload_sql
|
528
691
|
alias dynamic_instaload_sql instaload_sql
|
529
692
|
|
530
693
|
def test_drmf(model_with_an_id_column_and_timestamps)
|
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
|
-
|
694
|
+
m = model_with_an_id_column_and_timestamps
|
695
|
+
ar = m.superclass
|
696
|
+
mtname = m.table_name
|
697
|
+
ApplicationRecord.transaction do
|
698
|
+
puts 'test recieving columns not normally in the record.'
|
699
|
+
rec = m.dynamic_sql(%Q{
|
700
|
+
SELECT id, 5 AS random_column from #{mtname} LIMIT 10
|
701
|
+
}).first
|
702
|
+
raise StandardError.new('no id') unless rec.id
|
703
|
+
raise StandardError.new('no dynamic column') unless rec.random_column
|
704
|
+
puts 'pass 1'
|
705
|
+
|
706
|
+
puts 'test raw off with a custom name'
|
707
|
+
recs = ar.dynamic_sql('test_2', %Q{
|
708
|
+
SELECT id, 5 AS random_column from #{mtname} LIMIT 10
|
709
|
+
}, raw: false)
|
710
|
+
raise StandardError.new('not array of hashes') unless recs.first.class == Hash and recs.class == Array
|
711
|
+
rec = recs.first
|
712
|
+
raise StandardError.new('no id [raw off]') unless rec['id']
|
713
|
+
raise StandardError.new('no dynamic column [raw off]') unless rec['random_column']
|
714
|
+
puts 'pass 2'
|
715
|
+
|
716
|
+
puts 'test raw on'
|
717
|
+
recs = ar.dynamic_sql('test_3', %Q{
|
718
|
+
SELECT id, 5 AS random_column from #{mtname} LIMIT 10
|
719
|
+
}, raw: true)
|
720
|
+
raise StandardError.new('not raw') unless recs.class == ActiveRecord::Result
|
721
|
+
rec = recs.first
|
722
|
+
raise StandardError.new('no id [raw]') unless rec['id']
|
723
|
+
raise StandardError.new('no dynamic column [raw]') unless rec['random_column']
|
724
|
+
puts 'pass 3'
|
725
|
+
|
726
|
+
puts 'test when some of the variables are diffrent then the same (#see version 3.0.1 notes)'
|
727
|
+
x = Proc.new { |a, b|
|
728
|
+
recs = ar.dynamic_sql('test_4', %Q{
|
729
|
+
SELECT id, 5 AS random_column from #{mtname} WHERE id > :a LIMIT :b
|
730
|
+
}, a: a, b: b)
|
731
|
+
}
|
732
|
+
x.call(1, 2)
|
733
|
+
x.call(1, 1)
|
734
|
+
puts 'pass 4'
|
735
|
+
|
736
|
+
puts 'test MultiAttributeArrays, including symbols and duplicate values.'
|
737
|
+
time = DateTime.now
|
738
|
+
ids = m.limit(5).pluck(:id)
|
739
|
+
values = ids.map{|id|
|
740
|
+
[id, :time, time]
|
741
|
+
}
|
742
|
+
ar.dynamic_sql(%Q{
|
743
|
+
INSERT INTO #{mtname} (id, created_at, updated_at)
|
744
|
+
VALUES :values
|
745
|
+
ON CONFLICT (id) DO NOTHING
|
746
|
+
}, values: values, time: time)
|
747
|
+
puts 'pass 5'
|
748
|
+
|
749
|
+
puts 'test arrays'
|
750
|
+
recs = ar.dynamic_sql(%Q{
|
751
|
+
SELECT id from #{mtname} where id = ANY(:idz)
|
752
|
+
}, idz: ids, raw: false)
|
753
|
+
puts recs
|
754
|
+
raise StandardError.new('wrong length') if recs.length != 5
|
755
|
+
puts 'pass 6'
|
756
|
+
|
757
|
+
|
758
|
+
puts 'test instaload_sql'
|
759
|
+
out = ar.instaload_sql([
|
760
|
+
ar.instaload("SELECT id FROM users", relied_on: true, dont_return: true, table_name: "users_2"),
|
761
|
+
ar.instaload("SELECT id FROM users_2 WHERE id % 2 != 0 LIMIT :limit", table_name: 'a'),
|
762
|
+
m.instaload("SELECT id FROM users_2 WHERE id % 2 != 1 LIMIT :limit", table_name: 'b')
|
763
|
+
], limit: 2)
|
764
|
+
puts out
|
765
|
+
raise StandardError.new('Bad return') if out["users_2"]
|
766
|
+
raise StandardError.new('Bad return') unless out["a"]
|
767
|
+
raise StandardError.new('Bad return') unless out["b"]
|
768
|
+
puts 'pass 7'
|
606
769
|
|
607
770
|
puts "test dynamic_sql V3.0.6 error to do with multi_attribute_arrays which is hard to describe"
|
608
771
|
time = DateTime.now
|
@@ -615,170 +778,186 @@ module DynamicRecordsMeritfront
|
|
615
778
|
}, time: time, values: values)
|
616
779
|
puts 'pass 8'
|
617
780
|
|
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
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
781
|
+
raise ActiveRecord::Rollback
|
782
|
+
#ApplicationRecord.dynamic_sql("SELECT * FROM")
|
783
|
+
end
|
784
|
+
end
|
785
|
+
|
786
|
+
def dynamic_attach(instaload_sql_output, base_name, attach_name, base_on: nil, attach_on: nil, one_to_one: false, as: nil)
|
787
|
+
#as just lets it attach us anywhere on the base class, and not just as the attach_name.
|
788
|
+
#Can be useful in polymorphic situations, otherwise may lead to confusion.
|
789
|
+
as ||= attach_name
|
790
|
+
|
791
|
+
base_arr = instaload_sql_output[base_name]
|
792
|
+
|
793
|
+
#return if there is nothing for us to attach to.
|
794
|
+
return if base_arr.nil? or not base_arr.any?
|
795
|
+
|
796
|
+
#set variables for neatness and so we dont compute each time
|
797
|
+
# base class information
|
798
|
+
base_class = base_arr.first.class
|
799
|
+
base_class_is_hash = base_class <= Hash
|
800
|
+
|
801
|
+
#variable accessors and defaults. Make sure it only sets if not defined already as
|
802
|
+
#the 'as' option allows us to override to what value it actually gets set in the end,
|
803
|
+
#and in polymorphic situations this could be called in multiple instances
|
804
|
+
base_arr.each{ |o|
|
805
|
+
if not base_class_is_hash
|
806
|
+
if one_to_one
|
807
|
+
#attach name must be a string
|
808
|
+
o.questionable_attribute_set(as, nil, as_default: true)
|
809
|
+
else
|
810
|
+
o.questionable_attribute_set(as, [], as_default: true)
|
811
|
+
end
|
812
|
+
elsif not one_to_one
|
813
|
+
o[as] ||= []
|
814
|
+
end
|
815
|
+
}
|
816
|
+
|
817
|
+
#make sure the attach class has something going on. We do this after the default stage
|
818
|
+
attach_arr = instaload_sql_output[attach_name]
|
819
|
+
return if attach_arr.nil? or not attach_arr.any?
|
820
|
+
|
821
|
+
# attach class information
|
822
|
+
attach_class = attach_arr.first.class
|
823
|
+
attach_class_is_hash = attach_class <= Hash
|
824
|
+
|
825
|
+
# default attach column info
|
826
|
+
default_attach_col = (base_class.to_s.downcase + "_id")
|
827
|
+
|
828
|
+
#decide on the method of getting the matching id for the base table
|
829
|
+
unless base_on
|
830
|
+
if base_class_is_hash
|
831
|
+
base_on = Proc.new{|x| x['id']}
|
832
|
+
else
|
833
|
+
base_on = Proc.new{|x| x.id}
|
834
|
+
end
|
835
|
+
end
|
836
|
+
|
837
|
+
#return an id->object hash for the base table for better access
|
838
|
+
h = base_arr.map{|o|
|
839
|
+
[base_on.call(o), o]
|
840
|
+
}.to_h
|
841
|
+
|
842
|
+
#decide on the method of getting the matching id for the attach table
|
843
|
+
unless attach_on
|
844
|
+
if attach_class_is_hash
|
845
|
+
attach_on = Proc.new{|x| x[default_attach_col]}
|
846
|
+
else
|
847
|
+
attach_on = Proc.new{|x|
|
848
|
+
x.attributes[default_attach_col]
|
849
|
+
}
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
# if debug
|
854
|
+
# Rails.logger.info(base_arr.map{|b|
|
855
|
+
# base_on.call(b)
|
856
|
+
# })
|
857
|
+
# Rails.logger.info(attach_arr.map{|a|
|
858
|
+
# attach_on.call(a)
|
859
|
+
# })
|
860
|
+
# end
|
861
|
+
|
862
|
+
#method of adding the object to the base
|
863
|
+
#(b=base, a=attach)
|
864
|
+
add_to_base = Proc.new{|b, a|
|
865
|
+
if base_class_is_hash
|
866
|
+
if one_to_one
|
867
|
+
b[as] = a
|
868
|
+
else
|
869
|
+
b[as].push a
|
870
|
+
end
|
871
|
+
else
|
872
|
+
#getting a lil tired of the meta stuff.
|
873
|
+
if one_to_one
|
874
|
+
b.questionable_attribute_set(as, a)
|
875
|
+
else
|
876
|
+
b.questionable_attribute_set(as, a, push: true)
|
877
|
+
end
|
878
|
+
end
|
879
|
+
}
|
880
|
+
|
881
|
+
#for every attachable
|
882
|
+
# 1. match base id to the attach id (both configurable)
|
883
|
+
# 2. cancel out if there is no match
|
884
|
+
# 3. otherwise add to the base object.
|
885
|
+
attach_arr.each{|attach|
|
886
|
+
out = attach_on.call(attach) #you can use null to escape the vals
|
887
|
+
if out.nil?
|
888
|
+
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."
|
889
|
+
end
|
890
|
+
if out
|
891
|
+
base = h[out] #it is also escaped if no base element is found
|
892
|
+
if base
|
893
|
+
add_to_base.call(base, attach)
|
894
|
+
end
|
895
|
+
end
|
896
|
+
}
|
897
|
+
|
898
|
+
return attach_arr
|
899
|
+
end
|
900
|
+
alias swiss_attach dynamic_attach
|
901
|
+
|
902
|
+
def zip_ar_result(x)
|
903
|
+
x.to_a
|
904
|
+
end
|
905
|
+
|
906
|
+
def dynamic_init(klass, input)
|
907
|
+
if klass.abstract_class
|
908
|
+
return input
|
909
|
+
else
|
910
|
+
record = klass.instantiate(input.stringify_keys ) #trust me they need to be stringified
|
911
|
+
# #handle attributes through ar if allowed. Throws an error on unkown variables, except apparently for devise classes? 😡
|
912
|
+
# active_record_handled = input.slice(*(klass.attribute_names & input.keys))
|
913
|
+
# record = klass.instantiate(active_record_handled)
|
914
|
+
# #set those that were not necessarily expected
|
915
|
+
# not_expected = input.slice(*(input.keys - klass.attribute_names))
|
916
|
+
# record.dynamic = OpenStruct.new(not_expected.transform_keys{|k|k.to_sym}) if not_expected.keys.any?
|
917
|
+
return record
|
918
|
+
end
|
919
|
+
end
|
920
|
+
|
921
|
+
def quick_safe_increment(id, col, val)
|
922
|
+
where(id: id).update_all("#{col} = #{col} + #{val}")
|
923
|
+
end
|
924
|
+
|
925
|
+
|
926
|
+
end
|
927
|
+
|
928
|
+
def list_associations
|
929
|
+
#lists associations (see class method above)
|
930
|
+
self.class.list_associations
|
931
|
+
end
|
932
|
+
|
933
|
+
def has_association?(*args)
|
934
|
+
#just redirects to the class method for ease of use (see class method above)
|
935
|
+
self.class.has_association?(*args)
|
936
|
+
end
|
937
|
+
|
938
|
+
# custom hash GlobalID
|
939
|
+
def hgid(tag: nil)
|
940
|
+
gid = "gid://#{PROJECT_NAME}/#{self.class.to_s}/#{self.hid}"
|
941
|
+
if !tag
|
942
|
+
gid
|
943
|
+
else
|
944
|
+
"#{gid}@#{tag}"
|
945
|
+
end
|
946
|
+
end
|
947
|
+
alias ghid hgid #its worth it trust me the amount of times i go 'is it hash global id or global hashid?'
|
948
|
+
|
949
|
+
def hgid_as_selector(tag: nil, attribute: 'id')
|
950
|
+
#https://www.javascripttutorial.net/javascript-dom/javascript-queryselector/
|
951
|
+
gidstr = hgid(tag: tag).to_s
|
952
|
+
return self.class.string_as_selector(gidstr, attribute: attribute)
|
953
|
+
end
|
954
|
+
|
955
|
+
#just for ease of use
|
956
|
+
def headache_preload(records, associations)
|
957
|
+
self.class.headache_preload(records, associations)
|
958
|
+
end
|
959
|
+
def safe_increment(col, val) #also used in follow, also used in comment#kill
|
960
|
+
self.class.where(id: self.id).update_all("#{col} = #{col} + #{val}")
|
961
|
+
end
|
783
962
|
|
784
963
|
end
|