dynamic-records-meritfront 2.0.21 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +140 -60
- data/lib/dynamic-records-meritfront/version.rb +1 -1
- data/lib/dynamic-records-meritfront.rb +339 -207
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a2496e72da8096b231edb9fab7d9a469926bbcd9a698d0224a78ee7ed5e25a5
|
4
|
+
data.tar.gz: 23100e7e24dad43bdfeab2f1291d4616c72e3726eba69a1c3cb2be99cf73df19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2861fed694119175940f5153f429491cc25210611da866e55b5a52720610655a89b8291dd7bf1a1042fa155ce8c7dbeacbb846470e01645dda9381d868c6b42
|
7
|
+
data.tar.gz: 84110301448a31b997b6acbd89b99dfbe3d5b3c6432e7ba7cafc20b78bfa92a80818a4291da6a30090643fc7eec3da2028f8a30030339e0ac7cdd90f7c12d49a
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,33 @@
|
|
1
1
|
# Dynamic Records Meritfront
|
2
2
|
|
3
|
-
Dyanmic Records Meritfront
|
3
|
+
Dyanmic Records Meritfront helps extend active record functionality to make it more dynamic. These methods have the goal of allowing one to
|
4
4
|
1. communicate with the frontend quicker and more effectively through Global HashIds
|
5
|
-
2. communicate with the backend more effectively with
|
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
|
+
3. add other helper methods to work with your database, such as checking if relations exist, or if a migration has been run.
|
6
7
|
|
7
|
-
Note that postgres is a requirement for this gem.
|
8
|
+
Note that postgres is currently a requirement for this gem.
|
9
|
+
|
10
|
+
## Basic Examples
|
11
|
+
```ruby
|
12
|
+
# returns a json-like hash list of user data
|
13
|
+
users = ApplicationRecord.dynamic_sql(
|
14
|
+
'get_5_users',
|
15
|
+
'select * from users limit :our_limit',
|
16
|
+
our_limit: 5
|
17
|
+
)
|
18
|
+
|
19
|
+
#returns a list of users (each an instance of User)
|
20
|
+
users = User.dynamic_sql(
|
21
|
+
'get_users_from_ids',
|
22
|
+
'select * from users where id = ANY (:ids)',
|
23
|
+
ids: [1,2,3]
|
24
|
+
)
|
25
|
+
|
26
|
+
uhgid = users.first.hgid #returns a hashed global id like: 'gid://appname/User/K9YI4K'
|
27
|
+
user = User.locate_hgid(uhgid) #returns that user
|
28
|
+
|
29
|
+
#... and much more!
|
30
|
+
```
|
8
31
|
|
9
32
|
## Installation
|
10
33
|
|
@@ -38,58 +61,21 @@ end
|
|
38
61
|
|
39
62
|
These are methods written for easier sql usage.
|
40
63
|
|
41
|
-
#### has_run_migration?(nm)
|
42
|
-
|
43
|
-
put in a string name of the migration's class and it will say if it has allready run the migration.
|
44
|
-
good during enum migrations as the code to migrate wont run if enumerate is there
|
45
|
-
as it is not yet enumerated (causing an error when it loads the class that will have the
|
46
|
-
enumeration in it). This can lead it to being impossible to commit clean code.
|
47
|
-
|
48
|
-
<details><summary>example usage</summary>
|
49
|
-
only load relationa if it exists in the database
|
50
|
-
|
51
|
-
```ruby
|
52
|
-
if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
|
53
|
-
class UserImageRelation < ApplicationRecord
|
54
|
-
belongs_to :imageable, polymorphic: true
|
55
|
-
belongs_to :image
|
56
|
-
end
|
57
|
-
else
|
58
|
-
class UserImageRelation; end
|
59
|
-
end
|
60
|
-
|
61
|
-
```
|
62
|
-
</details>
|
63
|
-
|
64
|
-
#### has_association?(*args)
|
65
|
-
|
66
|
-
accepts a list, checks if the model contains those associations
|
67
|
-
|
68
|
-
<details><summary>example usage</summary>
|
69
|
-
Check if object is a votable class
|
70
|
-
|
71
|
-
```ruby
|
72
|
-
obj = Comment.first
|
73
|
-
obj.has_association?(:votes) #true
|
74
|
-
obj = User.first
|
75
|
-
obj.has_association?(:votes) #false
|
76
|
-
```
|
77
|
-
</details>
|
78
|
-
|
79
64
|
#### self.dynamic_sql(name, sql, opts = { })
|
80
65
|
A better and safer way to write sql. Can return either a Hash, ActiveRecord::Response object, or an instantiated model.
|
66
|
+
|
81
67
|
with options:
|
82
|
-
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
68
|
+
- options not stated below: considered sql arguments, and will replace their ":option_name" with a sql argument. Always use sql arguments to avoid sql injection. Lists are converted into a format such as ```{1,2,3,4}```. Lists of lists are converted into ```(1,2,3), (4,5,6), (7,8,9)``` etc. So as to allow easy inserts/upserts.
|
69
|
+
- raw: whether to return a ActiveRecord::Response object or a hash when called on an abstract class (like ApplicationRecord). Default can be switched with DYNAMIC_SQL_RAW variable on the class level.
|
70
|
+
- instantiate_class: determines what format to return. Can return ActiveRecord objects (User, Post, etc), or whatever raw is set to. I prefer doing the alterantive ```User.dynamic_sql(...)``` which is also supported. For example, ```User.dynamic_sql(...)``` will return User records. ```ApplicationRecord.dynamic_sql(..., raw: false)``` will return a List of Hashes with the column names as keys. ```ApplicationRecord.dynamic_sql(..., raw: true)``` will return an ActiveRecord::Response.
|
71
|
+
|
72
|
+
other options:
|
73
|
+
|
74
|
+
- prepare: Defaults to true. Gets passed to ActiveRecord::Base.connection.exec_query as a parameter. Should change whether the command will be prepared, which means that on subsequent calls the command will be faster. Downsides are when, for example, the sql query has hard-coded arguments, the query always changes, causing technical issues as the number of prepared statements stack up.
|
75
|
+
- name_modifiers: allows one to change the associated name dynamically.
|
88
76
|
- multi_query: allows more than one query (you can seperate an insert and an update with ';' I dont know how else to say it.)
|
89
|
-
this disables other options including arguments (except name_modifiers). Not sure how it effects prepared statements.
|
90
|
-
- async: Gets passed to ActiveRecord::Base.connection.exec_query as a parameter. See that methods documentation for more. I was looking through the source code, and I think it only effects how it logs to the logfile?
|
91
|
-
- raw: whether to return a ActiveRecord::Response object or a hash
|
92
|
-
- other options: considered sql arguments
|
77
|
+
this disables other options including arguments (except name_modifiers). Not sure how it effects prepared statements. Not super useful.
|
78
|
+
- async: Defaults to false. Gets passed to ActiveRecord::Base.connection.exec_query as a parameter. See that methods documentation for more. I was looking through the source code, and I think it only effects how it logs to the logfile?
|
93
79
|
|
94
80
|
<details>
|
95
81
|
<summary>example usage</summary>
|
@@ -150,7 +136,7 @@ Get users who match a list of ids. Uses a postgresql Array, see the potential is
|
|
150
136
|
|
151
137
|
```ruby
|
152
138
|
id_list = [1,2,3]
|
153
|
-
return User.
|
139
|
+
return User.dynamic_sql('get_usrs', %Q{
|
154
140
|
SELECT * FROM users WHERE id = ANY (:id_list)
|
155
141
|
}, id_list: id_list)
|
156
142
|
```
|
@@ -175,7 +161,7 @@ Do an upsert
|
|
175
161
|
DO UPDATE SET updated_at = :time
|
176
162
|
}, rows: rows, time: t)
|
177
163
|
```
|
178
|
-
This will output sql similar to below. Note this can be done for multiple conversation_participants. Also note that it only sent one time variable as an argument as
|
164
|
+
This will output sql similar to below. Note this can be done for multiple conversation_participants. Also note that it only sent one time variable as an argument as dynamic_sql detected that we were sending duplicate information.
|
179
165
|
```sql
|
180
166
|
INSERT INTO conversation_participants (user_id, conversation_id, invited_by, created_at, updated_at)
|
181
167
|
VALUES ($1,$2,$3,$4,$4)
|
@@ -187,16 +173,17 @@ This will output sql similar to below. Note this can be done for multiple conver
|
|
187
173
|
|
188
174
|
|
189
175
|
#### self.dynamic_preload(records, associations)
|
190
|
-
Preloads from a list of records, and not from a ActiveRecord_Relation. This will be useful when using the above
|
176
|
+
Preloads from a list of records, and not from a ActiveRecord_Relation. This will be useful when using the above dynamic_sql method (as it returns a list of records, and not a record relation). This is basically the same as a normal relation preload but it works on a list.
|
191
177
|
|
192
178
|
<details>
|
193
179
|
<summary>example usage</summary>
|
194
180
|
Preload :votes on some comments. :votes is an active record has_many relation.
|
195
181
|
|
196
182
|
```ruby
|
197
|
-
comments = Comment.
|
183
|
+
comments = Comment.dynamic_sql('get_comments', %Q{
|
198
184
|
SELECT * FROM comments LIMIT 4
|
199
185
|
})
|
186
|
+
comments.class.to_s # 'Array' note: not a relation.
|
200
187
|
ApplicationRecord.headache_preload(comments, [:votes])
|
201
188
|
puts comments[0].votes #this line should be preloaded and hence not call the database
|
202
189
|
|
@@ -204,12 +191,50 @@ Preload :votes on some comments. :votes is an active record has_many relation.
|
|
204
191
|
user.comments.preload(:votes)
|
205
192
|
```
|
206
193
|
</details>
|
207
|
-
|
194
|
+
|
195
|
+
#### has_run_migration?(nm)
|
196
|
+
|
197
|
+
put in a string name of the migration's class and it will say if it has allready run the migration.
|
198
|
+
good during enum migrations as the code to migrate wont run if enumerate is there
|
199
|
+
as it is not yet enumerated (causing an error when it loads the class that will have the
|
200
|
+
enumeration in it). This can lead it to being impossible to commit clean code.
|
201
|
+
|
202
|
+
<details><summary>example usage</summary>
|
203
|
+
only load relationa if it exists in the database
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
|
207
|
+
class UserImageRelation < ApplicationRecord
|
208
|
+
belongs_to :imageable, polymorphic: true
|
209
|
+
belongs_to :image
|
210
|
+
end
|
211
|
+
else
|
212
|
+
class UserImageRelation; end
|
213
|
+
end
|
214
|
+
|
215
|
+
```
|
216
|
+
</details>
|
217
|
+
|
218
|
+
#### has_association?(*args)
|
219
|
+
|
220
|
+
accepts a list of association names, checks if the model has those associations
|
221
|
+
|
222
|
+
<details><summary>example usage</summary>
|
223
|
+
Check if object is a votable class
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
obj = Comment.first
|
227
|
+
obj.has_association?(:votes) #true
|
228
|
+
obj = User.first
|
229
|
+
obj.has_association?(:votes) #false
|
230
|
+
```
|
231
|
+
</details>
|
232
|
+
|
208
233
|
#### self.dynamic_instaload_sql(name, insta_array, opts = { })
|
209
|
-
*instaloads* a bunch of diffrent models at the same time by casting them to json before returning them. Kinda cool. Seems to be more efficient to preloading when i tested it.
|
234
|
+
*instaloads* a bunch of diffrent models at the same time by casting them to json before returning them. Kinda cool. Maybe a bit overcomplicated. Seems to be more efficient to preloading when i tested it.
|
210
235
|
- name is passed to dynamic_sql and is the name of the sql request
|
211
236
|
- opts are passed to dynamic_sql (except for the raw option which is set to true. Raw output is not allowed on this request)
|
212
|
-
-
|
237
|
+
- requires a list of instaload method output which provides information for how to treat each sql block.
|
213
238
|
|
214
239
|
<details>
|
215
240
|
<summary>example usage</summary>
|
@@ -274,11 +299,37 @@ the output:
|
|
274
299
|
...]}
|
275
300
|
|
276
301
|
|
302
|
+
```
|
303
|
+
</details>
|
304
|
+
|
305
|
+
#### self.instaload(sql, table_name: nil, relied_on: false, dont_return: false)
|
306
|
+
A method used to prepare data for the dynamic_instaload_sql method. It returns a hash of options.
|
307
|
+
- klass called on: if called on an abstract class (ApplicationRecord) it will return a list of hashes with the data. Otherwise returns a list of the classes records.
|
308
|
+
- table_name: sets the name of the temporary postgresql table. This can then be used in further instaload sql snippets.
|
309
|
+
- relied_on: will make it so other instaload sql snippets can reference this table (it makes it use posrgresql's WITH operator)
|
310
|
+
- dont_return: when used with relied_on makes it so that this data is not returned to rails from the database.
|
311
|
+
|
312
|
+
note that the order of the instaload methods matter depending on how they reference eachother.
|
313
|
+
<details>
|
314
|
+
<summary> format data </summary>
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
|
318
|
+
User.instaload('SELECT id FROM users WHERE users.id = ANY (:user_ids) AND users.created_at > :time', table_name: 'limited_users', relied_on: true)
|
319
|
+
#output:
|
320
|
+
{
|
321
|
+
table_name: "limited_users",
|
322
|
+
klass: "User",
|
323
|
+
sql: "\tSELECT id FROM users WHERE users.id = ANY (:user_ids) AND users.created_at > :time",
|
324
|
+
relied_on: true,
|
325
|
+
dont_return: false
|
326
|
+
}
|
327
|
+
|
277
328
|
```
|
278
329
|
</details>
|
279
330
|
|
280
331
|
#### self.dynamic_attach(instaload_sql_output, base_name, attach_name, base_on: nil, attach_on: nil, one_to_one: false)
|
281
|
-
taking the output of the dynamic_instaload_sql, this method attaches the models together so they
|
332
|
+
taking the output of the dynamic_instaload_sql, this method attaches the models together so they are attached.
|
282
333
|
- base_name: the name of the table we will be attaching to
|
283
334
|
- attach_name: the name of the table that will be attached
|
284
335
|
- 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}
|
@@ -337,7 +388,8 @@ See the hashid-rails gem for more (https://github.com/jcypret/hashid-rails). Als
|
|
337
388
|
|
338
389
|
## Potential Issues
|
339
390
|
|
340
|
-
This gem was made with a postgresql database. This could cause a lot of issues with the sql-related methods. I dont have the bandwidth to help switch it elsewhere.
|
391
|
+
- 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.
|
392
|
+
- 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.
|
341
393
|
|
342
394
|
## Changelog
|
343
395
|
|
@@ -363,6 +415,34 @@ This gem was made with a postgresql database. This could cause a lot of issues w
|
|
363
415
|
- changed dynamic_attach so that it now uses the model.dynamic attribute, instead of using singleton classes. This is better practice, and also contains all the moving parts of this gem in one place.
|
364
416
|
- added the dynamic_print method to easier see the objects one is working with.
|
365
417
|
|
418
|
+
2.0.21
|
419
|
+
- figured out how to add to a model's @attributes, so .dynamic OpenStruct no longer needed, no longer need dynamic_print, singletons are out aswell. unexpected columns are now usable as just regular attributes.
|
420
|
+
- overrode inspect to show the dynamic attributes aswell, warning about passwords printed to logs etc.
|
421
|
+
|
422
|
+
2.0.24
|
423
|
+
- added error logging in dynamic_sql method for the sql query when and if that fails. So just look at log file to see exactly what sql was running and what the args are.
|
424
|
+
- added a dont_return option to the instaload method which works with the relied_on option to have a normal WITH statement that is not returned.
|
425
|
+
|
426
|
+
3.0.1
|
427
|
+
- Previous versions will break when two sql attributes unexpectantly share the same value. Yeah my bad, was trying to be fancy and decrease sql argument count.
|
428
|
+
- People using symbols as sql values (and expecting them to be turned to strings) may have breakages. (aka a sql option like "insert_list:
|
429
|
+
[[:a, 1], [:b, 2]]" will break)
|
430
|
+
- setting DYNAMIC_SQL_RAW apparently did nothing, you actually need to set DynamicRecordsMeritfront::DYNAMIC_SQL_RAW. Changed default to false, which may break things. But
|
431
|
+
since things may be broken already, it seemed like a good time to do this.
|
432
|
+
- Went to new version due to 1. a large functionality improvement, 2. the fact that previous versions are broken as explained above.
|
433
|
+
- more on breaking error
|
434
|
+
- got this error: ActiveRecord::StatementInvalid (PG::ProtocolViolation: ERROR: bind message supplies 3 parameters, but prepared statement "a27" requires 4)
|
435
|
+
- this tells me that names are not actually required to be unique for prepared statement identification, which was a bad assumption on my part
|
436
|
+
- this also tells me that uniq'ing variables to decrease the number of them was a bad idea which could cause random failures.
|
437
|
+
- functionality improvements
|
438
|
+
- 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.
|
439
|
+
- 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.
|
440
|
+
- dynamic_instaload_sql is now just instaload_sql. dynamic_instaload_sql has been aliased.
|
441
|
+
- Name is optional on instaload_sql aswell
|
442
|
+
- 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.
|
443
|
+
- When dynamic_sql errors out, it now posts some helpful information to the log.
|
444
|
+
- Added a test script. No experience testing, so its just a method you pass a model, and then it does a rollback to reverse any changes.
|
445
|
+
|
366
446
|
## Contributing
|
367
447
|
|
368
448
|
Bug reports and pull requests are welcome on GitHub at https://github.com/LukeClancy/dynamic-records-meritfront. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/LukeClancy/dynamic-records-meritfront/blob/master/CODE_OF_CONDUCT.md).
|
@@ -11,15 +11,102 @@ module DynamicRecordsMeritfront
|
|
11
11
|
module Hashid::Rails::ClassMethods
|
12
12
|
alias hfind find_by_hashid
|
13
13
|
end
|
14
|
-
|
15
14
|
included do
|
16
15
|
# include hash id gem
|
17
16
|
include Hashid::Rails
|
18
17
|
#should work, probably able to override by redefining in ApplicationRecord class.
|
19
18
|
#Note we defined here as it breaks early on as Rails.application returns nil
|
20
19
|
PROJECT_NAME = Rails.application.class.to_s.split("::").first.to_s.downcase
|
21
|
-
|
20
|
+
DYNAMIC_SQL_RAW = false
|
22
21
|
end
|
22
|
+
class DynamicSqlVariables
|
23
|
+
attr_accessor :sql_hash
|
24
|
+
attr_accessor :params
|
25
|
+
def initialize(params)
|
26
|
+
@sql_hash = {}
|
27
|
+
self.params = params
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_key_value(key, value = nil)
|
31
|
+
value = params[key] if value.nil?
|
32
|
+
#tracks the variable and returns the keys sql variable number
|
33
|
+
sql_hash[key] ||= convert_to_query_attribute(key, value)
|
34
|
+
return sql_hash.keys.index(key) + 1
|
35
|
+
end
|
36
|
+
|
37
|
+
def next_sql_num
|
38
|
+
#gets the next sql variable number
|
39
|
+
sql_hash.keys.length + 1
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_array_for_exec_query
|
43
|
+
sql_hash.values
|
44
|
+
end
|
45
|
+
|
46
|
+
#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
|
47
|
+
#BigIntArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::BigInteger.new).freeze
|
48
|
+
#IntegerArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::Integer.new).freeze
|
49
|
+
|
50
|
+
#https://api.rubyonrails.org/files/activemodel/lib/active_model/type_rb.html
|
51
|
+
# active_model/type/helpers
|
52
|
+
# active_model/type/value
|
53
|
+
# active_model/type/big_integer
|
54
|
+
# active_model/type/binary
|
55
|
+
# active_model/type/boolean
|
56
|
+
# active_model/type/date
|
57
|
+
# active_model/type/date_time
|
58
|
+
# active_model/type/decimal
|
59
|
+
# active_model/type/float
|
60
|
+
# active_model/type/immutable_string
|
61
|
+
# active_model/type/integer
|
62
|
+
# active_model/type/string
|
63
|
+
# active_model/type/time
|
64
|
+
# active_model
|
65
|
+
|
66
|
+
DB_TYPE_MAPS = {
|
67
|
+
String => ActiveModel::Type::String,
|
68
|
+
Symbol => ActiveModel::Type::String,
|
69
|
+
Integer => ActiveModel::Type::BigInteger,
|
70
|
+
BigDecimal => ActiveRecord::Type::Decimal,
|
71
|
+
TrueClass => ActiveModel::Type::Boolean,
|
72
|
+
FalseClass => ActiveModel::Type::Boolean,
|
73
|
+
Date => ActiveModel::Type::Date,
|
74
|
+
DateTime => ActiveModel::Type::DateTime,
|
75
|
+
Time => ActiveModel::Type::Time,
|
76
|
+
Float => ActiveModel::Type::Float,
|
77
|
+
Array => Proc.new{ |first_el_class| ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(DB_TYPE_MAPS[first_el_class].new) }
|
78
|
+
}
|
79
|
+
|
80
|
+
def convert_to_query_attribute(name, v)
|
81
|
+
#yes its dumb I know dont look at me look at rails
|
82
|
+
|
83
|
+
# https://stackoverflow.com/questions/40407700/rails-exec-query-bindings-ignored
|
84
|
+
# binds = [ ActiveRecord::Relation::QueryAttribute.new(
|
85
|
+
# "id", 6, ActiveRecord::Type::Integer.new
|
86
|
+
# )]
|
87
|
+
# ApplicationRecord.connection.exec_query(
|
88
|
+
# 'SELECT * FROM users WHERE id = $1', 'sql', binds
|
89
|
+
# )
|
90
|
+
|
91
|
+
return v if v.kind_of? ActiveRecord::Relation::QueryAttribute #so users can have fine-grained control if they are trying to do something
|
92
|
+
#that we didn't handle properly.
|
93
|
+
|
94
|
+
type = DB_TYPE_MAPS[v.class]
|
95
|
+
if type.nil?
|
96
|
+
raise StandardError.new("#{name} (of value: #{v}, class: #{v.class}) unsupported class for ApplicationRecord#headache_sql")
|
97
|
+
elsif type.class == Proc
|
98
|
+
a = v[0]
|
99
|
+
# if a.nil?
|
100
|
+
# a = Integer
|
101
|
+
# elsif a.class == Array
|
102
|
+
a = a.nil? ? Integer : a.class
|
103
|
+
type = type.call(a)
|
104
|
+
else
|
105
|
+
type = type.new
|
106
|
+
end
|
107
|
+
ActiveRecord::Relation::QueryAttribute.new( name, v, type )
|
108
|
+
end
|
109
|
+
end
|
23
110
|
|
24
111
|
class MultiRowExpression
|
25
112
|
#this class is meant to be used in congunction with headache_sql method
|
@@ -42,26 +129,23 @@ module DynamicRecordsMeritfront
|
|
42
129
|
#assuming we are putting in an array of arrays.
|
43
130
|
self.val = val
|
44
131
|
end
|
45
|
-
def for_query(
|
132
|
+
def for_query(key, var_track)
|
46
133
|
#accepts x = current number of variables previously processed
|
47
134
|
#returns ["sql string with $# location information", variables themselves in order, new x]
|
48
|
-
|
49
|
-
|
135
|
+
x = -1
|
136
|
+
db_val = val.map{|attribute_array| "(#{
|
50
137
|
attribute_array.map{|attribute|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
60
|
-
attribute_index += 1
|
61
|
-
next ret
|
138
|
+
if attribute.kind_of? Symbol
|
139
|
+
#allow pointers to other more explicit variables through symbols
|
140
|
+
x = var_track.add_key_value(attribute, nil)
|
141
|
+
else
|
142
|
+
k = "#{key}_#{var_track.next_sql_num.to_s}"
|
143
|
+
x = var_track.add_key_value(k, attribute)
|
144
|
+
end
|
145
|
+
next "$" + x.to_s
|
62
146
|
}.join(",")
|
63
147
|
})"}.join(",")
|
64
|
-
return db_val
|
148
|
+
return db_val
|
65
149
|
end
|
66
150
|
end
|
67
151
|
|
@@ -230,70 +314,6 @@ module DynamicRecordsMeritfront
|
|
230
314
|
end
|
231
315
|
end
|
232
316
|
|
233
|
-
#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
|
234
|
-
#BigIntArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::BigInteger.new).freeze
|
235
|
-
#IntegerArray = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::Integer.new).freeze
|
236
|
-
|
237
|
-
#https://api.rubyonrails.org/files/activemodel/lib/active_model/type_rb.html
|
238
|
-
# active_model/type/helpers
|
239
|
-
# active_model/type/value
|
240
|
-
# active_model/type/big_integer
|
241
|
-
# active_model/type/binary
|
242
|
-
# active_model/type/boolean
|
243
|
-
# active_model/type/date
|
244
|
-
# active_model/type/date_time
|
245
|
-
# active_model/type/decimal
|
246
|
-
# active_model/type/float
|
247
|
-
# active_model/type/immutable_string
|
248
|
-
# active_model/type/integer
|
249
|
-
# active_model/type/string
|
250
|
-
# active_model/type/time
|
251
|
-
# active_model
|
252
|
-
|
253
|
-
DB_TYPE_MAPS = {
|
254
|
-
String => ActiveModel::Type::String,
|
255
|
-
Symbol => ActiveModel::Type::String,
|
256
|
-
Integer => ActiveModel::Type::BigInteger,
|
257
|
-
BigDecimal => ActiveRecord::Type::Decimal,
|
258
|
-
TrueClass => ActiveModel::Type::Boolean,
|
259
|
-
FalseClass => ActiveModel::Type::Boolean,
|
260
|
-
Date => ActiveModel::Type::Date,
|
261
|
-
DateTime => ActiveModel::Type::DateTime,
|
262
|
-
Time => ActiveModel::Type::Time,
|
263
|
-
Float => ActiveModel::Type::Float,
|
264
|
-
Array => Proc.new{ |first_el_class| ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(DB_TYPE_MAPS[first_el_class].new) }
|
265
|
-
}
|
266
|
-
|
267
|
-
def convert_to_query_attribute(name, v)
|
268
|
-
#yes its dumb I know dont look at me look at rails
|
269
|
-
|
270
|
-
# https://stackoverflow.com/questions/40407700/rails-exec-query-bindings-ignored
|
271
|
-
# binds = [ ActiveRecord::Relation::QueryAttribute.new(
|
272
|
-
# "id", 6, ActiveRecord::Type::Integer.new
|
273
|
-
# )]
|
274
|
-
# ApplicationRecord.connection.exec_query(
|
275
|
-
# 'SELECT * FROM users WHERE id = $1', 'sql', binds
|
276
|
-
# )
|
277
|
-
|
278
|
-
return v if v.kind_of? ActiveRecord::Relation::QueryAttribute #so users can have fine-grained control if they are trying to do something
|
279
|
-
#that we didn't handle properly.
|
280
|
-
|
281
|
-
type = DB_TYPE_MAPS[v.class]
|
282
|
-
if type.nil?
|
283
|
-
raise StandardError.new("#{v}'s class #{v.class} unsupported type right now for ApplicationRecord#headache_sql")
|
284
|
-
elsif type.class == Proc
|
285
|
-
a = v[0]
|
286
|
-
# if a.nil?
|
287
|
-
# a = Integer
|
288
|
-
# elsif a.class == Array
|
289
|
-
a = a.nil? ? Integer : a.class
|
290
|
-
type = type.call(a)
|
291
|
-
else
|
292
|
-
type = type.new
|
293
|
-
end
|
294
|
-
|
295
|
-
ActiveRecord::Relation::QueryAttribute.new( name, v, type )
|
296
|
-
end
|
297
317
|
#allows us to preload on a list and not a active record relation. So basically from the output of headache_sql
|
298
318
|
def dynamic_preload(records, associations)
|
299
319
|
ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
|
@@ -301,122 +321,137 @@ module DynamicRecordsMeritfront
|
|
301
321
|
|
302
322
|
alias headache_preload dynamic_preload
|
303
323
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
324
|
+
def dynamic_sql(*args) #see below for opts
|
325
|
+
# call like: dynamic_sql(name, sql, option_1: 1, option_2: 2)
|
326
|
+
# or like: dynamic_sql(sql, {option: 1, option_2: 2})
|
327
|
+
# or like: dynamic_sql(sql, option: 1, option_2: 2)
|
328
|
+
# or just: dynamic_sql(sql)
|
329
|
+
#
|
330
|
+
# Options: (options not listed will be sql arguments)
|
331
|
+
# - instantiate_class - returns User, Post, etc objects instead of straight sql output.
|
332
|
+
# I prefer doing the alterantive
|
333
|
+
# User.headache_class(...)
|
334
|
+
# which is also supported
|
335
|
+
# - prepare sets whether the db will preprocess the strategy for lookup (defaults true) (I dont think turning this off works...)
|
336
|
+
# - name_modifiers allows one to change the preprocess associated name, useful in cases of dynamic sql.
|
337
|
+
# - multi_query allows more than one query (you can seperate an insert and an update with ';' I dont know how else to say it.)
|
338
|
+
# this disables other options (except name_modifiers). Not sure how it effects prepared statements. Its a fairly useless
|
339
|
+
# command as you can do multiple queries anyway with 'WITH' statements and also gain the other options.
|
340
|
+
# - async does what it says but I haven't used it yet so. Probabably doesn't work
|
341
|
+
# - raw switches between using a Hash or a ActiveRecord::Response object when used on a abstract class
|
342
|
+
args << {} unless args[-1].kind_of? Hash
|
343
|
+
if args.length == 3
|
344
|
+
name, sql, opts = args
|
345
|
+
elsif args.length == 2
|
346
|
+
sql, opts = args
|
347
|
+
#give default name functionality as a pointer to source code location
|
348
|
+
#of the method that called this. Love ruby. Meta up the a$$
|
349
|
+
first_app_stack_trace = caller[0...3].select{|str| not str.include?('dynamic_records_meritfront.rb')}.first
|
350
|
+
shorter_source_loc = first_app_stack_trace.split('/')[-1]
|
351
|
+
name = shorter_source_loc
|
352
|
+
else
|
353
|
+
raise StandardError.new("bad input to DynamicRecordsMeritfront#dynamic_sql method.")
|
354
|
+
end
|
355
|
+
|
356
|
+
#grab options from the opts hash
|
357
|
+
instantiate_class = opts.delete(:instantiate_class)
|
358
|
+
name_modifiers = opts.delete(:name_modifiers)
|
359
|
+
raw = opts.delete(:raw)
|
360
|
+
raw = DYNAMIC_SQL_RAW if raw.nil?
|
361
|
+
name_modifiers ||= []
|
362
|
+
prepare = opts.delete(:prepare) != false
|
363
|
+
multi_query = opts.delete(:multi_query) == true
|
364
|
+
async = opts.delete(:async) == true
|
365
|
+
params = opts
|
366
|
+
|
367
|
+
#unique value hash cuts down on the number of repeated arguments like in an update or insert statement
|
368
|
+
#by checking if there is an equal existing argument and then using that argument number instead.
|
369
|
+
#If this functionality is used at a lower level we should probably remove this.
|
370
|
+
#________________________________
|
371
|
+
#got this error: ActiveRecord::StatementInvalid (PG::ProtocolViolation: ERROR: bind message supplies 3 parameters, but prepared statement "a27" requires 4)
|
372
|
+
#this error tells me two things
|
373
|
+
# 1. the name of a sql statement actually has no effect on prepared statements (whoops).
|
374
|
+
# This means we should accept queries with no name.
|
375
|
+
# 2. Need to get rid of the unique variable name functionality which uniques all the variables
|
376
|
+
# to decrease the amount sent to database
|
377
|
+
|
378
|
+
#name_modifiers are super unnecessary now I realize the given name is not actually related
|
379
|
+
#to prepped statements. But will keep it as it is backwards compatitable and sorta useful maybe.
|
380
|
+
for mod in name_modifiers
|
381
|
+
name << "_#{mod.to_s}" unless mod.nil?
|
382
|
+
end
|
383
|
+
begin
|
384
|
+
var_track = DynamicSqlVariables.new(params)
|
385
|
+
unless multi_query
|
386
|
+
#https://stackoverflow.com/questions/49947990/can-i-execute-a-raw-sql-query-leverage-prepared-statements-and-not-use-activer/67442353#67442353
|
387
|
+
#change the keys to $1, $2 etc. this step is needed for ex. {id: 1, id_user: 2}.
|
388
|
+
#doing the longer ones first prevents id replacing :id_user -> $1_user
|
389
|
+
keys = params.keys.sort{|a,b| b.to_s.length <=> a.to_s.length}
|
390
|
+
|
391
|
+
for key in keys
|
392
|
+
#replace MultiRowExpressions
|
393
|
+
v = params[key]
|
394
|
+
#check if it looks like one
|
395
|
+
looks_like_multi_attribute_array = ((v.class == Array) and (not v.first.nil?) and (v.first.class == Array))
|
396
|
+
if v.class == MultiRowExpression or looks_like_multi_attribute_array
|
397
|
+
#we need to substitute with the correct sql now.
|
398
|
+
v = MultiRowExpression.new(v) if looks_like_multi_attribute_array #standardize
|
399
|
+
#process into appropriate sql while keeping track of variables
|
400
|
+
sql_for_replace = v.for_query(key, var_track)
|
401
|
+
#replace the key with the sql
|
402
|
+
sql.gsub!(":#{key}", sql_for_replace)
|
403
|
+
else
|
404
|
+
x = var_track.next_sql_num
|
405
|
+
if sql.gsub!(":#{key}", "$#{x}")
|
406
|
+
var_track.add_key_value(key, v)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
sql_vals = var_track.get_array_for_exec_query
|
411
|
+
ret = ActiveRecord::Base.connection.exec_query sql, name, sql_vals, prepare: prepare, async: async
|
412
|
+
else
|
413
|
+
ret = ActiveRecord::Base.connection.execute sql, name
|
414
|
+
end
|
415
|
+
rescue Exception => e
|
416
|
+
#its ok if some of these are empty, just dont want the error
|
417
|
+
name ||= ''
|
418
|
+
sql ||= ''
|
419
|
+
sql_vals ||= ''
|
420
|
+
prepare ||= ''
|
421
|
+
async ||= ''
|
422
|
+
Rails.logger.error(%Q{
|
423
|
+
DynamicRecords#dynamic_sql debug info.
|
424
|
+
name: #{name.to_s}
|
425
|
+
sql: #{sql.to_s}
|
426
|
+
sql_vals: #{sql_vals.to_s}
|
427
|
+
prepare: #{prepare.to_s}
|
428
|
+
async: #{async.to_s}
|
429
|
+
})
|
430
|
+
raise e
|
431
|
+
end
|
432
|
+
|
433
|
+
#this returns a PG::Result object, which is pretty basic. To make this into User/Post/etc objects we do
|
434
|
+
#the following
|
435
|
+
if instantiate_class or not self.abstract_class
|
436
|
+
instantiate_class = self if not instantiate_class
|
437
|
+
#no I am not actually this cool see https://stackoverflow.com/questions/30826015/convert-pgresult-to-an-active-record-model
|
438
|
+
ret = ret.to_a
|
439
|
+
return ret.map{|r| dynamic_init(instantiate_class, r)}
|
440
|
+
# fields = ret.columns
|
441
|
+
# vals = ret.rows
|
442
|
+
# ret = vals.map { |v|
|
443
|
+
# dynamic_init()
|
444
|
+
# instantiate_class.instantiate(Hash[fields.zip(v)])
|
445
|
+
# }
|
446
|
+
else
|
447
|
+
if raw
|
448
|
+
return ret
|
449
|
+
else
|
450
|
+
return ret.to_a
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
412
454
|
alias headache_sql dynamic_sql
|
413
|
-
|
414
|
-
def instaload(sql, table_name: nil, relied_on: false)
|
415
|
-
table_name ||= "_" + self.to_s.underscore.downcase.pluralize
|
416
|
-
klass = self.to_s
|
417
|
-
sql = "\t" + sql.strip
|
418
|
-
return {table_name: table_name, klass: klass, sql: sql, relied_on: relied_on}
|
419
|
-
end
|
420
455
|
|
421
456
|
def _dynamic_instaload_handle_with_statements(with_statements)
|
422
457
|
%Q{WITH #{
|
@@ -427,7 +462,9 @@ module DynamicRecordsMeritfront
|
|
427
462
|
end
|
428
463
|
|
429
464
|
def _dynamic_instaload_union(insta_array)
|
430
|
-
insta_array.
|
465
|
+
insta_array.select{|insta|
|
466
|
+
not insta[:dont_return]
|
467
|
+
}.map{|insta|
|
431
468
|
start = "SELECT row_to_json(#{insta[:table_name]}.*) AS row, '#{insta[:klass]}' AS _klass, '#{insta[:table_name]}' AS _table_name FROM "
|
432
469
|
if insta[:relied_on]
|
433
470
|
ending = "#{insta[:table_name]}\n"
|
@@ -439,13 +476,31 @@ module DynamicRecordsMeritfront
|
|
439
476
|
#{ 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 ')}
|
440
477
|
end
|
441
478
|
|
442
|
-
|
479
|
+
def instaload(sql, table_name: nil, relied_on: false, dont_return: false)
|
480
|
+
table_name ||= "_" + self.to_s.underscore.downcase.pluralize
|
481
|
+
klass = self.to_s
|
482
|
+
sql = "\t" + sql.strip
|
483
|
+
return {table_name: table_name, klass: klass, sql: sql, relied_on: relied_on, dont_return: dont_return}
|
484
|
+
end
|
485
|
+
|
486
|
+
def instaload_sql(*args) #name, insta_array, opts = { })
|
487
|
+
args << {} unless args[-1].kind_of? Hash
|
488
|
+
if args.length == 3
|
489
|
+
name, insta_array, opts = args
|
490
|
+
elsif args.length == 2
|
491
|
+
insta_array, opts = args
|
492
|
+
name = nil
|
493
|
+
else
|
494
|
+
raise StandardError.new("bad input to DynamicRecordsMeritfront#instaload_sql method.")
|
495
|
+
end
|
496
|
+
|
443
497
|
with_statements = insta_array.select{|a| a[:relied_on]}
|
444
498
|
sql = %Q{
|
445
499
|
#{ _dynamic_instaload_handle_with_statements(with_statements) if with_statements.any? }
|
446
500
|
#{ _dynamic_instaload_union(insta_array)}
|
447
501
|
}
|
448
|
-
|
502
|
+
returned_arrays = insta_array.select{|ar| not ar[:dont_return]}
|
503
|
+
ret_hash = returned_arrays.map{|ar| [ar[:table_name].to_s, []]}.to_h
|
449
504
|
opts[:raw] = true
|
450
505
|
ApplicationRecord.headache_sql(name, sql, opts).rows.each{|row|
|
451
506
|
#need to pre-parsed as it has a non-normal output.
|
@@ -454,13 +509,94 @@ module DynamicRecordsMeritfront
|
|
454
509
|
json = row[0]
|
455
510
|
parsed = JSON.parse(json)
|
456
511
|
|
457
|
-
ret_hash[table_name
|
512
|
+
ret_hash[table_name].push dynamic_init(klass, parsed)
|
458
513
|
}
|
459
514
|
return ret_hash
|
460
515
|
end
|
461
|
-
alias swiss_instaload_sql
|
516
|
+
alias swiss_instaload_sql instaload_sql
|
517
|
+
alias dynamic_instaload_sql instaload_sql
|
518
|
+
|
519
|
+
def test_drmf(model_with_an_id_column_and_timestamps)
|
520
|
+
m = model_with_an_id_column_and_timestamps
|
521
|
+
ar = m.superclass
|
522
|
+
mtname = m.table_name
|
523
|
+
ApplicationRecord.transaction do
|
524
|
+
puts 'test recieving columns not normally in the record.'
|
525
|
+
rec = m.dynamic_sql(%Q{
|
526
|
+
SELECT id, 5 AS random_column from #{mtname} LIMIT 10
|
527
|
+
}).first
|
528
|
+
raise StandardError.new('no id') unless rec.id
|
529
|
+
raise StandardError.new('no dynamic column') unless rec.random_column
|
530
|
+
puts 'pass 1'
|
531
|
+
|
532
|
+
puts 'test raw off with a custom name'
|
533
|
+
recs = ar.dynamic_sql('test_2', %Q{
|
534
|
+
SELECT id, 5 AS random_column from #{mtname} LIMIT 10
|
535
|
+
}, raw: false)
|
536
|
+
raise StandardError.new('not array of hashes') unless recs.first.class == Hash and recs.class == Array
|
537
|
+
rec = recs.first
|
538
|
+
raise StandardError.new('no id [raw off]') unless rec['id']
|
539
|
+
raise StandardError.new('no dynamic column [raw off]') unless rec['random_column']
|
540
|
+
puts 'pass 2'
|
541
|
+
|
542
|
+
puts 'test raw on'
|
543
|
+
recs = ar.dynamic_sql('test_3', %Q{
|
544
|
+
SELECT id, 5 AS random_column from #{mtname} LIMIT 10
|
545
|
+
}, raw: true)
|
546
|
+
raise StandardError.new('not raw') unless recs.class == ActiveRecord::Result
|
547
|
+
rec = recs.first
|
548
|
+
raise StandardError.new('no id [raw]') unless rec['id']
|
549
|
+
raise StandardError.new('no dynamic column [raw]') unless rec['random_column']
|
550
|
+
puts 'pass 3'
|
551
|
+
|
552
|
+
puts 'test when some of the variables are diffrent then the same (#see version 3.0.1 notes)'
|
553
|
+
x = Proc.new { |a, b|
|
554
|
+
recs = ar.dynamic_sql('test_4', %Q{
|
555
|
+
SELECT id, 5 AS random_column from #{mtname} WHERE id > :a LIMIT :b
|
556
|
+
}, a: a, b: b)
|
557
|
+
}
|
558
|
+
x.call(1, 2)
|
559
|
+
x.call(1, 1)
|
560
|
+
puts 'pass 4'
|
561
|
+
|
562
|
+
puts 'test MultiAttributeArrays, including symbols and duplicate values.'
|
563
|
+
time = DateTime.now
|
564
|
+
ids = m.limit(5).pluck(:id)
|
565
|
+
values = ids.map{|id|
|
566
|
+
[id, :time, time]
|
567
|
+
}
|
568
|
+
ar.dynamic_sql(%Q{
|
569
|
+
INSERT INTO #{mtname} (id, created_at, updated_at)
|
570
|
+
VALUES :values
|
571
|
+
ON CONFLICT (id) DO NOTHING
|
572
|
+
}, values: values, time: time)
|
573
|
+
puts 'pass 5'
|
574
|
+
|
575
|
+
puts 'test arrays'
|
576
|
+
recs = ar.dynamic_sql(%Q{
|
577
|
+
SELECT id from #{mtname} where id = ANY(:idz)
|
578
|
+
}, idz: ids, raw: false)
|
579
|
+
puts recs
|
580
|
+
raise StandardError.new('wrong length') if recs.length != 5
|
581
|
+
puts 'pass 6'
|
462
582
|
|
463
|
-
|
583
|
+
|
584
|
+
puts 'test instaload_sql'
|
585
|
+
out = ar.instaload_sql([
|
586
|
+
ar.instaload("SELECT id FROM users", relied_on: true, dont_return: true, table_name: "users_2"),
|
587
|
+
ar.instaload("SELECT id FROM users_2 WHERE id % 2 != 0 LIMIT :limit", table_name: 'a'),
|
588
|
+
m.instaload("SELECT id FROM users_2 WHERE id % 2 != 1 LIMIT :limit", table_name: 'b')
|
589
|
+
], limit: 2)
|
590
|
+
puts out
|
591
|
+
raise StandardError.new('Bad return') if out["users_2"]
|
592
|
+
raise StandardError.new('Bad return') unless out["a"]
|
593
|
+
raise StandardError.new('Bad return') unless out["b"]
|
594
|
+
puts 'pass 7'
|
595
|
+
|
596
|
+
raise ActiveRecord::Rollback
|
597
|
+
#ApplicationRecord.dynamic_sql("SELECT * FROM")
|
598
|
+
end
|
599
|
+
end
|
464
600
|
|
465
601
|
def dynamic_attach(instaload_sql_output, base_name, attach_name, base_on: nil, attach_on: nil, one_to_one: false)
|
466
602
|
base_arr = instaload_sql_output[base_name]
|
@@ -563,11 +699,7 @@ module DynamicRecordsMeritfront
|
|
563
699
|
alias swiss_attach dynamic_attach
|
564
700
|
|
565
701
|
def zip_ar_result(x)
|
566
|
-
|
567
|
-
vals = x.rows
|
568
|
-
vals.map { |v|
|
569
|
-
Hash[fields.zip(v)]
|
570
|
-
}
|
702
|
+
x.to_a
|
571
703
|
end
|
572
704
|
|
573
705
|
def dynamic_init(klass, input)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynamic-records-meritfront
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luke Clancy
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashid-rails
|