dynamic-records-meritfront 3.1.0 → 3.1.29

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: 187dd89025805fae04c8f188a900f6373cfd84584a720787dec852c0281a2060
4
- data.tar.gz: 778e0e23ede44cc3fbb304e7216d4846745c25cb0e9fc1d87c8871375b21efcb
3
+ metadata.gz: 0e329f6fa51f14843549a6d3681870a99a5e1b9ff78af32281ca8a5433122922
4
+ data.tar.gz: 5c14dc25ee35ea526f220d5b727032dcaf1be0f1bb744c080c9a12c4a70b15bb
5
5
  SHA512:
6
- metadata.gz: e1071cf066822a8da8b1bf522c51c80e012b7e8c8c137aa3c7666a2965952d254bd863d4fbb9df4d77192458ff0b744a96e01e7485f2be6a94fc1faa37b1fe7f
7
- data.tar.gz: eebfbac3a6a3c0202255f15bad0ced512d8c60c79f601aaada4706790a5af518c06b7413dce5ffb38e6f5e9e8318de7221ae12049ec4129f1ed23a7efb55b1d4
6
+ metadata.gz: 19070cbac3c6442ba5ba82c28d370efe9a3cc6cfd0e6a05e5a74175982000484baa804baceb187b00badb865ba57e6f9059bf878670c518830c97ea904157779
7
+ data.tar.gz: 85892bfa3f8708bbb683297513ffb0f10650ad389084a219203241918e52208041c29a477e478027e3f841e0235b983fd1948f1ff65389b34efcbe18c531b805
data/Gemfile.lock CHANGED
@@ -1,31 +1,45 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dynamic-records-meritfront (3.1.0)
4
+ dynamic-records-meritfront (3.1.29)
5
5
  hashid-rails
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (7.0.4.2)
11
- activesupport (= 7.0.4.2)
12
- activerecord (7.0.4.2)
13
- activemodel (= 7.0.4.2)
14
- activesupport (= 7.0.4.2)
15
- activesupport (7.0.4.2)
10
+ activemodel (7.1.1)
11
+ activesupport (= 7.1.1)
12
+ activerecord (7.1.1)
13
+ activemodel (= 7.1.1)
14
+ activesupport (= 7.1.1)
15
+ timeout (>= 0.4.0)
16
+ activesupport (7.1.1)
17
+ base64
18
+ bigdecimal
16
19
  concurrent-ruby (~> 1.0, >= 1.0.2)
20
+ connection_pool (>= 2.2.5)
21
+ drb
17
22
  i18n (>= 1.6, < 2)
18
23
  minitest (>= 5.1)
24
+ mutex_m
19
25
  tzinfo (~> 2.0)
20
- concurrent-ruby (1.2.0)
26
+ base64 (0.1.1)
27
+ bigdecimal (3.1.4)
28
+ concurrent-ruby (1.2.2)
29
+ connection_pool (2.4.1)
30
+ drb (2.1.1)
31
+ ruby2_keywords
21
32
  hashid-rails (1.4.1)
22
33
  activerecord (>= 4.0)
23
34
  hashids (~> 1.0)
24
35
  hashids (1.0.6)
25
- i18n (1.12.0)
36
+ i18n (1.14.1)
26
37
  concurrent-ruby (~> 1.0)
27
- minitest (5.17.0)
38
+ minitest (5.20.0)
39
+ mutex_m (0.1.2)
28
40
  rake (12.3.3)
41
+ ruby2_keywords (0.0.5)
42
+ timeout (0.4.0)
29
43
  tzinfo (2.0.6)
30
44
  concurrent-ruby (~> 1.0)
31
45
 
data/README.md CHANGED
@@ -5,8 +5,7 @@ Dyanmic Records Meritfront helps extend active record functionality to make it m
5
5
  2. communicate with the backend more effectively with sql queries. This becomes especially relevant when you hit the limits of Active Record Relations and the usual way of querying in rails. For instance, if you have dynamic sql queries that are hard to convert properly into ruby.
