dynamic-records-meritfront 0.1.4 → 0.1.7

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: 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