dynamic-records-meritfront 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db86b391a9dbb87a5499b32931520d39d5dbb1b12eb4680a4721cdb2c897d57b
4
- data.tar.gz: a2eed17ce50be8ddc027a64b389a6ba8f69d06b4adda75aab672919c559f6d44
3
+ metadata.gz: f1933cb7f3bc36b042f8969cc557509cfef64151ac5882f00243d1baff631ee1
4
+ data.tar.gz: c4558116af992883844a10035d66c38c4d7a41a5d607021018d4272290862053
5
5
  SHA512:
6
- metadata.gz: a0fcec82242810e9e9eec7ae6e46602a6dabfefaf4ded57660b9a0b83d44b4c35d52d8e8a5b50acb64028bb473ee626f3690632cdbe0c146761cffbafa38504e
7
- data.tar.gz: 98854c02fd8db676c39d358e33876a389e5da87c68ece01df3d005d9f4189d244271460ccfbac5316c9658a22019b04cfda2974f6beb25f5b209590a40fb30b0
6
+ metadata.gz: d58ddf4617e1c5f275e0c5b2321d3df1b1ad29aef7385738832f58a54b0e7ce113b165301a2273ee7c1729a446ed154751eaba0d14afb90ff44f4d7bdf8ea8f6
7
+ data.tar.gz: 4fab3726bf1cbfedabcd83348c367ae833138cde6fda8f156c592835c05a9a3cb3387c97319df160c1414a5d0019bb226d6cdd11bf6780d93ceeb13c288a5bd3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dynamic-records-meritfront (0.1.4)
4
+ dynamic-records-meritfront (0.1.7)
5
5
  hashid-rails
6
6
 
7
7
  GEM
@@ -1,5 +1,5 @@
1
1
 
2
2
  module DynamicRecordsMeritfront
3
- VERSION = '0.1.4'
3
+ VERSION = '0.1.7'
4
4
  end
5
5
  #this file gets overwritten automatically on minor updates, major ones need to be manually changed
@@ -1,10 +1,8 @@
1
1
  require "dynamic-records-meritfront/version"
2
+ require 'hashid/rails'
2
3
 
3
4
  module DynamicRecordsMeritfront
4
5
  extend ActiveSupport::Concern
5
-
6
- # include hash id gem
7
- include Hashid::Rails
8
6
 
9
7
  # the two aliases so I dont go insane
10
8
  module Hashid::Rails
@@ -14,102 +12,298 @@ module DynamicRecordsMeritfront
14
12
  alias hfind find_by_hashid
15
13
  end
16
14
 
