dynamic-records-meritfront 3.0.11 → 3.0.23

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- extend ActiveSupport::Concern
8
-
9
- # the two aliases so I dont go insane
10
- module Hashid::Rails
11
- alias hid hashid
12
- end
13
- module Hashid::Rails::ClassMethods
14
- alias hfind find_by_hashid
15
- end
16
- included do
17
- # include hash id gem
18
- include Hashid::Rails
19
- #should work, probably able to override by redefining in ApplicationRecord class.
20
- #Note we defined here as it breaks early on as Rails.application returns nil
21
- PROJECT_NAME = Rails.application.class.to_s.split("::").first.to_s.downcase
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
- end
25
+
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
- #BigIntArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::BigInteger.new).freeze
56
- #IntegerArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::Integer.new).freeze
57
-
58
- #https://api.rubyonrails.org/files/activemodel/lib/active_model/type_rb.html
59
- # active_model/type/helpers
60
- # active_model/type/value
61
- # active_model/type/big_integer
62
- # active_model/type/binary
63
- # active_model/type/boolean
64
- # active_model/type/date
65
- # active_model/type/date_time
66
- # active_model/type/decimal
67
- # active_model/type/float
68
- # active_model/type/immutable_string
69
- # active_model/type/integer
70
- # active_model/type/string
71
- # active_model/type/time
72
- # active_model
73
-
74
- DB_TYPE_MAPS = {
75
- String => ActiveModel::Type::String,
76
- Symbol => ActiveModel::Type::String,
77
- Integer => ActiveModel::Type::BigInteger,
78
- BigDecimal => ActiveRecord::Type::Decimal,
79
- TrueClass => ActiveModel::Type::Boolean,
80
- FalseClass => ActiveModel::Type::Boolean,
81
- Date => ActiveModel::Type::Date,
82
- DateTime => ActiveModel::Type::DateTime,
83
- Time => ActiveModel::Type::Time,
84
- Float => ActiveModel::Type::Float,
85
- Array => Proc.new{ |first_el_class| ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(DB_TYPE_MAPS[first_el_class].new) }
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
@@ -101,7 +106,11 @@ module DynamicRecordsMeritfront
101
106
 
102
107
  type = DB_TYPE_MAPS[v.class]
103
108
  if type.nil?
104
- raise StandardError.new("#{name} (of value: #{v}, class: #{v.class}) unsupported class for ApplicationRecord#headache_sql")
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
105
114
  elsif type.class == Proc
106
115
  a = v[0]
107
116
  # if a.nil?
@@ -116,33 +125,33 @@ module DynamicRecordsMeritfront
116
125
  end
117
126
  end
118
127
 
119
- class MultiRowExpression
120
- #this class is meant to be used in congunction with headache_sql method
121
- #Could be used like so in headache_sql:
122
-
123
- #ApplicationRecord.headache_sql( "teeeest", %Q{
124
- # INSERT INTO tests(id, username, is_awesome)
125
- # VALUES :rows
126
- # ON CONFLICT SET is_awesome = true
127
- #}, rows: [[1, luke, true], [2, josh, false]])
128
-
129
- #which would output this sql
130
-
131
- # INSERT INTO tests(id, username, is_awesome)
132
- # VALUES ($0,$1,$2),($3,$4,$5)
133
- # ON CONFLICT SET is_awesome = true
134
-
135
- attr_accessor :val
136
- def initialize(val)
137
- #assuming we are putting in an array of arrays.
138
- self.val = val
139
- end
140
- def for_query(key, var_track)
141
- #accepts x = current number of variables previously processed
142
- #returns ["sql string with $# location information", variables themselves in order, new x]
143
- x = -1
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
144
153
  db_val = val.map{|attribute_array| "(#{
145
- attribute_array.map{|attribute|
154
+ attribute_array.map{|attribute|
146
155
  if attribute.kind_of? Symbol
147
156
  #allow pointers to other more explicit variables through symbols
148
157
  x = var_track.add_key_value(attribute, nil)
@@ -151,184 +160,237 @@ module DynamicRecordsMeritfront
151
160
  x = var_track.add_key_value(k, attribute)
152
161
  end
153
162
  next "$" + x.to_s
154
- }.join(",")
155
- })"}.join(",")
156
- return db_val
157
- end
158
- end
159
-
160
- def questionable_attribute_set(atr, value)
161
- #this is needed on initalization of a new variable after the actual thing has been made already.
162
-
163
- #set a bunk type of the generic value type
164
- @attributes.instance_variable_get(:@types)[atr] = ActiveModel::Type::Value.new
165
- #Set it
166
- self[atr] = value
167
- end
168
-
169
- def inspect
170
- #basically the same as the upstream active record function (as of october 25 2022 on AR V7.0.4)
171
- #except that I changed self.class.attribute_names -> self.attribute_names to pick up our
172
- #dynamic insanity. Was this a good idea? Well I guess its better than not doing it
173
- inspection = if defined?(@attributes) && @attributes
174
- self.attribute_names.filter_map do |name|
175
- if _has_attribute?(name)
176
- "#{name}: #{attribute_for_inspect(name)}"
177
- end
178
- end.join(", ")
179
- else
180
- "not initialized"
181
- end
182
-
183
- "#<#{self.class} #{inspection}>"
184
- end
185
-
186
- module ClassMethods
187
-
188
- def has_run_migration?(nm)
189
- #put in a string name of the class and it will say if it has allready run the migration.
190
- #good during enum migrations as the code to migrate wont run if enumerate is there
191
- #as it is not yet enumerated (causing an error when it loads the class that will have the
192
- #enumeration in it). This can lead it to being impossible to commit clean code.
193
- #
194
- # example usage one: only create the record class if it currently exists in the database
195
- # if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
196
- # class UserImageRelation < ApplicationRecord
197
- # belongs_to :imageable, polymorphic: true
198
- # belongs_to :image
199
- # end
200
- # else
201
- # class UserImageRelation; end
202
- # end
203
- # example usage two: only load relation if it exists in the database
204
- # class UserImageRelation < ApplicationRecord
205
- # if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
206
- # belongs_to :imageable, polymorphic: true
207
- # end
208
- # end
209
- #
210
- #current version of migrations
211
- cv = ActiveRecord::Base.connection.migration_context.current_version
212
-
213
- #find the migration object for the name
214
- migration = ActiveRecord::Base.connection.migration_context.migrations.filter!{|a|
215
- a.name == nm
216
- }.first
217
-
218
- #if the migration object is nil, it has not yet been created
219
- if migration.nil?
220
- Rails.logger.info "No migration found for #{nm}. The migration has not yet been created, or is foreign to this database."
221
- return false
222
- end
223
-
224
- #get the version number for the migration name
225
- needed_version = migration.version
226
-
227
- #if current version is above or equal, the migration has allready been run
228
- migration_ran = (cv >= needed_version)
229
-
230
- if migration_ran
231
- Rails.logger.info "#{nm} migration was run on #{needed_version}. If old and all instances are migrated, consider removing code check."
232
- else
233
- Rails.logger.info "#{nm} migration has not run yet. This may lead to limited functionality"
234
- end
235
-
236
- return migration_ran
237
- end
238
- def list_associations
239
- #lists associations (see has_association? below)
240
- reflect_on_all_associations.map(&:name)
241
- end
242
- def has_association?(*args)
243
- #checks whether current class has needed association (for example, checks it has comments)
244
- #associations can be seen in has_many belongs_to and other similar methods
245
-
246
- #flattens so you can pass self.has_association?(:comments, :baseable_comments) aswell as
247
- # self.has_association?([:comments, :baseable_comments]) without issue
248
- #
249
- args = args.flatten.map { |a| a.to_sym }
250
- associations = list_associations
251
- (args.length == (associations & args).length)
252
- end
253
- def blind_hgid(id, tag: nil, encode: true)
254
- # this method is to get an hgid for a class without actually calling it down from the database.
255
- # For example Notification.blind_hgid 1 will give gid://PROJECT_NAME/Notification/69DAB69 etc.
256
- if id.class == Integer and encode
257
- id = self.encode_id id
258
- end
259
- gid = "gid://#{PROJECT_NAME}/#{self.to_s}/#{id}"
260
- if !tag
261
- gid
262
- else
263
- "#{gid}@#{tag}"
264
- end
265
- end
266
- def string_as_selector(str, attribute: 'id')
267
- #this is needed to allow us to quey various strange characters in the id etc. (see hgids)
268
- #also useful for querying various attributes
269
- return "*[#{attribute}=\"#{str}\"]"
270
- end
271
- def locate_hgid(hgid_string, with_associations: nil, returns_nil: false)
272
- if hgid_string == nil or hgid_string.class != String
273
- if returns_nil
274
- return nil
275
- else
276
- raise StandardError.new("non-string class passed to ApplicationRecord#locate_hgid as the hgid_string variable")
277
- end
278
- end
279
- if hgid_string.include?('@')
280
- hgid_string = hgid_string.split('@')
281
- hgid_string.pop
282
- hgid_string = hgid_string.join('@') # incase the model was a tag that was tagged. (few months later: Wtf? Guess ill keep it)
283
- end
284
- #split the thing
285
- splitz = hgid_string.split('/')
286
- #get the class
287
- begin
288
- cls = splitz[-2].constantize
289
- rescue NameError, NoMethodError
290
- if returns_nil
291
- nil
292
- else
293
- raise StandardError.new 'Unusual or unavailable string or hgid'
294
- end
295
- end
296
- #get the hash
297
- hash = splitz[-1]
298
- # if self == ApplicationRecord (for instance), then check that cls is a subclass
299
- # if self is not ApplicationRecord, then check cls == this objects class
300
- # if with_associations defined, make sure that the class has the associations given (see has_association above)
301
- if ((self.abstract_class? and cls < self) or ( (not self.abstract_class?) and cls == self )) and
302
- ( with_associations == nil or cls.has_association?(with_associations) )
303
- #if all is as expected, return the object with its id.
304
- if block_given?
305
- yield(hash)
306
- else
307
- cls.hfind(hash)
308
- end
309
- elsif returns_nil
310
- #allows us to handle issues with input
311
- nil
312
- else
313
- #stops execution as default
314
- raise StandardError.new 'Not the expected class, or a subclass of ApplicationRecord if called on that.'
315
- end
316
- end
317
- def get_hgid_tag(hgid_string)
318
- if hgid_string.include?('@')
319
- return hgid_string.split('@')[-1]
320
- else
321
- return nil
322
- end
323
- end
324
-
325
- #allows us to preload on a list and not a active record relation. So basically from the output of headache_sql
326
- def dynamic_preload(records, associations)
327
- ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
328
- end
329
-
330
- alias headache_preload dynamic_preload
331
-
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
+
332
394
  def dynamic_sql(*args) #see below for opts
333
395
  # call like: dynamic_sql(name, sql, option_1: 1, option_2: 2)
334
396
  # or like: dynamic_sql(sql, {option: 1, option_2: 2})
@@ -462,39 +524,53 @@ module DynamicRecordsMeritfront
462
524
  end
463
525
  end
464
526
  end
465
- alias headache_sql dynamic_sql
527
+ alias headache_sql dynamic_sql
466
528
 
467
- def _dynamic_instaload_handle_with_statements(with_statements)
468
- %Q{WITH #{
469
- with_statements.map{|ws|
470
- "#{ws[:table_name]} AS (\n#{ws[:sql]}\n)"
471
- }.join(", \n")
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")
472
534
  }}
