Module: DynamicRecordsMeritfront::ClassMethods

Defined in:
lib/dynamic-records-meritfront.rb

Instance Method Summary collapse

Instance Method Details

#_dynamic_instaload_handle_with_statements(with_statements) ⇒ Object



595
596
597
598
599
600
601
602
603
604
605
# File 'lib/dynamic-records-meritfront.rb', line 595

def _dynamic_instaload_handle_with_statements(with_statements)
    %Q{WITH #{
    with_statements.map{|ws|
			if(ws[:with])
				"#{ws[:with]} AS (\n#{ws[:sql]}\n)"
			else
		"#{ws[:table_name]} AS (\n#{ws[:sql]}\n)"
			end
    }.join(", \n")
}}
end

#_dynamic_instaload_union(insta_array) ⇒ Object



607
608
609
610
611
612
613
614
615
616
617
618
619
620
# File 'lib/dynamic-records-meritfront.rb', line 607

def _dynamic_instaload_union(insta_array)
    insta_array.select{|insta|
        not insta[:dont_return]
    }.map{|insta|
        start = "SELECT row_to_json(#{insta[:table_name]}.*) AS row, '#{insta[:klass]}' AS _klass, '#{insta[:table_name]}' AS _table_name FROM "
        if insta[:relied_on]
            ending = "#{insta[:table_name]}\n"
        else
            ending = "(\n#{insta[:sql]}\n) AS #{insta[:table_name]}\n"
        end
        next start + ending
    }.join(" UNION ALL \n")
    #{ 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 ')}
end

#blind_hgid(id, tag: nil, encode: true) ⇒ Object



380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/dynamic-records-meritfront.rb', line 380

def blind_hgid(id, tag: nil, encode: true)
# this method is to get an hgid for a class without actually calling it down from the database.
# For example Notification.blind_hgid 1 will give gid://PROJECT_NAME/Notification/69DAB69 etc.
    if id.class == Integer and encode
        id = self.encode_id id
    end
    gid = "gid://#{PROJECT_NAME}/#{self.to_s}/#{id}"
    if !tag
        gid
    else
        "#{gid}@#{tag}"
    end
end

#dynamic_attach(instaload_sql_output, base_name, attach_name, base_on: nil, attach_on: nil, one_to_one: false, as: nil) ⇒ Object Also known as: swiss_attach



800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
# File 'lib/dynamic-records-meritfront.rb', line 800

