dynamic-records-meritfront 1.1.11 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
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