6
6
  3. add other helper methods to work with your database, such as checking if relations exist, or if a migration has been run.
7
7
 
8
- Note that postgres is currently a requirement for using arrays in this gem. If it becomes an issue send me a pull request or an issue or somethin.
9
- Also note that the gem has not been tested outside of postgres.
8
+ Note that you will have issues using the gem outside of postgres. These issue can be fixed, and I am considering a major version update to fix them. Let me know and I will probably do it.
10
9
 
11
10
  ## Basic Examples
12
11
  ```ruby
@@ -55,7 +54,7 @@ class ApplicationRecord < ActiveRecord::Base
55
54
  end
56
55
  ```
57
56
 
58
- ### SQL methods
57
+ ## SQL methods
59
58
 
60
59
  Methods written for easier sql usage.
61
60
 
@@ -368,6 +367,10 @@ A method used to prepare data for the instaload_sql method. It returns a hash of
368
367
  - table_name: sets the name of the temporary postgresql table. This can then be used in further instaload sql snippets.
369
368
  - relied_on: will make it so other instaload sql snippets can reference this table (it makes it use posrgresql's WITH operator)
370
369
  - dont_return: when used with relied_on makes it so that this data is not returned to rails from the database.
370
+ - base_on: for relations, a proc describing how to attach to the base table
371
+ - base_name: for relations, the table name for the table to attach to
372
+ - attach_on: for relations, a proc describing how this table attaches to the the base table
373
+ - one_to_one: for relations, a boolean that flips one-to-many/one-to-one (default false)
371
374
 
372
375
  note that the order of the instaload methods matter depending on how they reference eachother.
373
376
  <details>
@@ -393,7 +396,7 @@ taking the output of the instaload_sql method, this method creates relations bet
393
396
  - base_name: the name of the table we will be attaching to
394
397
  - attach_name: the name of the table that will be attached
395
398
  - base_on: put a proc here to override the matching key for the base table. Default is, for a user and post type, {|user| user.id}
396
- - attach_on: put a proc here to override the matching key for the attach table. Default is, for a user and post type, {|post| post.user_id}
399
+ - attach_on: put a proc here to override the matching key for the attach table. Default is, for a user and post type, {|post| post.user_id}. Returning an array from this proc will make this object attach to base records that have one of those keys. This can be used with GROUP BY and ARRAY_AGG functions to stop duplicate rows while still attaching where needed. Think of attaching a user to a post where multiple posts can have the same user.
397
400
  - one_to_one: switches between a one-to-one relationship or not
398
401
 
399
402
  <details>
@@ -421,7 +424,7 @@ printed output:
421
424
 
422
425
  </details>
423
426
 
424
- ### Hashed Global IDS
427
+ ## Hashed Global IDS
425
428
 
426
429
  hashed global ids look like this: "gid://meritfront/User/K9YI4K". They also have an optional tag so it can also look like "gid://meritfront/User/K9YI4K@user_image". They are based on global ids.
427
430
 
@@ -448,7 +451,9 @@ See the hashid-rails gem for more (https://github.com/jcypret/hashid-rails). Als
448
451
 
449
452
  ## Potential Issues
450
453
 
451
- - This gem was made with a postgresql database. This could cause a lot of issues with the sql-related methods if you do not. I dont have the bandwidth to help switch it elsewhere, but if you want to take charge of that, I would be more than happy to assist by answering questions an pointing out any areas that need transitioning.
454
+ - This gem was made with a postgresql database. There will be some issues when using other databases. Let me know and I will help you out how I can. The main issue as far as i can tell in the transition will be with
455
+ 1. dynamic_sql arrays. I assumed going into this that prepared statements were generated when the sql or name of the sql changed *at all*. This is not true, prepared statements are generated based on the underlying logical flow of the request. Because of this I was treating List types as postgresql array data types. Instead I should of converted them to ($1,$2,$3,$4,...etc) strings. This change would make them act similarly to how Lists of Lists are treated currently. This change would make necessary a major version change. There would also need to be an option to roll back to postgresql arrays on a case by case basis for backwards compatibility.
456
+ 2. instaload_sql. The instaload_sql request has hardcoded sql in it that has not been tested on other databases. This may be especially problematic due to the use of the json data-type.
452
457
  - If you return a password column (for example) as pwd, this gem will accept that. That would mean that the password could me accessed as model.pwd. This is cool - until all passwords are getting logged in production servers. So be wary of accessing, storing, and logging of sensative information. Active Record has in built solutions for this type of data, as long as you dont change the column name. This gem is a sharp knife, its very versitile, but its also, you know, sharp.
453
458
 
454
459
  ## Changelog
@@ -496,7 +501,7 @@ since things may be broken already, it seemed like a good time to do this.
496
501
  - this also tells me that uniq'ing variables to decrease the number of them was a bad idea which could cause random failures.
497
502
  - functionality improvements
498
503
  - The biggest change is that names are now optional! name_modifiers is now depreciated functionality as it serves no useful purpose. Will leave in for compatibility but take out of documentation. Used to think the name was related to prepared statements. This will lead simpler ruby code.
499
- - If name is left out, the name will be set to the location in your app which called the method. For example, when dynamic_sql was called from irb, the name was: "(irb):45:in `irb_binding'". This is done using stack trace functionality. In another case the name was "app/models/report.rb:364:in `refresh_db_methods'"
504
+ - If name is left out, the name will be set to the location in your app which called the method. For example, when dynamic_sql was called from irb, the name was: "(irb):45:in 'irb_binding'". This is done using stack trace functionality. In another case the name was "app/models/report.rb:364:in 'refresh_db_methods'"
500
505
  - dynamic_instaload_sql is now just instaload_sql. dynamic_instaload_sql has been aliased.
501
506
  - Name is optional on instaload_sql aswell
502
507
  - MultiAttributeArrays (array's of arrays) which can be passed into dynamic_sql largely for inserts/upserts will now treat symbols as an attribute name. This leads to more consise sql without running into above error.
@@ -517,7 +522,22 @@ v3.0.6
517
522
  - I am thinking of changing how arrays are handled as that is really the only postgresql based dependency here and that will allow the library to open up to other databases. Issue is all the code I have already written in my app dependant on such things.
518
523
 
519
524
  3.0.24
520
- - changed how questionable_attribute_set works again, this time by using attr_accessors on the singleton class. Seems to paper over the default reflections nicely which has been a huge issue. They use this weird delegate thing which has been throwing me off. Anyway, no more evals which is nice. This fixed an issue with dynamic attach one-to-many relations.
525
+ - changed how questionable_attribute_set works again, this time by using attr_accessors on the singleton class. Seems to paper over the default reflections nicely which has been a huge issue. They use this weird delegate thing which has been throwing me off. Anyway, no more evals which is nice. This fixed an issue with dynamic attach one-to-many relation
526
+
527
+ 3.1.0
528
+ - will try keeping breaking changes out of minor version here-on-out.
529
+ - better debugging messages and warnings for instaload
530
+ - returning an array in the attach_on proc will now treat each item in the array as its own attach_on key instead of all as one key. So for example you can attach one user to multiple posts, and only return one user object from the database.
531
+ - see the dynamic_attach docs above for more on that attach_on thing.
532
+ ....
533
+ - took out undocumented quick_safe_increment and safe_increment as they were not relevant to the gem - and fairly basic.
534
+
535
+ 3.1.28
536
+ - warning messages for dynamic_attach. to_s'd any provided table_names for ease-of-use.
537
+ - logging info for dynamic_attach outside of instaload.
538
+ - fixed an error where instaload only worked with ApplicationRecord abstract_class. Now works on anything thats a subclass of ActiveRecord::Base
539
+ - wow
540
+
521
541
 
522
542
  ## Questions
523
543
  - Q: does the name of a sql operation have anything to do with prepared statements?
@@ -525,7 +545,7 @@ v3.0.6
525
545
  - Q: The default name of my sql statements looks like a stack trace? Whats going on?
526
546
  - A: We set the location of where you called the function as the default name for easy debugging. Its not an error, we just take some info from the stacktrace. It also includes the method name which can provide some insite into what the query is doing. Makes logs alot nicer to look at.
527
547
  - Q: Whats MeritFront?
528
- - A: I am making a social media platform
548
+ - A: I am making a social media platform. See meritfront.com, Its soft launched.
529
549
  - Q: Inspect on user records doesn't seem to work properly
530
550
  - A: inspect is overwritten by many diffrent libraries, in terms of devise for example, they override our override of active record's inspect. The best way to deal with this is to look at the source location of these methods and bring them together (user.method(:inspect).source_location). In my case with devise, i ended up with this in the user record:
531
551
  ```ruby
data/bin/update CHANGED
@@ -51,5 +51,8 @@ puts `git commit -m 'minor changes to version #{version_new}'`
51
51
  puts "_______________________________________________"
52
52
  puts "INSTALL GEM LOCALLY"
53
53
  puts `bundle exec rake install`
54
+ puts "note if you cant install in your project u prob have different ruby versions for the project and this folder."
55
+ puts "if in that case and using rbenv, change your global ruby version under ~/.rbenv/version or your local by adding"
56
+ puts "somthing similar in a .ruby_version file. Not using .rbenv? Add ur instructions here"
54
57
  puts "_______________________________________________"
55
58
  puts "info: write 'bundle exec rake release' to release the current version to the interwebz"
@@ -1,5 +1,5 @@
1
1
 
2
2
  module DynamicRecordsMeritfront
3
- VERSION = '3.1.0'
3
+ VERSION = '3.1.29'
4
4
  end
5
5
  #this file gets overwritten automatically on minor updates, major ones need to be manually changed
@@ -78,6 +78,7 @@ module DynamicRecordsMeritfront
78
78
  DB_TYPE_MAPS = {
79
79
  String => ActiveModel::Type::String,
80
80
  Symbol => ActiveModel::Type::String,
81
+ ActiveSupport::SafeBuffer => ActiveModel::Type::String,
81
82
  Integer => ActiveModel::Type::BigInteger,
82
83
  BigDecimal => ActiveRecord::Type::Decimal,
83
84
  TrueClass => ActiveModel::Type::Boolean,
@@ -104,7 +105,7 @@ module DynamicRecordsMeritfront
104
105
 
105
106
  type = DB_TYPE_MAPS[v.class]
106
107
  if type.nil?
107
- raise StandardError.new("#{name} (of value: #{v}, class: #{v.class}) unsupported class for ApplicationRecord#headache_sql")
108
+ raise StandardError.new("#{name} (of value: #{v}, class: #{v.class}) unsupported class for DynamicRecordsMeritfront#headache_sql")
108
109
  elsif type.class == Proc
109
110
  a = v[0]
110
111
  # if a.nil?
@@ -399,9 +400,14 @@ module DynamicRecordsMeritfront
399
400
  if returns_nil
400
401
  return nil
401
402
  else
402
- raise StandardError.new("non-string class passed to ApplicationRecord#locate_hgid as the hgid_string variable")
403
+ raise StandardError.new("non-string class passed to DynamicRecordsMeritfront#locate_hgid as the hgid_string variable")
403
404
  end
404
405
  end
406
+ if PROJECT_NAME == 'midflip'
407
+ #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.
408
+ #dont want to kill me jobs.
409
+ hgid_string = hgid_string.gsub('ApplicationRecord', 'Record')
410
+ end
405
411
  if hgid_string.include?('@')
406
412
  hgid_string = hgid_string.split('@')
407
413
  hgid_string.pop
@@ -437,7 +443,7 @@ module DynamicRecordsMeritfront
437
443
  nil
438
444
  else
439
445
  #stops execution as default
440
- raise StandardError.new 'Not the expected class, or a subclass of ApplicationRecord if called on that.'
446
+ raise StandardError.new 'Not the expected class or subclass.'
441
447
  end
442
448
  end
443
449
  def get_hgid_tag(hgid_string)
@@ -653,37 +659,31 @@ module DynamicRecordsMeritfront
653
659
  insta_array = insta_array.select{|ar| not ar[:dont_return]}
654
660
  ret_hash = insta_array.map{|ar| [ar[:table_name].to_s, []]}.to_h
655
661
  opts[:raw] = true
656
- ApplicationRecord.headache_sql(name, sql, opts).rows.each{|row|
662
+
663
+ #annoying bug
664
+ s = self
665
+ unless s.abstract_class?
666
+ s = s.superclass
667
+ end
668
+
669
+ s.dynamic_sql(name, sql, opts).rows.each{|row|
657
670
  #need to pre-parsed as it has a non-normal output.
658
671
  table_name = row[2]
659
672
  klass = row[1].constantize
660
673
  json = row[0]
661
674
  parsed = JSON.parse(json)
662
-
663
675
  ret_hash[table_name].push dynamic_init(klass, parsed)
664
676
  }
665
677
 
666
- insta_array = insta_array.map{|a|a.delete(:sql); next a} #better for debuggin and we dont need it anymore
678
+ insta_array.each{|a| a.delete(:sql)}
667
679
 
668
680
  #formatting options
669
681
  for insta in insta_array
670
682
  if insta[:base_name]
671
683
  #in this case, 'as' is meant as to what pseudonym to dynamicly attach it as
672
- n_attached = dynamic_attach(ret_hash, insta[:base_name], insta[:table_name], base_on: insta[:base_on], attach_on: insta[:attach_on],
684
+ #we are attaching to the base table. Variable could of been less confusing. My bad.
685
+ dynamic_attach(ret_hash, insta[:base_name], insta[:table_name], base_on: insta[:base_on], attach_on: insta[:attach_on],
673
686
  one_to_one: insta[:one_to_one], as: insta[:as])
674
-
675
- if Rails.logger.level <= 1
676
- tn = insta[:table_name]
677
- bn = insta[:base_name]
678
- tc = ret_hash[tn].count
679
- btc = ret_hash[bn].count
680
-
681
- if insta[:as]
682
- Rails.logger.debug "#{n_attached} attached from #{tn}(#{tc}) as #{insta[:as]} -> #{bn}(#{btc})"
683
- else
684
- Rails.logger.debug "#{n_attached} attached from #{tn}(#{tc}) -> #{bn}(#{btc})"
685
- end
686
- end
687
687
  elsif insta[:as]
688
688
  Rails.logger.debug "#{insta[:table_name]} as #{insta[:as]}"
689
689
  #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
@@ -707,7 +707,7 @@ module DynamicRecordsMeritfront
707
707
  m = model_with_an_id_column_and_timestamps
708
708
  ar = m.superclass
709
709
  mtname = m.table_name
710
- ApplicationRecord.transaction do
710
+ transaction do
711
711
  puts 'test recieving columns not normally in the record.'
712
712
  rec = m.dynamic_sql(%Q{
713
713
  SELECT id, 5 AS random_column from #{mtname} LIMIT 10
@@ -799,12 +799,23 @@ module DynamicRecordsMeritfront
799
799
  def dynamic_attach(instaload_sql_output, base_name, attach_name, base_on: nil, attach_on: nil, one_to_one: false, as: nil)
800
800
  #as just lets it attach us anywhere on the base class, and not just as the attach_name.
801
801
  #Can be useful in polymorphic situations, otherwise may lead to confusion.
802
+
803
+ #oh the errors
804
+ base_name = base_name.to_s
805
+ attach_name = attach_name.to_s
806
+
802
807
  as ||= attach_name
803
808
 
804
809
  base_arr = instaload_sql_output[base_name]
805
810
 
806
811
  #return if there is nothing for us to attach to.
807
- return 0 if base_arr.nil? or not base_arr.any?
812
+ if base_arr.nil?
813
+ Rails.logger.error("base table " + base_name + " was not set")
814
+ return 0
815
+ elsif not base_arr.any?
816
+ Rails.logger.debug("no items in base table " + base_name)
817
+ return 0
818
+ end
808
819
 
809
820
  #set variables for neatness and so we dont compute each time
810
821
  # base class information
@@ -829,7 +840,14 @@ module DynamicRecordsMeritfront
829
840
 
830
841
  #make sure the attach class has something going on. We do this after the default stage
831
842
  attach_arr = instaload_sql_output[attach_name]
832
- return 0 if attach_arr.nil? or not attach_arr.any?
843
+
844
+ if attach_arr.nil?
845
+ Rails.logger.error("attaching table " + attach_name + " was not set")
846
+ return 0
847
+ elsif not attach_arr.any?
848
+ Rails.logger.debug("no items in attach table " + attach_name)
849
+ return 0
850
+ end
833
851
 
834
852
  # attach class information
835
853
  attach_class = attach_arr.first.class
@@ -922,7 +940,7 @@ module DynamicRecordsMeritfront
922
940
  if base_rec
923
941
  dupl = duplicates_base.include? ak
924
942
  if dupl
925
- 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 for your id and ARRAY_AGG for the base_on column."
943
+ 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."
926
944
  Rails.logger.warn "base_on key: #{ak.to_s}"
927
945
  end
928
946
 
@@ -933,6 +951,14 @@ module DynamicRecordsMeritfront
933
951
  end
934
952
  }
935
953
 
954
+ if Rails.logger.level <= 1
955
+ if as
956
+ Rails.logger.debug "#{x}/#{attach_arr.count} attached from #{attach_name} as #{as} -> #{base_name}(#{x}/#{base_arr.count})"
957
+ else
958
+ Rails.logger.debug "#{x}/#{attach_arr.count} attached from #{attach_name} -> #{base_name}(#{x}/#{base_arr.count})"
959
+ end
960
+ end
961
+
936
962
  return x
937
963
  end
938
964
  alias swiss_attach dynamic_attach
@@ -955,10 +981,11 @@ module DynamicRecordsMeritfront
955
981
  return record
956
982
  end
957
983
  end
958
-
959
- def quick_safe_increment(id, col, val)
960
- where(id: id).update_all("#{col} = #{col} + #{val}")
961
- end
984
+
985
+ #undocumented and simple
986
+ #def quick_safe_increment(id, col, val)
987
+ # where(id: id).update_all("#{col} = #{col} + #{val}")
988
+ #end
962
989
 
963
990
 
964
991
  end
@@ -995,8 +1022,10 @@ module DynamicRecordsMeritfront
995
1022
  self.class.headache_preload(records, associations)
996
1023
  end
997
1024
 
998
- def safe_increment(col, val) #also used in follow, also used in comment#kill
999
- self.class.where(id: self.id).update_all("#{col} = #{col} + #{val}")
1000
- end
1025
+
1026
+ #undocumented and simple
1027
+ # def safe_increment(col, val) #also used in follow, also used in comment#kill
1028
+ # self.class.where(id: self.id).update_all("#{col} = #{col} + #{val}")
1029
+ # end
1001
1030
 
1002
1031
  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: 3.1.0
4
+ version: 3.1.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luke Clancy
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-13 00:00:00.000000000 Z
11
+ date: 2023-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashid-rails
@@ -54,7 +54,7 @@ metadata:
54
54
  homepage_uri: https://github.com/LukeClancy/dynamic-records-meritfront
55
55
  source_code_uri: https://github.com/LukeClancy/dynamic-records-meritfront
56
56
  changelog_uri: https://github.com/LukeClancy/dynamic-records-meritfront/blob/main/README.md
57
- post_install_message:
57
+ post_install_message:
58
58
  rdoc_options: []
59
59
  require_paths:
60
60
  - lib
@@ -69,8 +69,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
69
  - !ruby/object:Gem::Version
70
70
  version: '0'
71
71
  requirements: []
72
- rubygems_version: 3.3.7
73
- signing_key:
72
+ rubygems_version: 3.4.10
73
+ signing_key:
74
74
  specification_version: 4
75
75
  summary: Helpers for active record that allow for more abstract and fine-grained code.
76
76
  test_files: []