17
- def self.has_run_migration?(nm)
18
- #put in a string name of the class and it will say if it has allready run the migration.
19
- #good during enum migrations as the code to migrate wont run if enumerate is there
20
- #as it is not yet enumerated (causing an error when it loads the class that will have the
21
- #enumeration in it). This can lead it to being impossible to commit clean code.
22
- #
23
- # example usage one: only create the record class if it currently exists in the database
24
- # if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
25
- # class UserImageRelation < ApplicationRecord
26
- # belongs_to :imageable, polymorphic: true
27
- # belongs_to :image
28
- # end
29
- # else
30
- # class UserImageRelation; end
31
- # end
32
- # example usage two: only load relation if it exists in the database
33
- # class UserImageRelation < ApplicationRecord
34
- # if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
35
- # belongs_to :imageable, polymorphic: true
36
- # end
37
- # end
38
- #
39
- #current version of migrations
40
- cv = ActiveRecord::Base.connection.migration_context.current_version
41
-
42
- #find the migration object for the name
43
- migration = ActiveRecord::Base.connection.migration_context.migrations.filter!{|a|
44
- a.name == nm
45
- }.first
46
-
47
- #if the migration object is nil, it has not yet been created
48
- if migration.nil?
49
- Rails.logger.info "No migration found for #{nm}. The migration has not yet been created, or is foreign to this database."
50
- return false
51
- end
52
-
53
- #get the version number for the migration name
54
- needed_version = migration.version
55
-
56
- #if current version is above or equal, the migration has allready been run
57
- migration_ran = (cv >= needed_version)
58
-
59
- if migration_ran
60
- Rails.logger.info "#{nm} migration was run on #{needed_version}. If old and all instances are migrated, consider removing code check."
61
- else
62
- Rails.logger.info "#{nm} migration has not run yet. This may lead to limited functionality"
63
- end
64
-
65
- return migration_ran
66
- end
67
-
68
- def self.list_associations
69
- #lists associations (see has_association? below)
70
- reflect_on_all_associations.map(&:name)
71
- end
15
+ included do
16
+ # include hash id gem
17
+ include Hashid::Rails
18
+ #should work, probably able to override by redefining in ApplicationRecord class.
19
+ #Note we defined here as it breaks early on as Rails.application returns nil
20
+ PROJECT_NAME = Rails.application.class.to_s.split("::").first.to_s.downcase
21
+ end
22
+
23
+ module ClassMethods
24
+ def has_run_migration?(nm)
25
+ #put in a string name of the class and it will say if it has allready run the migration.
26
+ #good during enum migrations as the code to migrate wont run if enumerate is there
27
+ #as it is not yet enumerated (causing an error when it loads the class that will have the
28
+ #enumeration in it). This can lead it to being impossible to commit clean code.
29
+ #
30
+ # example usage one: only create the record class if it currently exists in the database
31
+ # if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
32
+ # class UserImageRelation < ApplicationRecord
33
+ # belongs_to :imageable, polymorphic: true
34
+ # belongs_to :image
35
+ # end
36
+ # else
37
+ # class UserImageRelation; end
38
+ # end
39
+ # example usage two: only load relation if it exists in the database
40
+ # class UserImageRelation < ApplicationRecord
41
+ # if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
42
+ # belongs_to :imageable, polymorphic: true
43
+ # end
44
+ # end
45
+ #
46
+ #current version of migrations
47
+ cv = ActiveRecord::Base.connection.migration_context.current_version
48
+
49
+ #find the migration object for the name
50
+ migration = ActiveRecord::Base.connection.migration_context.migrations.filter!{|a|
51
+ a.name == nm
52
+ }.first
53
+
54
+ #if the migration object is nil, it has not yet been created
55
+ if migration.nil?
56
+ Rails.logger.info "No migration found for #{nm}. The migration has not yet been created, or is foreign to this database."
57
+ return false
58
+ end
59
+
60
+ #get the version number for the migration name
61
+ needed_version = migration.version
62
+
63
+ #if current version is above or equal, the migration has allready been run
64
+ migration_ran = (cv >= needed_version)
65
+
66
+ if migration_ran
67
+ Rails.logger.info "#{nm} migration was run on #{needed_version}. If old and all instances are migrated, consider removing code check."
68
+ else
69
+ Rails.logger.info "#{nm} migration has not run yet. This may lead to limited functionality"
70
+ end
71
+
72
+ return migration_ran
73
+ end
74
+
75
+ def list_associations
76
+ #lists associations (see has_association? below)
77
+ reflect_on_all_associations.map(&:name)
78
+ end
79
+
80
+ def has_association?(*args)
81
+ #checks whether current class has needed association (for example, checks it has comments)
82
+ #associations can be seen in has_many belongs_to and other similar methods
83
+
84
+ #flattens so you can pass self.has_association?(:comments, :baseable_comments) aswell as
85
+ # self.has_association?([:comments, :baseable_comments]) without issue
86
+ #
87
+ args = args.flatten.map { |a| a.to_sym }
88
+ associations = list_associations
89
+ (args.length == (associations & args).length)
90
+ end
91
+
92
+ def blind_hgid(id, tag: nil)
93
+ # this method is to get an hgid for a class without actually calling it down from the database.
94
+ # For example Notification.blind_hgid 1 will give gid://PROJECT_NAME/Notification/69DAB69 etc.
95
+ unless id.class == String
96
+ id = self.encode_id id
97
+ end
98
+ gid = "gid://#{PROJECT_NAME}/#{self.to_s}/#{id}"
99
+ if !tag
100
+ gid
101
+ else
102
+ "#{gid}@#{tag}"
103
+ end
104
+ end
105
+
106
+ def string_as_selector(str, attribute: 'id')
107
+ #this is needed to allow us to quey various strange characters in the id etc. (see hgids)
108
+ #also useful for querying various attributes
109
+ return "*[#{attribute}=\"#{str}\"]"
110
+ end
111
+
112
+ def locate_hgid(hgid_string, with_associations: nil, returns_nil: false)
113
+ if hgid_string == nil or hgid_string.class != String
114
+ if returns_nil
115
+ return nil
116
+ else
117
+ raise StandardError.new("non-string class passed to ApplicationRecord#locate_hgid as the hgid_string variable")
118
+ end
119
+ end
120
+ if hgid_string.include?('@')
121
+ hgid_string = hgid_string.split('@')
122
+ hgid_string.pop
123
+ hgid_string = hgid_string.join('@') # incase the model was a tag that was tagged. (few months later: Wtf? Guess ill keep it)
124
+ end
125
+ #split the thing
126
+ splitz = hgid_string.split('/')
127
+ #get the class
128
+ begin
129
+ cls = splitz[-2].constantize
130
+ rescue NameError, NoMethodError
131
+ if returns_nil
132
+ nil
133
+ else
134
+ raise StandardError.new 'Unusual or unavailable string or hgid'
135
+ end
136
+ end
137
+ #get the hash
138
+ hash = splitz[-1]
139
+ # if self == ApplicationRecord (for instance), then check that cls is a subclass
140
+ # if self is not ApplicationRecord, then check cls == this objects class
141
+ # if with_associations defined, make sure that the class has the associations given (see has_association above)
142
+ if ((self.abstract_class? and cls < self) or ( (not self.abstract_class?) and cls == self )) and
143
+ ( with_associations == nil or cls.has_association?(with_associations) )
144
+ #if all is as expected, return the object with its id.
145
+ if block_given?
146
+ yield(hash)
147
+ else
148
+ cls.hfind(hash)
149
+ end
150
+ elsif returns_nil
151
+ #allows us to handle issues with input
152
+ nil
153
+ else
154
+ #stops execution as default
155
+ raise StandardError.new 'Not the expected class, or a subclass of ApplicationRecord if called on that.'
156
+ end
157
+ end
158
+
159
+ def get_hgid_tag(hgid_string)
160
+ if hgid_string.include?('@')
161
+ return hgid_string.split('@')[-1]
162
+ else
163
+ return nil
164
+ end
165
+ end
166
+
167
+ #thank god for some stack overflow people are pretty awesome https://stackoverflow.com/questions/64894375/executing-a-raw-sql-query-in-rails-with-an-array-parameter-against-postgresql
168
+ #BigIntArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::BigInteger.new).freeze
169
+ #IntegerArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::Integer.new).freeze
170
+
171
+ #https://api.rubyonrails.org/files/activemodel/lib/active_model/type_rb.html
172
+ # active_model/type/helpers
173
+ # active_model/type/value
174
+ # active_model/type/big_integer
175
+ # active_model/type/binary
176
+ # active_model/type/boolean
177
+ # active_model/type/date
178
+ # active_model/type/date_time
179
+ # active_model/type/decimal
180
+ # active_model/type/float
181
+ # active_model/type/immutable_string
182
+ # active_model/type/integer
183
+ # active_model/type/string
184
+ # active_model/type/time
185
+ # active_model
186
+
187
+ DB_TYPE_MAPS = {
188
+ String => ActiveModel::Type::String,
189
+ Symbol => ActiveModel::Type::String,
190
+ Integer => ActiveModel::Type::BigInteger,
191
+ BigDecimal => ActiveRecord::Type::Decimal,
192
+ TrueClass => ActiveModel::Type::Boolean,
193
+ FalseClass => ActiveModel::Type::Boolean,
194
+ Date => ActiveModel::Type::Date,
195
+ DateTime => ActiveModel::Type::DateTime,
196
+ Time => ActiveModel::Type::Time,
197
+ Float => ActiveModel::Type::Float,
198
+ Array => Proc.new{ |first_el_class| ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(DB_TYPE_MAPS[first_el_class].new) }
199
+ }
200
+
201
+
202
+ #allows us to preload on a list and not a active record relation. So basically from the output of headache_sql
203
+ def headache_preload(records, associations)
204
+ ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
205
+ end
206
+
207
+ def headache_sql(name, sql, opts = { }) #see below for opts
208
+ # - instantiate_class - returns User, Post, etc objects instead of straight sql output.
209
+ # I prefer doing the alterantive
210
+ # User.headache_class(...)
211
+ # which is also supported
212
+ # - prepare sets whether the db will preprocess the strategy for lookup (defaults true) (I dont think turning this off works...)
213
+ # - name_modifiers allows one to change the preprocess associated name, useful in cases of dynamic sql.
214
+ # - multi_query allows more than one query (you can seperate an insert and an update with ';' I dont know how else to say it.)
215
+ # this disables other options (except name_modifiers). Not sure how it effects prepared statements.
216
+ # - async does what it says but I haven't used it yet so. Probabably doesn't work
217
+ #
218
+ # Any other option is assumed to be a sql argument (see other examples in code base)
219
+
220
+ #grab options from the opts hash
221
+ instantiate_class = opts.delete(:instantiate_class)
222
+ name_modifiers = opts.delete(:name_modifiers)
223
+ name_modifiers ||= []
224
+ prepare = opts.delete(:prepare) != false
225
+ multi_query = opts.delete(:multi_query) == true
226
+ async = opts.delete(:async) == true
227
+ params = opts
228
+
229
+ #allows dynamic sql prepared statements.
230
+ for mod in name_modifiers
231
+ name << "_#{mod.to_s}" unless mod.nil?
232
+ end
233
+
234
+ unless multi_query
235
+ #https://stackoverflow.com/questions/49947990/can-i-execute-a-raw-sql-query-leverage-prepared-statements-and-not-use-activer/67442353#67442353
236
+
237
+ #change the keys to $1, $2 etc. this step is needed for ex. {id: 1, id_user: 2}.
238
+ #doing the longer ones first prevents id replacing :id_user -> 1_user
239
+ keys = params.keys.sort{|a,b| b.to_s.length <=> a.to_s.length}
240
+ vals = []
241
+ x = 1
242
+ for key in keys
243
+ #replace the key with $1, $2 etc
244
+ if sql.gsub!(":#{key}", "$#{x}") == nil
245
+ #nothing changed, param not used, delete it
246
+ x -= 1
247
+ params.delete key
248
+ else
249
+ #yes its dumb I know dont look at me look at rails
250
+
251
+ # https://stackoverflow.com/questions/40407700/rails-exec-query-bindings-ignored
252
+ # binds = [ ActiveRecord::Relation::QueryAttribute.new(
253
+ # "id", 6, ActiveRecord::Type::Integer.new
254
+ # )]
255
+ # ApplicationRecord.connection.exec_query(
256
+ # 'SELECT * FROM users WHERE id = $1', 'sql', binds
257
+ # )
258
+ v = params[key]
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
+ a.nil? ? a = Integer : a = a.class
265
+ type = type.call(a)
266
+ else
267
+ type = type.new
268
+ end
269
+
270
+ vals << ActiveRecord::Relation::QueryAttribute.new( key, v, type )
271
+ end
272
+ x += 1
273
+ end
274
+ ret = ActiveRecord::Base.connection.exec_query sql, name, vals, prepare: prepare, async: async
275
+ else
276
+ ret = ActiveRecord::Base.connection.execute sql, name
277
+ end
278
+
279
+ #this returns a PG::Result object, which is pretty basic. To make this into User/Post/etc objects we do
280
+ #the following
281
+ if instantiate_class or self != ApplicationRecord
282
+ instantiate_class = self if not instantiate_class
283
+ #no I am not actually this cool see https://stackoverflow.com/questions/30826015/convert-pgresult-to-an-active-record-model
284
+ fields = ret.columns
285
+ vals = ret.rows
286
+ ret = vals.map { |v|
287
+ instantiate_class.instantiate(Hash[fields.zip(v)])
288
+ }
289
+ end
290
+ ret
291
+ end
292
+ def quick_safe_increment(id, col, val)
293
+ where(id: id).update_all("#{col} = #{col} + #{val}")
294
+ end
295
+ end
72
296
 