473
- end
535
+ end
474
536
 
475
- def _dynamic_instaload_union(insta_array)
476
- insta_array.select{|insta|
537
+ def _dynamic_instaload_union(insta_array)
538
+ insta_array.select{|insta|
477
539
  not insta[:dont_return]
478
540
  }.map{|insta|
479
- start = "SELECT row_to_json(#{insta[:table_name]}.*) AS row, '#{insta[:klass]}' AS _klass, '#{insta[:table_name]}' AS _table_name FROM "
480
- if insta[:relied_on]
481
- ending = "#{insta[:table_name]}\n"
482
- else
483
- ending = "(\n#{insta[:sql]}\n) AS #{insta[:table_name]}\n"
484
- end
485
- next start + ending
486
- }.join(" UNION ALL \n")
487
- #{ 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 ')}
488
- end
489
-
490
- def instaload(sql, table_name: nil, relied_on: false, dont_return: false)
491
- table_name ||= "_" + self.to_s.underscore.downcase.pluralize
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
+
492
565
  klass = self.to_s
566
+
493
567
  sql = "\t" + sql.strip
494
- return {table_name: table_name, klass: klass, sql: sql, relied_on: relied_on, dont_return: dont_return}
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}
495
571
  end
496
572
 
497
- def instaload_sql(*args) #name, insta_array, opts = { })
573
+ def instaload_sql(*args) #name, insta_array, opts = { })
498
574
  args << {} unless args[-1].kind_of? Hash