def dynamic_attach(instaload_sql_output, base_name, attach_name, base_on: nil, attach_on: nil, one_to_one: false, as: nil)
    #as just lets it attach us anywhere on the base class, and not just as the attach_name.
    #Can be useful in polymorphic situations, otherwise may lead to confusion.
    
    #oh the errors
    base_name = base_name.to_s
    attach_name = attach_name.to_s

    as ||= attach_name

    base_arr = instaload_sql_output[base_name]
    
    #return if there is nothing for us to attach to.
    if base_arr.nil?
        Rails.logger.error("base table " + base_name + " was not set")
        return 0
    elsif not base_arr.any?
        Rails.logger.debug("no items in base table " + base_name)
        return 0
    end

    #set variables for neatness and so we dont compute each time
    #	base class information
    base_class = base_arr.first.class
    base_class_is_hash = base_class <= Hash
    
    #variable accessors and defaults. Make sure it only sets if not defined already as
    #the 'as' option allows us to override to what value it actually gets set in the end, 
    #and in polymorphic situations this could be called in multiple instances
    base_arr.each{ |o|
        if not base_class_is_hash
            if one_to_one
                #attach name must be a string
                o.questionable_attribute_set(as, nil, as_default: true)
            else
                o.questionable_attribute_set(as, [], as_default: true)
            end
        elsif not one_to_one
            o[as] ||= []
        end
    }

    #make sure the attach class has something going on. We do this after the default stage
    attach_arr = instaload_sql_output[attach_name]
    
    if attach_arr.nil?
        Rails.logger.error("attaching table " + attach_name + " was not set")
        return 0
    elsif not attach_arr.any?
        Rails.logger.debug("no items in attach table " + attach_name)
        return 0
    end
    
    #   attach class information
    attach_class = attach_arr.first.class
    attach_class_is_hash = attach_class <= Hash

    #	default attach column info
    default_attach_col = (base_class.to_s.downcase + "_id")

    #decide on the method of getting the matching id for the base table
    unless base_on
        if base_class_is_hash
            base_on = Proc.new{|x| x['id']}
        else
            base_on = Proc.new{|x| x.id}
        end
    end

    #return an id->object hash for the base table for better access
    h = {}
    duplicates_base = Set[]
    for base_rec in base_arr
        bo = base_on.call(base_rec)
        if h[bo]
            duplicates_base << bo
        else
            h[bo] = base_rec
        end
    end
    
    #decide on the method of getting the matching id for the attach table
    unless attach_on
        if attach_class_is_hash
            attach_on = Proc.new{|x| x[default_attach_col]}
        else
            attach_on = Proc.new{|x| 
                x.attributes[default_attach_col]
            }
        end
    end

    # if debug
    # 	Rails.logger.info(base_arr.map{|b|
    # 		base_on.call(b)
    # 	})
    # 	Rails.logger.info(attach_arr.map{|a|
    # 		attach_on.call(a)
    # 	})
    # end

    #method of adding the object to the base
    #(b=base, a=attach)
    add_to_base = Proc.new{|b, a|
        if base_class_is_hash
            if one_to_one
                b[as] = a
            else
                b[as].push a
            end
        else
            #getting a lil tired of the meta stuff.
            if one_to_one
                b.questionable_attribute_set(as, a)
            else
                b.questionable_attribute_set(as, a, push: true)
            end
        end
    }

    #for every attachable
    #	1. match base id to the attach id (both configurable)
    #	2. cancel out if there is no match
    #	3. otherwise add to the base object.
    x = 0

    attach_arr.each{|attach_rec|
        #we have it plural in case it attaches to multiple, for example a user can belong to many post-cards. Yes, this
        #was a bug. In order to solve it you have to do some sort of 'distinct' or 'group' sql.
        
        attachment_keys = attach_on.call(attach_rec) #you can use null to escape the vals
        
        if attachment_keys.nil?
            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."
        elsif not attachment_keys.kind_of? Array
            attachment_keys = [attachment_keys]
        end
        
        if attachment_keys and attachment_keys.any?
            for ak in attachment_keys
                base_rec = h[ak] #it is also escaped if no base element is found
                if base_rec
                    dupl = duplicates_base.include? ak
                    if dupl
                        Rails.logger.warn "WARNING in #{attach_name} -> #{base_name}. Duplicate base_on key being utilized (this is usually in error). Only one base record will have an attachment. For the base table, consider using GROUP BY id and ARRAY_AGG for the base_on column."
                        Rails.logger.warn "base_on key: #{ak.to_s}"
                    end
                    
                    x += 1 unless dupl
                    add_to_base.call(base_rec, attach_rec)
                end
            end
        end
    }

    if Rails.logger.level <= 1
        if as
            Rails.logger.debug "#{x}/#{attach_arr.count} attached from #{attach_name} as #{as} -> #{base_name}(#{x}/#{base_arr.count})"
        else
            Rails.logger.debug "#{x}/#{attach_arr.count} attached from #{attach_name} -> #{base_name}(#{x}/#{base_arr.count})"
        end
    end

    return x
end

#dynamic_init(klass, input) ⇒ Object



971
972
973
974
975
976
977
978
979
980
981
982
983
984
# File 'lib/dynamic-records-meritfront.rb', line 971