73
297
  def list_associations
74
298
  #lists associations (see class method above)
75
299
  self.class.list_associations
76
300
  end
77
301
 
78
- def self.has_association?(*args)
79
- #checks whether current class has needed association (for example, checks it has comments)
80
- #associations can be seen in has_many belongs_to and other similar methods
81
-
82
- #flattens so you can pass self.has_association?(:comments, :baseable_comments) aswell as
83
- # self.has_association?([:comments, :baseable_comments]) without issue
84
- #
85
- args = args.flatten.map { |a| a.to_sym }
86
- associations = list_associations
87
- (args.length == (associations & args).length)
88
- end
89
-
90
302
  def has_association?(*args)
91
303
  #just redirects to the class method for ease of use (see class method above)
92
304
  self.class.has_association?(*args)
93
305
  end
94
306
 
95
- #should work, probably able to override by redefining in ApplicationRecord class
96
-
97
- PROJECT_NAME = Rails.application.class.to_s.split("::").first.to_s.downcase
98
-
99
- # this method is to get an hgid for a class without actually calling it down from the database.
100
- # For example Notification.blind_hgid 1 will give gid://PROJECT_NAME/Notification/69DAB69 etc.
101
- def self.blind_hgid(id, tag: nil)
102
- unless id.class == String
103
- id = self.encode_id id
104
- end
105
- gid = "gid://#{PROJECT_NAME}/#{self.to_s}/#{id}"
106
- if !tag
107
- gid
108
- else
109
- "#{gid}@#{tag}"
110
- end
111
- end
112
-
113
307
  # custom hash GlobalID