499
575
  if args.length == 3
500
576
  name, insta_array, opts = args
@@ -505,104 +581,132 @@ module DynamicRecordsMeritfront
505
581
  raise StandardError.new("bad input to DynamicRecordsMeritfront#instaload_sql method.")
506
582
  end
507
583
 
508
- with_statements = insta_array.select{|a| a[:relied_on]}
509
- sql = %Q{
584
+ with_statements = insta_array.select{|a| a[:relied_on]}
585
+ sql = %Q{
510
586
  #{ _dynamic_instaload_handle_with_statements(with_statements) if with_statements.any? }
511
587
  #{ _dynamic_instaload_union(insta_array)}
512
588
  }
513
- returned_arrays = insta_array.select{|ar| not ar[:dont_return]}
514
- ret_hash = returned_arrays.map{|ar| [ar[:table_name].to_s, []]}.to_h
515
- opts[:raw] = true
516
- ApplicationRecord.headache_sql(name, sql, opts).rows.each{|row|
517
- #need to pre-parsed as it has a non-normal output.
518
- table_name = row[2]
519
- klass = row[1].constantize
520
- json = row[0]
521
- parsed = JSON.parse(json)
522
-
523
- ret_hash[table_name].push dynamic_init(klass, parsed)
524
- }
525
- return ret_hash
526
- end
527
- alias swiss_instaload_sql instaload_sql
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
528
632
  alias dynamic_instaload_sql instaload_sql
529
633
 
530
634
  def test_drmf(model_with_an_id_column_and_timestamps)
531
- m = model_with_an_id_column_and_timestamps
532
- ar = m.superclass
533
- mtname = m.table_name
534
- ApplicationRecord.transaction do
535
- puts 'test recieving columns not normally in the record.'
536
- rec = m.dynamic_sql(%Q{
537
- SELECT id, 5 AS random_column from #{mtname} LIMIT 10
538
- }).first
539
- raise StandardError.new('no id') unless rec.id
540
- raise StandardError.new('no dynamic column') unless rec.random_column
541
- puts 'pass 1'
542
-
543
- puts 'test raw off with a custom name'
544
- recs = ar.dynamic_sql('test_2', %Q{
545
- SELECT id, 5 AS random_column from #{mtname} LIMIT 10
546
- }, raw: false)
547
- raise StandardError.new('not array of hashes') unless recs.first.class == Hash and recs.class == Array
548
- rec = recs.first
549
- raise StandardError.new('no id [raw off]') unless rec['id']
550
- raise StandardError.new('no dynamic column [raw off]') unless rec['random_column']
551
- puts 'pass 2'
552
-
553
- puts 'test raw on'
554
- recs = ar.dynamic_sql('test_3', %Q{
555
- SELECT id, 5 AS random_column from #{mtname} LIMIT 10
556
- }, raw: true)
557
- raise StandardError.new('not raw') unless recs.class == ActiveRecord::Result
558
- rec = recs.first
559
- raise StandardError.new('no id [raw]') unless rec['id']
560
- raise StandardError.new('no dynamic column [raw]') unless rec['random_column']
561
- puts 'pass 3'
562
-
563
- puts 'test when some of the variables are diffrent then the same (#see version 3.0.1 notes)'
564
- x = Proc.new { |a, b|
565
- recs = ar.dynamic_sql('test_4', %Q{
566
- SELECT id, 5 AS random_column from #{mtname} WHERE id > :a LIMIT :b
567
- }, a: a, b: b)
568
- }
569
- x.call(1, 2)
570
- x.call(1, 1)
571
- puts 'pass 4'
572
-
573
- puts 'test MultiAttributeArrays, including symbols and duplicate values.'
574
- time = DateTime.now
575
- ids = m.limit(5).pluck(:id)
576
- values = ids.map{|id|
577
- [id, :time, time]
578
- }
579
- ar.dynamic_sql(%Q{
580
- INSERT INTO #{mtname} (id, created_at, updated_at)
581
- VALUES :values
582
- ON CONFLICT (id) DO NOTHING
583
- }, values: values, time: time)
584
- puts 'pass 5'
585
-
586
- puts 'test arrays'
587
- recs = ar.dynamic_sql(%Q{
588
- SELECT id from #{mtname} where id = ANY(:idz)
589
- }, idz: ids, raw: false)
590
- puts recs
591
- raise StandardError.new('wrong length') if recs.length != 5
592
- puts 'pass 6'
593
-
594
-
595
- puts 'test instaload_sql'
596
- out = ar.instaload_sql([
597
- ar.instaload("SELECT id FROM users", relied_on: true, dont_return: true, table_name: "users_2"),
598
- ar.instaload("SELECT id FROM users_2 WHERE id % 2 != 0 LIMIT :limit", table_name: 'a'),
599
- m.instaload("SELECT id FROM users_2 WHERE id % 2 != 1 LIMIT :limit", table_name: 'b')
600
- ], limit: 2)
601
- puts out
602
- raise StandardError.new('Bad return') if out["users_2"]
603
- raise StandardError.new('Bad return') unless out["a"]
604
- raise StandardError.new('Bad return') unless out["b"]
605
- puts 'pass 7'
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'
606
710
 
607
711
  puts "test dynamic_sql V3.0.6 error to do with multi_attribute_arrays which is hard to describe"
608
712
  time = DateTime.now
@@ -615,170 +719,186 @@ module DynamicRecordsMeritfront
615
719
  }, time: time, values: values)