def dynamic_init(klass, input)
    if klass.abstract_class
        return input
    else
        record = klass.instantiate(input.stringify_keys ) #trust me they need to be stringified
        # #handle attributes through ar if allowed. Throws an error on unkown variables, except apparently for devise classes? 😡
        # active_record_handled = input.slice(*(klass.attribute_names & input.keys))
        # record = klass.instantiate(active_record_handled)
        # #set those that were not necessarily expected
        # not_expected = input.slice(*(input.keys - klass.attribute_names))
        # record.dynamic = OpenStruct.new(not_expected.transform_keys{|k|k.to_sym}) if not_expected.keys.any?
        return record
    end
end

#dynamic_preload(records, associations) ⇒ Object Also known as: headache_preload

allows us to preload on a list and not a active record relation. So basically from the output of headache_sql



458
459
460
# File 'lib/dynamic-records-meritfront.rb', line 458

def dynamic_preload(records, associations)
    ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
end

#dynamic_sql(*args) ⇒ Object Also known as: headache_sql

see below for opts



464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
# File 'lib/dynamic-records-meritfront.rb', line 464

def dynamic_sql(*args) #see below for opts
# call like: dynamic_sql(name, sql, option_1: 1, option_2: 2)
#   or like: dynamic_sql(sql, {option: 1, option_2: 2})
#   or like: dynamic_sql(sql, option: 1, option_2: 2)
#	or just: dynamic_sql(sql)
#
# Options: (options not listed will be sql arguments)
# - instantiate_class - returns User, Post, etc objects instead of straight sql output.
#		I prefer doing the alterantive
#			User.headache_class(...)
#		which is also supported
# - prepare sets whether the db will preprocess the strategy for lookup (defaults true) (I dont think turning this off works...)
# - name_modifiers allows one to change the preprocess associated name, useful in cases of dynamic sql.
# - multi_query allows more than one query (you can seperate an insert and an update with ';' I dont know how else to say it.)
#		this disables other options (except name_modifiers). Not sure how it effects prepared statements. Its a fairly useless
#		command as you can do multiple queries anyway with 'WITH' statements and also gain the other options.
# - raw switches between using a Hash or a ActiveRecord::Response object when used on a abstract class
    args << {} unless args[-1].kind_of? Hash
    if args.length == 3
        name, sql, opts = args
    elsif args.length == 2
        sql, opts = args
        #give default name functionality as a pointer to source code location
        #of the method that called this. Love ruby. Meta up the a$$
        first_app_stack_trace = caller[0...3].select{|str| not str.include?('dynamic_records_meritfront.rb')}.first
        shorter_source_loc = first_app_stack_trace.split('/')[-1]
        name = shorter_source_loc
    else
        raise StandardError.new("bad input to DynamicRecordsMeritfront#dynamic_sql method.")
    end

    #grab options from the opts hash
    instantiate_class = opts.delete(:instantiate_class)
    name_modifiers = opts.delete(:name_modifiers)
    raw = opts.delete(:raw)
    raw = DYNAMIC_SQL_RAW if raw.nil?
    name_modifiers ||= []
    prepare = opts.delete(:prepare) != false
    multi_query = opts.delete(:multi_query) == true
    params = opts

        #unique value hash cuts down on the number of repeated arguments like in an update or insert statement
        #by checking if there is an equal existing argument and then using that argument number instead.
        #If this functionality is used at a lower level we should probably remove this.
        #________________________________
        #got this error: ActiveRecord::StatementInvalid (PG::ProtocolViolation: ERROR:  bind message supplies 3 parameters, but prepared statement "a27" requires 4)
        #this error tells me two things
        #   1. the name of a sql statement actually has no effect on prepared statements (whoops).
        #       This means we should accept queries with no name.
        #   2. Need to get rid of the unique variable name functionality which uniques all the variables
        #       to decrease the amount sent to database

    #name_modifiers are super unnecessary now I realize the given name is not actually related
    #to prepped statements. But will keep it as it is backwards compatitable and sorta useful maybe.
    for mod in name_modifiers
        name << "_#{mod.to_s}" unless mod.nil?
    end
    begin
        var_track = DynamicSqlVariables.new(params)
        unless multi_query
            #https://stackoverflow.com/questions/49947990/can-i-execute-a-raw-sql-query-leverage-prepared-statements-and-not-use-activer/67442353#67442353
            #change the keys to $1, $2 etc. this step is needed for ex. {id: 1, id_user: 2}.
            #doing the longer ones first prevents id replacing :id_user -> $1_user
            keys = params.keys.sort{|a,b| b.to_s.length <=> a.to_s.length}

            for key in keys
                #replace MultiRowExpressions
                v = params[key]
                #check if it looks like one
                looks_like_multi_row_expression = ((v.class == Array) and (not v.first.nil?) and (v.first.class == Array))
                if v.class == MultiRowExpression or looks_like_multi_row_expression
                #we need to substitute with the correct sql now.
                    v = MultiRowExpression.new(v) if looks_like_multi_row_expression #standardize
                    #process into appropriate sql while keeping track of variables
                    sql_for_replace = v.for_query(key, var_track)
                    #replace the key with the sql
                    sql.gsub!(":#{key}", sql_for_replace)
                else
                    #check if its currently in the sql argument list
                    x = var_track.key_index(key)
                    if x.nil?
                        #if not, get the next number that it will be assigned and replace the key w/ that number.
                        x = var_track.next_sql_num
                        if sql.gsub!(":#{key}", "$#{x}")
                            #only actually add to sql arguments when we know the attribute was used.
                            var_track.add_key_value(key, v)
                        end
                    else
                        #its already in use as a sql argument and has a number, use that number.
                        sql.gsub!(":#{key}", "$#{x}")
                    end
                end
            end
            sql_vals = var_track.get_array_for_exec_query
            ret = ActiveRecord::Base.connection.exec_query sql, name, sql_vals, prepare: prepare
        else
            ret = ActiveRecord::Base.connection.execute sql, name
        end
    rescue Exception => e
        #its ok if some of these are empty, just dont want the error
        name ||= ''
        sql ||= ''
        sql_vals ||= ''
        prepare ||= ''
        Rails.logger.error(%Q{
    DynamicRecords#dynamic_sql debug info.
    name: #{name.to_s}
    sql: #{sql.to_s}
    sql_vals: #{sql_vals.to_s}
    prepare: #{prepare.to_s}
    })
        raise e
    end

    #this returns a PG::Result object, which is pretty basic. To make this into User/Post/etc objects we do
            #the following
    if instantiate_class or not self.abstract_class
        instantiate_class = self if not instantiate_class
        #no I am not actually this cool see https://stackoverflow.com/questions/30826015/convert-pgresult-to-an-active-record-model
        ret = ret.to_a
        return ret.map{|r| dynamic_init(instantiate_class, r)}
    else
        if raw
            return ret
        else
            return ret.to_a
        end
    end