114
308
  def hgid(tag: nil)
115
309
  gid = "gid://#{PROJECT_NAME}/#{self.class.to_s}/#{self.hid}"
@@ -127,202 +321,13 @@ module DynamicRecordsMeritfront
127
321
  return self.class.string_as_selector(gidstr, attribute: attribute)
128
322
  end
129
323
 
130
- def self.string_as_selector(str, attribute: 'id')
131
- #this is needed to allow us to quey various strange characters in the id etc. (see hgids)
132
- #also useful for querying various attributes
133
- return "*[#{attribute}=\"#{str}\"]"
134
- end
324
+ #just for ease of use
325
+ def headache_preload(records, associations)
326
+ self.class.headache_preload(records, associations)
327
+ end
135
328
 
136
- def self.locate_hgid(hgid_string, with_associations: nil, returns_nil: false)
137
- if hgid_string == nil or hgid_string.class != String
138
- if returns_nil
139
- return nil
140
- else
141
- raise StandardError.new("non-string class passed to ApplicationRecord#locate_hgid as the hgid_string variable")
142
- end
143
- end
144
- if hgid_string.include?('@')
145
- hgid_string = hgid_string.split('@')
146
- hgid_string.pop
147
- hgid_string = hgid_string.join('@') # incase the model was a tag that was tagged. (few months later: Wtf? Guess ill keep it)
148
- end
149
- #split the thing
150
- splitz = hgid_string.split('/')
151
- #get the class
152
- begin
153
- cls = splitz[-2].constantize
154
- rescue NameError, NoMethodError => e
155
- if returns_nil
156
- nil
157
- else
158
- raise StandardError.new 'Unusual or unavailable string or hgid'
159
- end
160
- end
161
- #get the hash
162
- hash = splitz[-1]
163
- # if self == ApplicationRecord (for instance), then check that cls is a subclass
164
- # if self is not ApplicationRecord, then check cls == this objects class
165
- # if with_associations defined, make sure that the class has the associations given (see has_association above)
166
- if ((self.abstract_class? and cls < self) or ( (not self.abstract_class?) and cls == self )) and
167
- ( with_associations == nil or cls.has_association?(with_associations) )
168
- #if all is as expected, return the object with its id.
169
- if block_given?
170
- yield(hash)
171
- else
172
- cls.hfind(hash)
173
- end
174
- elsif returns_nil
175
- #allows us to handle issues with input
176
- nil
177
- else
178
- #stops execution as default
179
- raise StandardError.new 'Not the expected class, or a subclass of ApplicationRecord if called on that.'
180
- end
181
- end
182
-
183
- def self.get_hgid_tag(hgid_string)
184
- if hgid_string.include?('@')
185
- return hgid_string.split('@')[-1]
186
- else
187
- return nil
188
- end
189
- end
190
-
191
- #thank god for some stack overflow people are pretty awesome https://stackoverflow.com/questions/64894375/executing-a-raw-sql-query-in-rails-with-an-array-parameter-against-postgresql
192
- #BigIntArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::BigInteger.new).freeze
193
- #IntegerArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::Integer.new).freeze
194
-
195
- #https://api.rubyonrails.org/files/activemodel/lib/active_model/type_rb.html
196
- # active_model/type/helpers
197
- # active_model/type/value
198
- # active_model/type/big_integer
199
- # active_model/type/binary
200
- # active_model/type/boolean
201
- # active_model/type/date
202
- # active_model/type/date_time
203
- # active_model/type/decimal
204
- # active_model/type/float
205
- # active_model/type/immutable_string
206
- # active_model/type/integer
207
- # active_model/type/string
208
- # active_model/type/time
209
- # active_model
210
-
211
- DB_TYPE_MAPS = {
212
- String => ActiveModel::Type::String,
213
- Symbol => ActiveModel::Type::String,
214
- Integer => ActiveModel::Type::BigInteger,
215
- BigDecimal => ActiveRecord::Type::Decimal,
216
- TrueClass => ActiveModel::Type::Boolean,
217
- FalseClass => ActiveModel::Type::Boolean,
218
- Date => ActiveModel::Type::Date,
219
- DateTime => ActiveModel::Type::DateTime,
220
- Time => ActiveModel::Type::Time,
221
- Float => ActiveModel::Type::Float,
222
- Array => Proc.new{ |first_el_class| ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(DB_TYPE_MAPS[first_el_class].new) }
223
- }
224
-
225
-
226
- #allows us to preload on a list and not a active record relation. So basically from the output of headache_sql
227
- def self.headache_preload(records, associations)
228
- ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
229
- end
230
-
231
- #just for ease of use
232
- def headache_preload(records, associations)
233
- self.class.headache_preload(records, associations)
234
- end
235
-
236
- def self.headache_sql(name, sql, opts = { }) #see below for opts
237
- # - instantiate_class - returns User, Post, etc objects instead of straight sql output.
238
- # I prefer doing the alterantive
239
- # User.headache_class(...)
240
- # which is also supported
241
- # - prepare sets whether the db will preprocess the strategy for lookup (defaults true) (I dont think turning this off works...)
242
- # - name_modifiers allows one to change the preprocess associated name, useful in cases of dynamic sql.
243
- # - multi_query allows more than one query (you can seperate an insert and an update with ';' I dont know how else to say it.)
244
- # this disables other options (except name_modifiers). Not sure how it effects prepared statements.
245
- # - async does what it says but I haven't used it yet so. Probabably doesn't work
246
- #
247
- # Any other option is assumed to be a sql argument (see other examples in code base)
248
-
249
- #grab options from the opts hash
250
- instantiate_class = opts.delete(:instantiate_class)
251
- name_modifiers = opts.delete(:name_modifiers)
252
- name_modifiers ||= []
253
- prepare = opts.delete(:prepare) != false
254
- multi_query = opts.delete(:multi_query) == true
255
- async = opts.delete(:async) == true
256
- params = opts
257
-
258
- #allows dynamic sql prepared statements.
259
- for mod in name_modifiers
260
- name << "_#{mod.to_s}" unless mod.nil?
261
- end
262
-
263
- unless multi_query
264
- #https://stackoverflow.com/questions/49947990/can-i-execute-a-raw-sql-query-leverage-prepared-statements-and-not-use-activer/67442353#67442353
265
-
266
- #change the keys to $1, $2 etc. this step is needed for ex. {id: 1, id_user: 2}.
267
- #doing the longer ones first prevents id replacing :id_user -> 1_user
268
- keys = params.keys.sort{|a,b| b.to_s.length <=> a.to_s.length}
269
- vals = []
270
- x = 1
271
- for key in keys
272
- #replace the key with $1, $2 etc
273
- if sql.gsub!(":#{key}", "$#{x}") == nil
274
- #nothing changed, param not used, delete it
275
- x -= 1
276
- params.delete key
277
- else
278
- #yes its dumb I know dont look at me look at rails
279
-
280
- # https://stackoverflow.com/questions/40407700/rails-exec-query-bindings-ignored
281
- # binds = [ ActiveRecord::Relation::QueryAttribute.new(
282
- # "id", 6, ActiveRecord::Type::Integer.new
283
- # )]
284
- # ApplicationRecord.connection.exec_query(
285
- # 'SELECT * FROM users WHERE id = $1', 'sql', binds
286
- # )
287
- v = params[key]
288
- type = DB_TYPE_MAPS[v.class]
289
- if type.nil?
290
- raise StandardError.new("#{v}'s class #{v.class} unsupported type right now for ApplicationRecord#headache_sql")
291
- elsif type.class == Proc
292
- a = v[0]
293
- a.nil? ? a = Integer : a = a.class
294
- type = type.call(a)
295
- else
296
- type = type.new
297
- end
298
-
299
- vals << ActiveRecord::Relation::QueryAttribute.new( key, v, type )
300
- end
301
- x += 1
302
- end
303
- ret = ActiveRecord::Base.connection.exec_query sql, name, vals, prepare: prepare, async: async
304
- else
305
- ret = ActiveRecord::Base.connection.execute sql, name
306
- end
307
-
308
- #this returns a PG::Result object, which is pretty basic. To make this into User/Post/etc objects we do
309
- #the following
310
- if instantiate_class or self != ApplicationRecord
311
- instantiate_class = self if not instantiate_class
312
- #no I am not actually this cool see https://stackoverflow.com/questions/30826015/convert-pgresult-to-an-active-record-model
313
- fields = ret.columns
314
- vals = ret.rows
315
- ret = vals.map { |v|
316
- instantiate_class.instantiate(Hash[fields.zip(v)])
317
- }
318
- end
319
- ret
320
- end
321
329
  def safe_increment(col, val) #also used in follow, also used in comment#kill
322
330
  self.class.where(id: self.id).update_all("#{col} = #{col} + #{val}")
323
331
  end
324
- def self.quick_safe_increment(id, col, val)
325
- where(id: id).update_all("#{col} = #{col} + #{val}")
326
- end
327
332
 
328
333
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamic-records-meritfront
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luke Clancy