616
720
  puts 'pass 8'
617
721
 
618
- raise ActiveRecord::Rollback
619
- #ApplicationRecord.dynamic_sql("SELECT * FROM")
620
- end
621
- end
622
-
623
- def dynamic_attach(instaload_sql_output, base_name, attach_name, base_on: nil, attach_on: nil, one_to_one: false)
624
- base_arr = instaload_sql_output[base_name]
625
-
626
- #return if there is nothing for us to attach to.
627
- return unless base_arr.any?
628
-
629
- #set variables for neatness and so we dont compute each time
630
- # base class information
631
- base_class = base_arr.first.class
632
- base_class_is_hash = base_class <= Hash
633
-
634
-
635
- #variable accessors and defaults.
636
- base_arr.each{ |o|
637
- #
638
- # there is no way to set an attribute after instantiation I tried I looked
639
- # I dealt with silent breaks on symbol keys, I have wasted time, its fine.
640
-
641
- if not base_class_is_hash
642
- if one_to_one
643
- #attach name must be a string
644
- o.questionable_attribute_set(attach_name, nil)
645
- else
646
- o.questionable_attribute_set(attach_name, [])
647
- end
648
- end
649
- # o.dynamic o.singleton_class.public_send(:attr_accessor, attach_name_sym) unless base_class_is_hash
650
- # o.instance_variable_set(attach_name_with_at, []) unless one_to_one
651
- }
652
-
653
- #make sure the attach class has something going on
654
- attach_arr = instaload_sql_output[attach_name]
655
- return unless attach_arr.any?
656
-
657
- # attach class information
658
- attach_class = attach_arr.first.class
659
- attach_class_is_hash = attach_class <= Hash
660
-
661
- # default attach column info
662
- default_attach_col = (base_class.to_s.downcase + "_id")
663
-
664
- #decide on the method of getting the matching id for the base table
665
- unless base_on
666
- if base_class_is_hash
667
- base_on = Proc.new{|x| x['id']}
668
- else
669
- base_on = Proc.new{|x| x.id}
670
- end
671
- end
672
-
673
- #return an id->object hash for the base table for better access
674
- h = base_arr.map{|o|
675
- [base_on.call(o), o]
676
- }.to_h
677
-
678
- #decide on the method of getting the matching id for the attach table
679
- unless attach_on
680
- if attach_class_is_hash
681
- attach_on = Proc.new{|x| x[default_attach_col]}
682
- else
683
- attach_on = Proc.new{|x|
684
- x.attributes[default_attach_col]
685
- }
686
- end
687
- end
688
-
689
- # if debug
690
- # Rails.logger.info(base_arr.map{|b|
691
- # base_on.call(b)
692
- # })
693
- # Rails.logger.info(attach_arr.map{|a|
694
- # attach_on.call(a)
695
- # })
696
- # end
697
-
698
- #method of adding the object to the base
699
- #(b=base, a=attach)
700
- add_to_base = Proc.new{|b, a|
701
- if one_to_one
702
- b[attach_name] = a
703
- else
704
- b[attach_name].push a
705
- end
706
- }
707
-
708
- #for every attachable
709
- # 1. match base id to the attach id (both configurable)
710
- # 2. cancel out if there is no match
711
- # 3. otherwise add to the base object.
712
- attach_arr.each{|attach|
713
- if out = attach_on.call(attach) #you can use null to escape the vals
714
- if base = h[out] #it is also escaped if no base element is found
715
- add_to_base.call(base, attach)
716
- end
717
- end
718
- }
719
- return attach_arr
720
- end
721
- alias swiss_attach dynamic_attach
722
-
723
- def zip_ar_result(x)
724
- x.to_a
725
- end
726
-
727
- def dynamic_init(klass, input)
728
- if klass.abstract_class
729
- return input
730
- else
731
- record = klass.instantiate(input.stringify_keys ) #trust me they need to be stringified
732
- # #handle attributes through ar if allowed. Throws an error on unkown variables, except apparently for devise classes? 😡
733
- # active_record_handled = input.slice(*(klass.attribute_names & input.keys))
734
- # record = klass.instantiate(active_record_handled)
735
- # #set those that were not necessarily expected
736
- # not_expected = input.slice(*(input.keys - klass.attribute_names))
737
- # record.dynamic = OpenStruct.new(not_expected.transform_keys{|k|k.to_sym}) if not_expected.keys.any?
738
- return record
739
- end
740
- end
741
-
742
- def quick_safe_increment(id, col, val)
743
- where(id: id).update_all("#{col} = #{col} + #{val}")
744
- end
745
-
746
-
747
- end
748
-
749
- def list_associations
750
- #lists associations (see class method above)
751
- self.class.list_associations
752
- end
753
-
754
- def has_association?(*args)
755
- #just redirects to the class method for ease of use (see class method above)
756
- self.class.has_association?(*args)
757
- end
758
-
759
- # custom hash GlobalID
760
- def hgid(tag: nil)
761
- gid = "gid://#{PROJECT_NAME}/#{self.class.to_s}/#{self.hid}"
762
- if !tag
763
- gid
764
- else
765
- "#{gid}@#{tag}"
766
- end
767
- end
768
- alias ghid hgid #its worth it trust me the amount of times i go 'is it hash global id or global hashid?'
769
-
770
- def hgid_as_selector(tag: nil, attribute: 'id')
771
- #https://www.javascripttutorial.net/javascript-dom/javascript-queryselector/
772
- gidstr = hgid(tag: tag).to_s
773
- return self.class.string_as_selector(gidstr, attribute: attribute)
774
- end
775
-
776
- #just for ease of use
777
- def headache_preload(records, associations)
778
- self.class.headache_preload(records, associations)
779
- end
780
- def safe_increment(col, val) #also used in follow, also used in comment#kill
781
- self.class.where(id: self.id).update_all("#{col} = #{col} + #{val}")
782
- end
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
783
903
 
784
904
  end