dynamic-records-meritfront 1.1.11 → 2.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d7119a7be4c9ab826096a83190f68aa1d069e7db36f03ddeade1665788fc449
4
- data.tar.gz: 21e633fc3369067d1e6bd9090e83a2f241d6efacd2bb63f4c6d06cba0bf85e5f
3
+ metadata.gz: b0ad57039c2e650f4882c75aaeccca4fe55a771db29e0552ef648a9eaf974be8
4
+ data.tar.gz: 0af2b5a0f1add956c8b6029196237b9945c820204c114498078f8bf80be90ecf
5
5
  SHA512:
6
- metadata.gz: 330b3763f9c3387313f2142fe039e93fd36cca3a0736563f832b929640f6286fc3f03f3dde6dfa6b9e28ad52b576d14bab2eefc0525bc5fae712673f7069f4b5
7
- data.tar.gz: e05da4103fe1fed2942963bf898f4a0994fa75a9fd2ddcf7cf9c4b2a3aa0cf78baed5c42c39fcdba43e2fa949850160a591da2ba092cfc41b62f873ef2f51848
6
+ metadata.gz: 86ca4444f4848bb8af6389f005faf9a9813b9a49f0c0b31450d95d4317470c2c0e3e4b6435d3fba1cf57a7f7f1a02b7763a0dde9861b59a53cfac1681e4c6cbb
7
+ data.tar.gz: 29593f3c3ec593384c573e74ff2737e2b97e77cb38dbc371c3847693488095a2b828b316fbe105a0a599efa430c33c611f17637808c51f27932d47b76fccbd45
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dynamic-records-meritfront (1.1.11)
4
+ dynamic-records-meritfront (2.0.3)
5
5
  hashid-rails
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -205,7 +205,7 @@ See the hashid-rails gem for more (https://github.com/jcypret/hashid-rails). Als
205
205
  - ApplicationRecord.locate_hgid(hgid, with_associations: [:votes]) - locates the record but only if the record's class has a :votes active record association. So for instance, you can accept only votable objects for upvote functionality. Fires an error if the hgid does not match.
206
206
  - User.locate_hgid(hgid, returns_nil: true) - locates the hgid but only if it is the user class. Returns nil if not.
207
207
  4. get_hgid_tag(hgid) - returns the tag attached to the hgid
208
- 5. self.blind_hgid(id, tag) - creates a hgid without bringing the object down from the database. Useful with hashid-rails encode_id and decode_id methods
208
+ 5. self.blind_hgid(id, tag: nil, encode: true) - creates a hgid without bringing the object down from the database. Useful with hashid-rails encode_id and decode_id methods.
209
209
 
210
210
  ## Potential Issues
211
211
 
@@ -223,6 +223,9 @@ Let me know if this actually becomes an issue for someone and I will throw in a
223
223
  - Added functionality in headache_sql where for sql arguments that are equal, we only use one sql argument instead of repeating arguments
224
224
  - Added functionality in headache_sql for 'multi row expressions' which are inputtable as an Array of Arrays. See the upsert example in the headache_sql documentation above for more.
225
225
  - Added a warning in the README for non-postgresql databases. Contact me if you hit issues and we can work it out.
226
+
227
+ 1.1.11
228
+ - Added encode option for blind_hgid to allow creation of just a general gid
226
229
 
227
230
  ## Contributing
228
231
 
@@ -1,5 +1,5 @@
1
1
 
2
2
  module DynamicRecordsMeritfront
3
- VERSION = '1.1.11'
3
+ VERSION = '2.0.3'
4
4
  end
5
5
  #this file gets overwritten automatically on minor updates, major ones need to be manually changed
@@ -18,53 +18,56 @@ module DynamicRecordsMeritfront
18
18
  #should work, probably able to override by redefining in ApplicationRecord class.
19
19
  #Note we defined here as it breaks early on as Rails.application returns nil
20
20
  PROJECT_NAME = Rails.application.class.to_s.split("::").first.to_s.downcase
21
+ DYNAMIC_SQL_RAW = true
22
+ attr_accessor :dynamic
21
23
  end
22
24
 
23
- class MultiRowExpression
24
- #this class is meant to be used in congunction with headache_sql method
25
- #Could be used like so in headache_sql:
26
-
27
- #ApplicationRecord.headache_sql( "teeeest", %Q{
28
- # INSERT INTO tests(id, username, is_awesome)
29
- # VALUES :rows
30
- # ON CONFLICT SET is_awesome = true
31
- #}, rows: [[1, luke, true], [2, josh, false]])
32
-
33
- #which would output this sql
34
-
35
- # INSERT INTO tests(id, username, is_awesome)
36
- # VALUES ($0,$1,$2),($3,$4,$5)
37
- # ON CONFLICT SET is_awesome = true
38
-
39
- attr_accessor :val
40
- def initialize(val)
41
- #assuming we are putting in an array of arrays.
42
- self.val = val
43
- end
44
- def for_query(x = 0, unique_value_hash:)
45
- #accepts x = current number of variables previously processed
46
- #returns ["sql string with $# location information", variables themselves in order, new x]
47
- db_val = val.map{|attribute_array| "(#{
48
- attribute_index = 0
49
- attribute_array.map{|attribute|
50
- prexist_num = unique_value_hash[attribute]
51
- if prexist_num
52
- attribute_array[attribute_index] = nil
53
- ret = "$#{prexist_num}"
54
- else
55
- unique_value_hash[attribute] = x
56
- ret = "$#{x}"
57
- x += 1
58
- end
59
- attribute_index += 1
60
- next ret
61
- }.join(",")
62
- })"}.join(",")
63
- return db_val, val.flatten.select{|a| not a.nil?}, x
64
- end
65
- end
25
+ class MultiRowExpression
26
+ #this class is meant to be used in congunction with headache_sql method
27
+ #Could be used like so in headache_sql:
28
+
29
+ #ApplicationRecord.headache_sql( "teeeest", %Q{
30
+ # INSERT INTO tests(id, username, is_awesome)
31
+ # VALUES :rows
32
+ # ON CONFLICT SET is_awesome = true
33
+ #}, rows: [[1, luke, true], [2, josh, false]])
34
+
35
+ #which would output this sql
36
+
37
+ # INSERT INTO tests(id, username, is_awesome)
38
+ # VALUES ($0,$1,$2),($3,$4,$5)
39
+ # ON CONFLICT SET is_awesome = true
40
+
41
+ attr_accessor :val
42
+ def initialize(val)
43
+ #assuming we are putting in an array of arrays.
44
+ self.val = val
45
+ end
46
+ def for_query(x = 0, unique_value_hash:)
47
+ #accepts x = current number of variables previously processed
48
+ #returns ["sql string with $# location information", variables themselves in order, new x]
49
+ db_val = val.map{|attribute_array| "(#{
50
+ attribute_index = 0
51
+ attribute_array.map{|attribute|
52
+ prexist_num = unique_value_hash[attribute]
53
+ if prexist_num
54
+ attribute_array[attribute_index] = nil
55
+ ret = "$#{prexist_num}"
56
+ else
57
+ unique_value_hash[attribute] = x
58
+ ret = "$#{x}"
59
+ x += 1
60
+ end
61
+ attribute_index += 1
62
+ next ret
63
+ }.join(",")
64
+ })"}.join(",")
65
+ return db_val, val.flatten.select{|a| not a.nil?}, x
66
+ end
67
+ end
66
68
 