end

#get_hgid_tag(hgid_string) ⇒ Object



449
450
451
452
453
454
455
# File 'lib/dynamic-records-meritfront.rb', line 449

def get_hgid_tag(hgid_string)
    if hgid_string.include?('@')
        return hgid_string.split('@')[-1]
    else
        return nil
    end
end

#has_association?(*args) ⇒ Boolean

Returns:

  • (Boolean)


369
370
371
372
373
374
375
376
377
378
379
# File 'lib/dynamic-records-meritfront.rb', line 369

def has_association?(*args)
    #checks whether current class has needed association (for example, checks it has comments)
    #associations can be seen in has_many belongs_to and other similar methods

    #flattens so you can pass self.has_association?(:comments, :baseable_comments) aswell as
    #		self.has_association?([:comments, :baseable_comments]) without issue
    #		
    args = args.flatten.map { |a| a.to_sym }
    associations = list_associations
    (args.length == (associations & args).length)
end

#has_run_migration?(nm) ⇒ Boolean

Returns:

  • (Boolean)


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
# File 'lib/dynamic-records-meritfront.rb', line 315

def has_run_migration?(nm)
#put in a string name of the class and it will say if it has allready run the migration.
#good during enum migrations as the code to migrate wont run if enumerate is there 
#as it is not yet enumerated (causing an error when it loads the class that will have the
#enumeration in it). This can lead it to being impossible to commit clean code.
#
# example usage one: only create the record class if it currently exists in the database
    # if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
    # 	class UserImageRelation < ApplicationRecord
    # 		belongs_to :imageable, polymorphic: true
    # 		belongs_to :image
    # 	end
    # else
    # 	class UserImageRelation; end
    # end
