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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +4 -1
- data/lib/dynamic-records-meritfront/version.rb +1 -1
- data/lib/dynamic-records-meritfront.rb +357 -179
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0ad57039c2e650f4882c75aaeccca4fe55a771db29e0552ef648a9eaf974be8
|
4
|
+
data.tar.gz: 0af2b5a0f1add956c8b6029196237b9945c820204c114498078f8bf80be90ecf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86ca4444f4848bb8af6389f005faf9a9813b9a49f0c0b31450d95d4317470c2c0e3e4b6435d3fba1cf57a7f7f1a02b7763a0dde9861b59a53cfac1681e4c6cbb
|
7
|
+
data.tar.gz: 29593f3c3ec593384c573e74ff2737e2b97e77cb38dbc371c3847693488095a2b828b316fbe105a0a599efa430c33c611f17637808c51f27932d47b76fccbd45
|
data/Gemfile.lock
CHANGED
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
|
|
@@ -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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
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
|
273
|
+
def dynamic_preload(records, associations)
|
278
274
|
ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
|
279
275
|
end
|
280
276
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
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:
|
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-
|
11
|
+
date: 2022-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashid-rails
|