67
69
  module ClassMethods
70
+
68
71
  def has_run_migration?(nm)
69
72
  #put in a string name of the class and it will say if it has allready run the migration.
70
73
  #good during enum migrations as the code to migrate wont run if enumerate is there
@@ -115,12 +118,10 @@ module DynamicRecordsMeritfront
115
118
 
116
119
  return migration_ran
117
120
  end
118
-
119
121
  def list_associations
120
122
  #lists associations (see has_association? below)
121
123
  reflect_on_all_associations.map(&:name)
122
124
  end
123
-
124
125
  def has_association?(*args)
125
126
  #checks whether current class has needed association (for example, checks it has comments)
126
127
  #associations can be seen in has_many belongs_to and other similar methods
@@ -131,8 +132,7 @@ module DynamicRecordsMeritfront
131
132
  args = args.flatten.map { |a| a.to_sym }
132
133
  associations = list_associations
133
134
  (args.length == (associations & args).length)
134
- end
135
-
135
+ end
136
136
  def blind_hgid(id, tag: nil, encode: true)
137
137
  # this method is to get an hgid for a class without actually calling it down from the database.
138
138
  # For example Notification.blind_hgid 1 will give gid://PROJECT_NAME/Notification/69DAB69 etc.
@@ -146,13 +146,11 @@ module DynamicRecordsMeritfront
146
146
  "#{gid}@#{tag}"