# example usage two: only load relation if it exists in the database
    # class UserImageRelation < ApplicationRecord
    #	if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
    #	 	belongs_to :imageable, polymorphic: true
    #	end
    # end
#	
    #current version of migrations
    cv = ActiveRecord::Base.connection.migration_context.current_version
    
    #find the migration object for the name
    migration = ActiveRecord::Base.connection.migration_context.migrations.filter!{|a|
            a.name == nm
        }.first

    #if the migration object is nil, it has not yet been created
    if migration.nil?
        Rails.logger.info "No migration found for #{nm}. The migration has not yet been created, or is foreign to this database."
        return false
    end
    
    #get the version number for the migration name
    needed_version = migration.version
    
    #if current version is above or equal, the migration has allready been run
    migration_ran = (cv >= needed_version)
    
    if migration_ran
        Rails.logger.info "#{nm} migration was run on #{needed_version}. If old and all instances are migrated, consider removing code check."
    else
        Rails.logger.info "#{nm} migration has not run yet. This may lead to limited functionality"
    end
    
    return migration_ran
end

#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, with: nil) ⇒ Object

Raises:

  • (StandardError)


622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
# File 'lib/dynamic-records-meritfront.rb', line 622

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, with: nil)
    #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.
    #At the end of the day it just returns a hash with the settings in it though. So dont overthink it too much.

    as = as.to_s if as
    base_name = base_name.to_s if base_name

    if table_name
        table_name = table_name.to_s
    else
        table_name = "_" + self.to_s.underscore.downcase.pluralize
    end

    klass = self.to_s

    sql = "\t" + sql.strip
    raise StandardError.new("base_on needs to be nil or a Proc") unless base_on.nil? or base_on.kind_of? Proc
    raise StandardError.new("attach_on needs to be nil or a Proc") unless attach_on.nil? or attach_on.kind_of? Proc
    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, with: with }
end

#instaload_sql(*args) ⇒ Object Also known as: swiss_instaload_sql, dynamic_instaload_sql

name, insta_array, opts = { })



644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
# File 'lib/dynamic-records-meritfront.rb', line 644

def instaload_sql(*args) #name, insta_array, opts = { })
    args << {} unless args[-1].kind_of? Hash
    if args.length == 3
        name, insta_array, opts = args
    elsif args.length == 2
        insta_array, opts = args
        name = nil
    else
        raise StandardError.new("bad input to DynamicRecordsMeritfront#instaload_sql method.")
    end

    with_statements = insta_array.select{|a| a[:relied_on] or a[:with]}
    sql = %Q{
#{ _dynamic_instaload_handle_with_statements(with_statements) if with_statements.any? }
#{ _dynamic_instaload_union(insta_array)}
}
    insta_array = insta_array.select{|ar| not ar[:dont_return]}
    ret_hash = insta_array.map{|ar| [ar[:table_name].to_s, []]}.to_h
    opts[:raw] = true

    #annoying bug
    s = self
    unless s.abstract_class?
        s = s.superclass
    end
    
    s.dynamic_sql(name, sql, opts).rows.each{|row|
        #need to pre-parsed as it has a non-normal output.
        table_name = row[2]
        klass = row[1].constantize
        json = row[0]
        parsed = JSON.parse(json)
        ret_hash[table_name].push dynamic_init(klass, parsed)
    }

    insta_array.each{|a| a.delete(:sql)}
    
    #formatting options
    for insta in insta_array
        if insta[:base_name]
            #in this case, 'as' is meant as to what pseudonym to dynamicly attach it as
            #we are attaching to the base table. Variable could of been less confusing. My bad.
            dynamic_attach(ret_hash, insta[:base_name], insta[:table_name], base_on: insta[:base_on], attach_on: insta[:attach_on],
                one_to_one: insta[:one_to_one], as: insta[:as])
        elsif insta[:as]
            Rails.logger.debug "#{insta[:table_name]} as #{insta[:as]}"
            #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
            #      table_name)
            if ret_hash[insta[:as]]
                ret_hash[insta[:as]] += ret_hash[insta[:table_name]]
            else
                ret_hash[insta[:as]] = ret_hash[insta[:table_name]].dup #only top level dup
            end
        else
            Rails.logger.debug "#{insta[:table_name]}"
        end
    end

    return ret_hash
end

#list_associationsObject



365
366
367
368
# File 'lib/dynamic-records-meritfront.rb', line 365

def list_associations
    #lists associations (see has_association? below)
    reflect_on_all_associations.map(&:name)
end

#locate_hgid(hgid_string, with_associations: nil, returns_nil: false) ⇒ Object



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/dynamic-records-meritfront.rb', line 398

def locate_hgid(hgid_string, with_associations: nil, returns_nil: false)
    if hgid_string == nil or hgid_string.class != String
        if returns_nil
            return nil
        else
            raise StandardError.new("non-string class passed to DynamicRecordsMeritfront#locate_hgid as the hgid_string variable")
        end
    end
    if PROJECT_NAME == 'midflip'
        #should be fine to take out in a month or so, just got lazy and pretty sure I am the only one using this gem.
        #dont want to kill me jobs.
        hgid_string = hgid_string.gsub('ApplicationRecord', 'Record')
    end
    if hgid_string.include?('@')
        hgid_string = hgid_string.split('@')
        hgid_string.pop
        hgid_string = hgid_string.join('@') # incase the model was a tag that was tagged. (few months later: Wtf? Guess ill keep it)
    end
    #split the thing
    splitz = hgid_string.split('/')
    #get the class
    begin
        cls = splitz[-2].constantize
    rescue NameError, NoMethodError
        if returns_nil
            nil
        else
            raise StandardError.new 'Unusual or unavailable string or hgid'
        end
    end
    #get the hash
    hash = splitz[-1]
    # if self == ApplicationRecord (for instance), then check that cls is a subclass
    # if self is not ApplicationRecord, then check cls == this objects class
    # if with_associations defined, make sure that the class has the associations given (see has_association above)
    if ((self.abstract_class? and cls < self) or ( (not self.abstract_class?) and cls == self )) and
        ( with_associations == nil or cls.has_association?(with_associations) )
        #if all is as expected, return the object with its id.
        if block_given?
            yield(hash)
        else
            cls.hfind(hash)
        end
    elsif returns_nil
        #allows us to handle issues with input
        nil
    else
        #stops execution as default
        raise StandardError.new 'Not the expected class or subclass.'
    end
end

#string_as_selector(str, attribute: 'id') ⇒ Object



393
394
395
396
397
# File 'lib/dynamic-records-meritfront.rb', line 393

def string_as_selector(str, attribute: 'id')
    #this is needed to allow us to quey various strange characters in the id etc. (see hgids)
    #also useful for querying various attributes
    return "*[#{attribute}=\"#{str}\"]"
end

#test_drmf(model_with_an_id_column_and_timestamps) ⇒ Object



707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
# File 'lib/dynamic-records-meritfront.rb', line 707