147
147
  end
148
148
  end
149
-
150
149
  def string_as_selector(str, attribute: 'id')
151
150
  #this is needed to allow us to quey various strange characters in the id etc. (see hgids)
152
151
  #also useful for querying various attributes
153
152
  return "*[#{attribute}=\"#{str}\"]"
154
153
  end
155
-
156
154
  def locate_hgid(hgid_string, with_associations: nil, returns_nil: false)
157
155
  if hgid_string == nil or hgid_string.class != String
158
156
  if returns_nil
@@ -199,7 +197,6 @@ module DynamicRecordsMeritfront
199
197
  raise StandardError.new 'Not the expected class, or a subclass of ApplicationRecord if called on that.'
200
198
  end
201
199
  end
202
-
203
200
  def get_hgid_tag(hgid_string)
204
201
  if hgid_string.include?('@')
205
202
  return hgid_string.split('@')[-1]
@@ -242,138 +239,319 @@ module DynamicRecordsMeritfront
242
239
  Array => Proc.new{ |first_el_class| ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(DB_TYPE_MAPS[first_el_class].new) }
243
240
  }
244
241
 
245
- def convert_to_query_attribute(name, v)
246
- #yes its dumb I know dont look at me look at rails
247
-
248
- # https://stackoverflow.com/questions/40407700/rails-exec-query-bindings-ignored
249
- # binds = [ ActiveRecord::Relation::QueryAttribute.new(
250
- # "id", 6, ActiveRecord::Type::Integer.new
251
- # )]
252
- # ApplicationRecord.connection.exec_query(
253
- # 'SELECT * FROM users WHERE id = $1', 'sql', binds
254
- # )
255
-
256
- return v if v.kind_of? ActiveRecord::Relation::QueryAttribute #so users can have fine-grained control if they are trying to do something
257
- #that we didn't handle properly.
258
-
259
- type = DB_TYPE_MAPS[v.class]
260
- if type.nil?
261
- raise StandardError.new("#{v}'s class #{v.class} unsupported type right now for ApplicationRecord#headache_sql")
262
- elsif type.class == Proc
263
- a = v[0]
264
- # if a.nil?
265
- # a = Integer
266
- # elsif a.class == Array
267
- a = a.nil? ? Integer : a.class
268
- type = type.call(a)
269
- else
270
- type = type.new
271
- end
272
-
273
- ActiveRecord::Relation::QueryAttribute.new( name, v, type )
274
- end
275
-
242
+ def convert_to_query_attribute(name, v)
243
+ #yes its dumb I know dont look at me look at rails
244
+
245
+ # https://stackoverflow.com/questions/40407700/rails-exec-query-bindings-ignored
246
+ # binds = [ ActiveRecord::Relation::QueryAttribute.new(
247
+ # "id", 6, ActiveRecord::Type::Integer.new
248
+ # )]
249
+ # ApplicationRecord.connection.exec_query(
250
+ # 'SELECT * FROM users WHERE id = $1', 'sql', binds
251
+ # )
252
+
253
+ return v if v.kind_of? ActiveRecord::Relation::QueryAttribute #so users can have fine-grained control if they are trying to do something
254
+ #that we didn't handle properly.
255
+
256
+ type = DB_TYPE_MAPS[v.class]
257
+ if type.nil?
258
+ raise StandardError.new("#{v}'s class #{v.class} unsupported type right now for ApplicationRecord#headache_sql")
259
+ elsif type.class == Proc
260
+ a = v[0]
261
+ # if a.nil?
262
+ # a = Integer
263
+ # elsif a.class == Array
264
+ a = a.nil? ? Integer : a.class
265
+ type = type.call(a)
266
+ else
267
+ type = type.new
268
+ end
269
+
270
+ ActiveRecord::Relation::QueryAttribute.new( name, v, type )
271
+ end
276
272
  #allows us to preload on a list and not a active record relation. So basically from the output of headache_sql
277
- def headache_preload(records, associations)
273
+ def dynamic_preload(records, associations)
278
274
  ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
279
275
  end
280
276
 