def test_drmf(model_with_an_id_column_and_timestamps)
    m = model_with_an_id_column_and_timestamps
    ar = m.superclass
    mtname = m.table_name
    transaction do
        puts 'test recieving columns not normally in the record.'
        rec = m.dynamic_sql(%Q{
            SELECT id, 5 AS random_column from #{mtname} LIMIT 10
        }).first
        raise StandardError.new('no id') unless rec.id
        raise StandardError.new('no dynamic column') unless rec.random_column
        puts 'pass 1'
        
        puts 'test raw off with a custom name'
        recs = ar.dynamic_sql('test_2', %Q{
            SELECT id, 5 AS random_column from #{mtname} LIMIT 10
        }, raw: false)
        raise StandardError.new('not array of hashes') unless recs.first.class == Hash and recs.class == Array
        rec = recs.first
        raise StandardError.new('no id [raw off]') unless rec['id']
        raise StandardError.new('no dynamic column [raw off]') unless rec['random_column']
        puts 'pass 2'

        puts 'test raw on'
        recs = ar.dynamic_sql('test_3', %Q{
            SELECT id, 5 AS random_column from #{mtname} LIMIT 10
        }, raw: true)
        raise StandardError.new('not raw') unless recs.class == ActiveRecord::Result
        rec = recs.first
        raise StandardError.new('no id [raw]') unless rec['id']
        raise StandardError.new('no dynamic column [raw]') unless rec['random_column']
        puts 'pass 3'

        puts 'test when some of the variables are diffrent then the same (#see version 3.0.1 notes)'
        x = Proc.new { |a, b|
            recs = ar.dynamic_sql('test_4', %Q{
                SELECT id, 5 AS random_column from #{mtname} WHERE id > :a LIMIT :b
            }, a: a, b: b)
        }
        x.call(1, 2)
        x.call(1, 1)
        puts 'pass 4'

        puts 'test MultiAttributeArrays, including symbols and duplicate values.'
        time = DateTime.now
        ids = m.limit(5).pluck(:id)
        values = ids.map{|id|
            [id, :time, time]
        }
        ar.dynamic_sql(%Q{
            INSERT INTO #{mtname} (id, created_at, updated_at)
            VALUES :values
            ON CONFLICT (id) DO NOTHING
        }, values: values, time: time)
        puts 'pass 5'
        
        puts 'test arrays'
        recs = ar.dynamic_sql(%Q{
            SELECT id from #{mtname} where id = ANY(:idz)
        }, idz: ids, raw: false)
        puts recs
        raise StandardError.new('wrong length') if recs.length != 5
        puts 'pass 6'

        
        puts 'test instaload_sql'
        out = ar.instaload_sql([
            ar.instaload("SELECT id FROM users", relied_on: true, dont_return: true, table_name: "users_2"),
            ar.instaload("SELECT id FROM users_2 WHERE id % 2 != 0 LIMIT :limit", table_name: 'a'),
            m.instaload("SELECT id FROM users_2 WHERE id % 2 != 1 LIMIT :limit", table_name: 'b')
        ], limit: 2)
        puts out
        raise StandardError.new('Bad return') if out["users_2"]
        raise StandardError.new('Bad return') unless out["a"]
        raise StandardError.new('Bad return') unless out["b"]
        puts 'pass 7'

        puts "test dynamic_sql V3.0.6 error to do with multi_attribute_arrays which is hard to describe"
        time = DateTime.now
        values = [[1, :time, :time], [2, :time, :time]]
        out = ar.dynamic_sql(%Q{
            insert into #{mtname} (id, created_at, updated_at)
            values :values
            on conflict (id)
            do update set updated_at = :time
        }, time: time, values: values)
        puts 'pass 8'

        raise ActiveRecord::Rollback
        #ApplicationRecord.dynamic_sql("SELECT * FROM")
    end
end

#zip_ar_result(x) ⇒ Object



967
968
969
# File 'lib/dynamic-records-meritfront.rb', line 967

def zip_ar_result(x)
    x.to_a
end