281
- def headache_sql(name, sql, opts = { }) #see below for opts
282
- # - instantiate_class - returns User, Post, etc objects instead of straight sql output.
283
- # I prefer doing the alterantive
284
- # User.headache_class(...)
285
- # which is also supported
286
- # - prepare sets whether the db will preprocess the strategy for lookup (defaults true) (I dont think turning this off works...)
287
- # - name_modifiers allows one to change the preprocess associated name, useful in cases of dynamic sql.
288
- # - multi_query allows more than one query (you can seperate an insert and an update with ';' I dont know how else to say it.)
289
- # this disables other options (except name_modifiers). Not sure how it effects prepared statements. Its a fairly useless
290
- # command as you can do multiple queries anyway with 'WITH' statements and also gain the other options.
291
- # - async does what it says but I haven't used it yet so. Probabably doesn't work
292
- #
293
- # Any other option is assumed to be a sql argument (see other examples in code base)
294
-
295
- #grab options from the opts hash
296
- instantiate_class = opts.delete(:instantiate_class)
297
- name_modifiers = opts.delete(:name_modifiers)
298
- name_modifiers ||= []
299
- prepare = opts.delete(:prepare) != false
300
- multi_query = opts.delete(:multi_query) == true
301
- async = opts.delete(:async) == true
302
- params = opts
303
-
304
- #unique value hash cuts down on the number of repeated arguments like in an update or insert statement
305
- #by checking if there is an equal existing argument and then using that argument number instead.
306
- #If this functionality is used at a lower level we should probably remove this.
307
- unique_value_hash = {}
308
-
309
- #allows dynamic sql prepared statements.
310
- for mod in name_modifiers
311
- name << "_#{mod.to_s}" unless mod.nil?
312
- end
313
-
314
- unless multi_query
315
- #https://stackoverflow.com/questions/49947990/can-i-execute-a-raw-sql-query-leverage-prepared-statements-and-not-use-activer/67442353#67442353
316
- #change the keys to $1, $2 etc. this step is needed for ex. {id: 1, id_user: 2}.
317
- #doing the longer ones first prevents id replacing :id_user -> 1_user
318
- keys = params.keys.sort{|a,b| b.to_s.length <=> a.to_s.length}
319
- sql_vals = []
320
- x = 1
321
- for key in keys
322
- #replace the key with $1, $2 etc
323
- v = params[key]
324
-
325
- #this is where we guess what it is
326
- looks_like_multi_attribute_array = ((v.class == Array) and (not v.first.nil?) and (v.first.class == Array))
327
-
328
- if v.class == MultiRowExpression or looks_like_multi_attribute_array
329
- #it looks like or is a multi-row expression (like those in an insert statement)
330
- v = MultiRowExpression.new(v) if looks_like_multi_attribute_array
331
- #process into usable information
332
- sql_for_replace, mat_vars, new_x = v.for_query(x, unique_value_hash: unique_value_hash)
333
- #replace the key with the sql
334
- if sql.gsub!(":#{key}", sql_for_replace) != nil
335
- #if successful set the new x number and append variables to our sql variables
336
- x = new_x
337
- name_num = 0
338
- mat_vars.each{|mat_var|
339
- name_num += 1
340
- sql_vals << convert_to_query_attribute("#{key}_#{name_num}", mat_var)
341
- }
342
- end
343
- else
344
- prexist_arg_num = unique_value_hash[v]
345
- if prexist_arg_num
346
- sql.gsub!(":#{key}", "$#{prexist_arg_num}")
347
- else
348
- if sql.gsub!(":#{key}", "$#{x}") == nil
349
- #nothing changed, param not used, delete it
350
- params.delete key
351
- else
352
- unique_value_hash[v] = x
353
- sql_vals << convert_to_query_attribute(key, v)
354
- x += 1
355
- end
356
- end
357
- end
358
- end
359
- ret = ActiveRecord::Base.connection.exec_query sql, name, sql_vals, prepare: prepare, async: async
360
- else
361
- ret = ActiveRecord::Base.connection.execute sql, name
362
- end
363
-
364
- #this returns a PG::Result object, which is pretty basic. To make this into User/Post/etc objects we do
365
- #the following
366
- if instantiate_class or self != ApplicationRecord
367
- instantiate_class = self if not instantiate_class
368
- #no I am not actually this cool see https://stackoverflow.com/questions/30826015/convert-pgresult-to-an-active-record-model
369
- fields = ret.columns
370
- vals = ret.rows
371
- ret = vals.map { |v|
372
- instantiate_class.instantiate(Hash[fields.zip(v)])
373
- }
374
- end
375
- ret
376
- end
277
+ alias headache_preload dynamic_preload
278
+
279
+ def dynamic_sql(name, sql, opts = { }) #see below for opts
280
+ # - instantiate_class - returns User, Post, etc objects instead of straight sql output.
281
+ # I prefer doing the alterantive
282
+ # User.headache_class(...)
283
+ # which is also supported
284
+ # - prepare sets whether the db will preprocess the strategy for lookup (defaults true) (I dont think turning this off works...)
285
+ # - name_modifiers allows one to change the preprocess associated name, useful in cases of dynamic sql.
286
+ # - multi_query allows more than one query (you can seperate an insert and an update with ';' I dont know how else to say it.)
287
+ # this disables other options (except name_modifiers). Not sure how it effects prepared statements. Its a fairly useless
288
+ # command as you can do multiple queries anyway with 'WITH' statements and also gain the other options.
289
+ # - async does what it says but I haven't used it yet so. Probabably doesn't work
290
+ # - instaload is actually insane just look at the example in the readme yolo
291
+ #
292
+ # Any other option is assumed to be a sql argument (see other examples in code base)
293
+
294
+ #grab options from the opts hash
295
+ instantiate_class = opts.delete(:instantiate_class)
296
+ name_modifiers = opts.delete(:name_modifiers)
297
+ raw = opts.delete(:raw)
298
+ raw = DYNAMIC_SQL_RAW if raw.nil?
299
+ name_modifiers ||= []
300
+ prepare = opts.delete(:prepare) != false
301
+ multi_query = opts.delete(:multi_query) == true
302
+ async = opts.delete(:async) == true
303
+ params = opts
304
+
305
+ #unique value hash cuts down on the number of repeated arguments like in an update or insert statement
306
+ #by checking if there is an equal existing argument and then using that argument number instead.
307
+ #If this functionality is used at a lower level we should probably remove this.
308
+ unique_value_hash = {}
309
+
310
+ #allows dynamic sql prepared statements.
311
+ for mod in name_modifiers
312
+ name << "_#{mod.to_s}" unless mod.nil?
313
+ end
314
+
315
+ unless multi_query
316
+ #https://stackoverflow.com/questions/49947990/can-i-execute-a-raw-sql-query-leverage-prepared-statements-and-not-use-activer/67442353#67442353
317
+ #change the keys to $1, $2 etc. this step is needed for ex. {id: 1, id_user: 2}.
318
+ #doing the longer ones first prevents id replacing :id_user -> 1_user
319
+ keys = params.keys.sort{|a,b| b.to_s.length <=> a.to_s.length}
320
+ sql_vals = []
321
+ x = 1
322
+ for key in keys
323
+ #replace the key with $1, $2 etc
324
+ v = params[key]
325
+
326
+ #this is where we guess what it is
327
+ looks_like_multi_attribute_array = ((v.class == Array) and (not v.first.nil?) and (v.first.class == Array))
328
+
329
+ if v.class == MultiRowExpression or looks_like_multi_attribute_array
330
+ #it looks like or is a multi-row expression (like those in an insert statement)
331
+ v = MultiRowExpression.new(v) if looks_like_multi_attribute_array
332
+ #process into usable information
333
+ sql_for_replace, mat_vars, new_x = v.for_query(x, unique_value_hash: unique_value_hash)
334
+ #replace the key with the sql
335
+ if sql.gsub!(":#{key}", sql_for_replace) != nil
336
+ #if successful set the new x number and append variables to our sql variables
337
+ x = new_x
338
+ name_num = 0
339
+ mat_vars.each{|mat_var|
340
+ name_num += 1
341
+ sql_vals << convert_to_query_attribute("#{key}_#{name_num}", mat_var)
342
+ }
343
+ end
344
+ else
345
+ prexist_arg_num = unique_value_hash[v]
346
+ if prexist_arg_num
347
+ sql.gsub!(":#{key}", "$#{prexist_arg_num}")
348
+ else
349
+ if sql.gsub!(":#{key}", "$#{x}") == nil
350
+ #nothing changed, param not used, delete it
351
+ params.delete key
352
+ else
353
+ unique_value_hash[v] = x
354
+ sql_vals << convert_to_query_attribute(key, v)
355
+ x += 1
356
+ end
357
+ end
358
+ end
359
+ end
360
+ ret = ActiveRecord::Base.connection.exec_query sql, name, sql_vals, prepare: prepare, async: async
361
+ else
362
+ ret = ActiveRecord::Base.connection.execute sql, name
363
+ end
364
+
365
+ #this returns a PG::Result object, which is pretty basic. To make this into User/Post/etc objects we do
366
+ #the following
367
+ if instantiate_class or not self.abstract_class
368
+ instantiate_class = self if not instantiate_class
369
+ #no I am not actually this cool see https://stackoverflow.com/questions/30826015/convert-pgresult-to-an-active-record-model
370
+ ret = zip_ar_result(ret)
371
+ return ret.map{|r| dynamic_init(instantiate_class, r)}
372
+ # fields = ret.columns
373
+ # vals = ret.rows
374
+ # ret = vals.map { |v|
375
+ # dynamic_init()
376
+ # instantiate_class.instantiate(Hash[fields.zip(v)])
377
+ # }
378
+ else
379
+ if raw
380
+ return ret
381
+ else
382
+ return zip_ar_result(ret)
383
+ end
384
+ end
385
+ end
386
+ alias headache_sql dynamic_sql
387
+
388
+ def instaload(sql, table_name: nil, relied_on: false)
389
+ table_name ||= "_" + self.to_s.underscore.downcase.pluralize
390
+ klass = self.to_s
391
+ sql = "\t" + sql.strip
392
+ return {table_name: table_name, klass: klass, sql: sql, relied_on: relied_on}
393
+ end
394
+
395
+ def _dynamic_instaload_handle_with_statements(with_statements)
396
+ %Q{WITH #{
397
+ with_statements.map{|ws|
398
+ "#{ws[:table_name]} AS (\n#{ws[:sql]}\n)"
399
+ }.join(", \n")
400
+ }}
401
+ end
402
+
403
+ def _dynamic_instaload_union(insta_array)
404
+ insta_array.map{|insta|
405
+ start = "SELECT row_to_json(#{insta[:table_name]}.*) AS row, '#{insta[:klass]}' AS _klass, '#{insta[:table_name]}' AS _table_name FROM "
406
+ if insta[:relied_on]
407
+ ending = "#{insta[:table_name]}\n"
408
+ else
409
+ ending = "(\n#{insta[:sql]}\n) AS #{insta[:table_name]}\n"
410
+ end
411
+ next start + ending
412
+ }.join(" UNION ALL \n")
413
+ #{ 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 ')}
414
+ end
415
+
416
+ def dynamic_instaload_sql(name, insta_array, opts = { })
417
+ with_statements = insta_array.select{|a| a[:relied_on]}
418
+ sql = %Q{
419
+ #{ _dynamic_instaload_handle_with_statements(with_statements) if with_statements.any? }
420
+ #{ _dynamic_instaload_union(insta_array)}
421
+ }
422
+ ret_hash = insta_array.map{|ar| [ar[:table_name].to_s, []]}.to_h
423
+ ApplicationRecord.headache_sql(name, sql, opts).rows.each{|row|
424
+ #need to pre-parsed as it has a non-normal output.
425
+ table_name = row[2]
426
+ klass = row[1].constantize
427
+ json = row[0]
428
+ parsed = JSON.parse(json)
429
+
430
+ ret_hash[table_name ].push dynamic_init(klass, parsed)
431
+ }
432
+ return ret_hash
433
+ end
434
+ alias swiss_instaload_sql dynamic_instaload_sql
435
+
436
+ def dynamic_attach(instaload_sql_output, base_name, attach_name, base_on: nil, attach_on: nil, one_to_one: false, debug: false)
437
+ base_arr = instaload_sql_output[base_name]
438
+
439
+ #return if there is nothing for us to attach to.
440
+ return unless base_arr.any?
441
+
442
+ #set variables for neatness and so we dont compute each time
443
+ # base class information
444
+ base_class = base_arr.first.class
445
+ base_class_is_hash = base_class <= Hash
446
+ # attach name information for variables
447
+ attach_name_sym = attach_name.to_sym
448
+ attach_name_with_at = "@#{attach_name}"
449
+
450
+ #variable accessors and defaults.
451
+ base_arr.each{|o|
452
+ o.singleton_class.public_send(:attr_accessor, attach_name_sym) unless base_class_is_hash
453
+ o.instance_variable_set(attach_name_with_at, []) unless one_to_one
454
+ }
455
+
456
+ #make sure the attach class has something going on
457
+ attach_arr = instaload_sql_output[attach_name]
458
+ return unless attach_arr.any?
459
+
460
+ # attach class information
461
+ attach_class = attach_arr.first.class
462
+ attach_class_is_hash = attach_class <= Hash
463
+
464
+ # default attach column info
465
+ default_attach_col = (base_class.to_s.downcase + "_id")
466
+
467
+ #decide on the method of getting the matching id for the base table
468
+ unless base_on
469
+ if base_class_is_hash
470
+ base_on = Proc.new{|x| x['id']}
471
+ else
472
+ base_on = Proc.new{|x| x.id}
473
+ end
474
+ end
475
+
476
+ #return an id->object hash for the base table for better access
477
+ h = base_arr.map{|o|
478
+ [base_on.call(o), o]
479
+ }.to_h
480
+
481
+ #decide on the method of getting the matching id for the attach table
482
+ unless attach_on
483
+ if attach_class_is_hash
484
+ attach_on = Proc.new{|x| x[default_attach_col]}
485
+ else
486
+ attach_on = Proc.new{|x|
487
+ x.method(default_attach_col).call
488
+ }
489
+ end
490
+ end
491
+
492
+ # if debug
493
+ # Rails.logger.info(base_arr.map{|b|
494
+ # base_on.call(b)
495
+ # })
496
+ # Rails.logger.info(attach_arr.map{|a|
497
+ # attach_on.call(a)
498
+ # })
499
+ # end
500
+
501
+ #method of adding the object to the base
502
+ #(b=base, a=attach)
503
+ add_to_base = Proc.new{|b, a|
504
+ if one_to_one
505
+ if base_class_is_hash
506
+ b[attach_name] = a
507
+ else
508
+ b.instance_variable_set(attach_name_with_at, a)
509
+ end
510
+ else
511
+ if base_class_is_hash
512
+ b[attach_name].push a
513
+ else
514
+ b.instance_variable_get(attach_name_with_at).push a
515
+ end
516
+ end
517
+ }
518
+
519
+ #for every attachable
520
+ # 1. match base id to the attach id (both configurable)
521
+ # 2. cancel out if there is no match
522
+ # 3. otherwise add to the base object.
523
+ attach_arr.each{|attach|
524
+ if out = attach_on.call(attach) #you can use null to escape the vals
525
+ if base = h[out] #it is also escaped if no base element is found
526
+ add_to_base.call(base, attach)
527
+ end
528
+ end
529
+ }
530
+ return attach_arr
531
+ end
532
+ alias swiss_attach dynamic_attach
533
+
534
+ def zip_ar_result(x)
535
+ fields = x.columns
536
+ vals = x.rows
537
+ vals.map { |v|
538
+ Hash[fields.zip(v)]
539
+ }
540
+ end
541
+
542
+ def dynamic_init(klass, input)
543
+ if klass.abstract_class
544
+ return input
545
+ else
546
+ #handle attributes through ar if allowed. Throws an error on unkown variables, except apparently for devise classes? 😡
547
+ active_record_handled = input.slice(*(klass.attribute_names & input.keys))
548
+ record = klass.instantiate(active_record_handled)
549
+ #set those that were not necessarily expected
550
+ not_expected = input.slice(*(input.keys - klass.attribute_names))
551
+ record.dynamic = not_expected.transform_keys{|k|k.to_sym} if not_expected.keys.any?
552
+ return record
553
+ end
554
+ end
377
555
 
378
556
  def quick_safe_increment(id, col, val)
379
557
  where(id: id).update_all("#{col} = #{col} + #{val}")
@@ -415,4 +593,4 @@ module DynamicRecordsMeritfront
415
593
  self.class.where(id: self.id).update_all("#{col} = #{col} + #{val}")
416
594
  end
417
595
 
418
- end
596
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamic-records-meritfront
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.11
4
+ version: 2.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luke Clancy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-17 00:00:00.000000000 Z
11
+ date: 2022-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashid-rails