dynamic-records-meritfront 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +40 -0
- data/LICENSE.txt +21 -0
- data/README.md +155 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/update +55 -0
- data/dynamic-records-meritfront.gemspec +31 -0
- data/lib/dynamic-records-meritfront/version.rb +5 -0
- data/lib/dynamic-records-meritfront.rb +328 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: db86b391a9dbb87a5499b32931520d39d5dbb1b12eb4680a4721cdb2c897d57b
|
4
|
+
data.tar.gz: a2eed17ce50be8ddc027a64b389a6ba8f69d06b4adda75aab672919c559f6d44
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0fcec82242810e9e9eec7ae6e46602a6dabfefaf4ded57660b9a0b83d44b4c35d52d8e8a5b50acb64028bb473ee626f3690632cdbe0c146761cffbafa38504e
|
7
|
+
data.tar.gz: 98854c02fd8db676c39d358e33876a389e5da87c68ece01df3d005d9f4189d244271460ccfbac5316c9658a22019b04cfda2974f6beb25f5b209590a40fb30b0
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at lukeclancy@hotmail.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [https://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: https://contributor-covenant.org
|
74
|
+
[version]: https://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
dynamic-records-meritfront (0.1.4)
|
5
|
+
hashid-rails
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (7.0.4)
|
11
|
+
activesupport (= 7.0.4)
|
12
|
+
activerecord (7.0.4)
|
13
|
+
activemodel (= 7.0.4)
|
14
|
+
activesupport (= 7.0.4)
|
15
|
+
activesupport (7.0.4)
|
16
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
|
+
i18n (>= 1.6, < 2)
|
18
|
+
minitest (>= 5.1)
|
19
|
+
tzinfo (~> 2.0)
|
20
|
+
concurrent-ruby (1.1.10)
|
21
|
+
hashid-rails (1.4.1)
|
22
|
+
activerecord (>= 4.0)
|
23
|
+
hashids (~> 1.0)
|
24
|
+
hashids (1.0.6)
|
25
|
+
i18n (1.12.0)
|
26
|
+
concurrent-ruby (~> 1.0)
|
27
|
+
minitest (5.16.3)
|
28
|
+
rake (12.3.3)
|
29
|
+
tzinfo (2.0.5)
|
30
|
+
concurrent-ruby (~> 1.0)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
dynamic-records-meritfront!
|
37
|
+
rake (~> 12.0)
|
38
|
+
|
39
|
+
BUNDLED WITH
|
40
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Dynamic Records Meritfront
|
2
|
+
|
3
|
+
Dyanmic Records Meritfront contains some helpers methods for active record. These methods have the goal of allowing one to
|
4
|
+
1. communicate with the frontend quicker and more effectively through Global HashIds
|
5
|
+
2. communicate with the backend more effectively with raw 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 a page-long dynamic sql query.
|
6
|
+
|
7
|
+
I dont tend to get much feedback, so any given would be appreciated.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'active_record_meritfront'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle install
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install active_record_meritfront
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### Hashed Global IDS
|
28
|
+
|
29
|
+
hashed global ids look like this: "gid://meritfront/User/K9YI4K". They also have an optional tag so it can also look like "gid://meritfront/User/K9YI4K@user_image". They are based on global ids.
|
30
|
+
|
31
|
+
I have been using hgids (Hashed Global IDs) for a while now and they have some unique benefits in front-end back-end communication. This is as they:
|
32
|
+
1. hash the id which is good practice
|
33
|
+
2. provide a way to have tags, this is good when updating different UI elements dynamically from the backend. For instance updating the @user_image without affecting the @user_name
|
34
|
+
3. Carry the class with them, this can allow for more abstract and efficient code, and prevents id collisions between diffrent classes.
|
35
|
+
|
36
|
+
#### methods from the hashid-rails gem
|
37
|
+
|
38
|
+
See the hashid-rails gem for more (https://github.com/jcypret/hashid-rails). Also note that I aliased .hashid to .hid and .find_by_hashid to .hfind
|
39
|
+
|
40
|
+
#### methods from this gem
|
41
|
+
|
42
|
+
1. hgid(tag: nil) - get the hgid with optional tag. Aliased to ghid
|
43
|
+
2. hgid_as_selector(str, attribute: 'id') - get a css selector for the hgid, good for updating the front-end
|
44
|
+
3. self.locate_hgid(hgid_string, with_associations: nil, returns_nil: false) - locates the database record from a hgid. Here are some examples of usage:
|
45
|
+
- ApplicationRecord.locate_hgid(hgid) - <b>DANGEROUS</b> will return any object referenced by the hgid.
|
46
|
+
- User.locate_hgid(hgid) - locates the User record but only if the hgid references a user class. Fires an error if not.
|
47
|
+
- ApplicationRecord.locate_hgid(hgid, with_associations: [:votes]) - locates the record but only if the record's class has a :votes active record association. So for instance, you can accept only votable objects for upvote functionality. Fires an error if the hgid does not match.
|
48
|
+
- User.locate_hgid(hgid, returns_nil: true) - locates the hgid but only if it is the user class. Returns nil if not.
|
49
|
+
4. get_hgid_tag(hgid) - returns the tag attached to the hgid
|
50
|
+
5. self.blind_hgid(id, tag) - creates
|
51
|
+
|
52
|
+
### SQL methods
|
53
|
+
|
54
|
+
These are methods written for easier sql usage.
|
55
|
+
|
56
|
+
#### has_run_migration?(nm)
|
57
|
+
|
58
|
+
put in a string name of the migration's class and it will say if it has allready run the migration.
|
59
|
+
good during enum migrations as the code to migrate wont run if enumerate is there
|
60
|
+
as it is not yet enumerated (causing an error when it loads the class that will have the
|
61
|
+
enumeration in it). This can lead it to being impossible to commit clean code.
|
62
|
+
|
63
|
+
<details><summary>example usage</summary>
|
64
|
+
only load relationa if it exists in the database
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
if ApplicationRecord.has_run_migration?('UserImageRelationsTwo')
|
68
|
+
class UserImageRelation < ApplicationRecord
|
69
|
+
belongs_to :imageable, polymorphic: true
|
70
|
+
belongs_to :image
|
71
|
+
end
|
72
|
+
else
|
73
|
+
class UserImageRelation; end
|
74
|
+
end
|
75
|
+
|
76
|
+
```
|
77
|
+
</details>
|
78
|
+
|
79
|
+
#### has_association?(*args)
|
80
|
+
|
81
|
+
accepts a list, checks if the model contains those associations
|
82
|
+
|
83
|
+
<details><summary>example usage</summary>
|
84
|
+
Check if object is a votable class
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
obj = Comment.first
|
88
|
+
obj.has_association?(:votes) #true
|
89
|
+
obj = User.first
|
90
|
+
obj.has_association?(:votes) #false
|
91
|
+
```
|
92
|
+
</details>
|
93
|
+
|
94
|
+
#### self.headache_sql(name, sql, opts = { })
|
95
|
+
with options:
|
96
|
+
- instantiate_class: returns User, Post, etc objects instead of straight sql output.
|
97
|
+
I prefer doing the alterantive
|
98
|
+
```User.headache_sql(...)```
|
99
|
+
which is also supported
|
100
|
+
- prepare: sets whether the db will preprocess the strategy for lookup (defaults true) (have not verified the prepared-ness)
|
101
|
+
- name_modifiers: allows one to change the preprocess associated name, useful in cases of dynamic sql.
|
102
|
+
- multi_query: allows more than one query (you can seperate an insert and an update with ';' I dont know how else to say it.)
|
103
|
+
this disables other options (except name_modifiers). Not sure how it effects prepared statements.
|
104
|
+
- async: does what it says but I haven't used it yet so. Probabably doesn't work
|
105
|
+
- other options: considered sql arguments
|
106
|
+
|
107
|
+
<details>
|
108
|
+
<summary>example usage</summary>
|
109
|
+
Delete Friend Requests between two users after they have become friends.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
ApplicationRecord.headache_sql("remove_friend_requests_before_creating_friend", %Q{
|
113
|
+
DELETE FROM friend_requests
|
114
|
+
WHERE (requestie_id = :uid and requester_id = :other_user_id) OR
|
115
|
+
(requester_id = :uid and requestie_id = :other_user_id)
|
116
|
+
}, uid: Current.user.id, other_user_id: other_user.id)
|
117
|
+
```
|
118
|
+
</details>
|
119
|
+
|
120
|
+
<details>
|
121
|
+
<summary>advanced example usage</summary>
|
122
|
+
Get all users who have made a friend request to a particular user with an optional limit.
|
123
|
+
This is an example of why this method is good for dynamic prepared statements.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
return User.headache_sql('get_friend_requests', %Q{
|
127
|
+
SELECT * FROM (
|
128
|
+
SELECT other_user.id, ..., friend_requests.created_at
|
129
|
+
FROM users
|
130
|
+
INNER JOIN friend_requests ON users.id = friend_requests.requestie_id
|
131
|
+
INNER JOIN users other_user ON friend_requests.requester_id = other_user.id
|
132
|
+
WHERE users.id = :uid
|
133
|
+
) AS all_friend_requests
|
134
|
+
ORDER BY all_friend_requests.created_at DESC
|
135
|
+
#{"LIMIT :limit" if limit > 0}
|
136
|
+
}, uid: u, limit: limit, name_modifiers: [
|
137
|
+
limit > 0 ? 'limited' : nil
|
138
|
+
])
|
139
|
+
```
|
140
|
+
</details>
|
141
|
+
|
142
|
+
#### self.headache_preload(records, associations)
|
143
|
+
Preloads from a list of records, and not from a ActiveRecord_Relation. This will be useful when using the above headache_sql method.
|
144
|
+
|
145
|
+
## Contributing
|
146
|
+
|
147
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/active_record_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/[USERNAME]/active_record_meritfront/blob/master/CODE_OF_CONDUCT.md).
|
148
|
+
|
149
|
+
## License
|
150
|
+
|
151
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
152
|
+
|
153
|
+
## Code of Conduct
|
154
|
+
|
155
|
+
Everyone interacting in the ActiveRecordMeritfront project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/active_record_meritfront/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "dynamic/records/meritfront"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/bin/update
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/bin/env ruby
|
2
|
+
#this is for local development and should not be for actual updates,
|
3
|
+
# I just got annoyed with the number of things you had to do to get the updated
|
4
|
+
# version on your machine. Even after this you have to update the and restart your website...
|
5
|
+
# you can one-line that with './bin/bundle update && ../bin rails s' though. Just
|
6
|
+
# make sure you use ~> in the gemfile and your first 2 version numbers are correct.
|
7
|
+
|
8
|
+
#x = File.read('./#{project}.gemspec')
|
9
|
+
#x = x.split("\n")
|
10
|
+
#puts x.class
|
11
|
+
#puts x.length
|
12
|
+
#puts x
|
13
|
+
|
14
|
+
project = 'dynamic-records-meritfront' #<---change on a per project basis
|
15
|
+
|
16
|
+
puts "_______________________________________________"
|
17
|
+
|
18
|
+
require_relative "../lib/#{project}/version"
|
19
|
+
|
20
|
+
clz = DynamicRecordsMeritfront #<---change on a per project basis
|
21
|
+
|
22
|
+
old_version = clz::VERSION
|
23
|
+
version = old_version.split('.')
|
24
|
+
version[-1] = ((version[-1].to_i) + 1).to_s
|
25
|
+
version_new = version.join('.')
|
26
|
+
|
27
|
+
puts File.dirname(__FILE__)
|
28
|
+
puts "old version: #{old_version}"
|
29
|
+
|
30
|
+
write_to = "#{File.dirname(__FILE__)}/../lib/#{project}/version.rb"
|
31
|
+
|
32
|
+
puts "updating #{write_to} to new version #{version_new}"
|
33
|
+
|
34
|
+
|
35
|
+
File.write(write_to, "
|
36
|
+
module #{clz}
|
37
|
+
VERSION = '#{version_new}'
|
38
|
+
end
|
39
|
+
#this file gets overwritten automatically on minor updates, major ones need to be manually changed
|
40
|
+
")
|
41
|
+
|
42
|
+
puts "_______________________________________________"
|
43
|
+
puts "BUNDLE UPDATE"
|
44
|
+
puts `bundle update`
|
45
|
+
puts "_______________________________________________"
|
46
|
+
puts "GIT ADD"
|
47
|
+
puts `git add *`
|
48
|
+
puts "_______________________________________________"
|
49
|
+
puts "GIT COMMIT"
|
50
|
+
puts `git commit -m 'minor changes to version #{version_new}'`
|
51
|
+
puts "_______________________________________________"
|
52
|
+
puts "INSTALL GEM LOCALLY"
|
53
|
+
puts `bundle exec rake install`
|
54
|
+
puts "_______________________________________________"
|
55
|
+
puts "info: write 'bundle exec rake release' to release the current version to the interwebz"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'lib/dynamic-records-meritfront/version.rb'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "dynamic-records-meritfront"
|
5
|
+
spec.version = DynamicRecordsMeritfront::VERSION
|
6
|
+
spec.authors = ["Luke Clancy"]
|
7
|
+
spec.email = ["lukeclancy@hotmail.com"]
|
8
|
+
|
9
|
+
spec.summary = %q{Helpers for active record that allow for more abstract and fine-grained code.}
|
10
|
+
spec.description = %q{Adds better functionality for writing raw sql. Adds hashed global id reference string for database records. This can help with the flexibility and speed of handling records.}
|
11
|
+
spec.homepage = "https://github.com/LukeClancy/dynamic-records-meritfront"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
14
|
+
|
15
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/LukeClancy/dynamic-records-meritfront"
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/LukeClancy/dynamic-records-meritfront/blob/main/README.md"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_runtime_dependency "hashid-rails"
|
31
|
+
end
|
@@ -0,0 +1,328 @@
|
|
1
|
+
require "dynamic-records-meritfront/version"
|
2
|
+
|
3
|
+
module DynamicRecordsMeritfront
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# include hash id gem
|
7
|
+
include Hashid::Rails
|
8
|
+
|
9
|
+
# the two aliases so I dont go insane
|
10
|
+
module Hashid::Rails
|
11
|
+
alias hid hashid
|
12
|
+
end
|
13
|
+
module Hashid::Rails::ClassMethods
|
14
|
+
alias hfind find_by_hashid
|
15
|
+
end
|
16
|
+
|
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
|
72
|
+
|
73
|
+
def list_associations
|
74
|
+
#lists associations (see class method above)
|
75
|
+
self.class.list_associations
|
76
|
+
end
|
77
|
+
|
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
|
+
def has_association?(*args)
|
91
|
+
#just redirects to the class method for ease of use (see class method above)
|
92
|
+
self.class.has_association?(*args)
|
93
|
+
end
|
94
|
+
|
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
|
+
# custom hash GlobalID
|
114
|
+
def hgid(tag: nil)
|
115
|
+
gid = "gid://#{PROJECT_NAME}/#{self.class.to_s}/#{self.hid}"
|
116
|
+
if !tag
|
117
|
+
gid
|
118
|
+
else
|
119
|
+
"#{gid}@#{tag}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
alias ghid hgid #its worth it trust me the amount of times i go 'is it hash global id or global hashid?'
|
123
|
+
|
124
|
+
def hgid_as_selector(tag: nil, attribute: 'id')
|
125
|
+
#https://www.javascripttutorial.net/javascript-dom/javascript-queryselector/
|
126
|
+
gidstr = hgid(tag: tag).to_s
|
127
|
+
return self.class.string_as_selector(gidstr, attribute: attribute)
|
128
|
+
end
|
129
|
+
|
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
|
135
|
+
|
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
|
+
def safe_increment(col, val) #also used in follow, also used in comment#kill
|
322
|
+
self.class.where(id: self.id).update_all("#{col} = #{col} + #{val}")
|
323
|
+
end
|
324
|
+
def self.quick_safe_increment(id, col, val)
|
325
|
+
where(id: id).update_all("#{col} = #{col} + #{val}")
|
326
|
+
end
|
327
|
+
|
328
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dynamic-records-meritfront
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luke Clancy
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-10-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hashid-rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Adds better functionality for writing raw sql. Adds hashed global id
|
28
|
+
reference string for database records. This can help with the flexibility and speed
|
29
|
+
of handling records.
|
30
|
+
email:
|
31
|
+
- lukeclancy@hotmail.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- ".gitignore"
|
37
|
+
- CODE_OF_CONDUCT.md
|
38
|
+
- Gemfile
|
39
|
+
- Gemfile.lock
|
40
|
+
- LICENSE.txt
|
41
|
+
- README.md
|
42
|
+
- Rakefile
|
43
|
+
- bin/console
|
44
|
+
- bin/setup
|
45
|
+
- bin/update
|
46
|
+
- dynamic-records-meritfront.gemspec
|
47
|
+
- lib/dynamic-records-meritfront.rb
|
48
|
+
- lib/dynamic-records-meritfront/version.rb
|
49
|
+
homepage: https://github.com/LukeClancy/dynamic-records-meritfront
|
50
|
+
licenses:
|
51
|
+
- MIT
|
52
|
+
metadata:
|
53
|
+
allowed_push_host: https://rubygems.org
|
54
|
+
homepage_uri: https://github.com/LukeClancy/dynamic-records-meritfront
|
55
|
+
source_code_uri: https://github.com/LukeClancy/dynamic-records-meritfront
|
56
|
+
changelog_uri: https://github.com/LukeClancy/dynamic-records-meritfront/blob/main/README.md
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 2.3.0
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubygems_version: 3.1.6
|
73
|
+
signing_key:
|
74
|
+
specification_version: 4
|
75
|
+
summary: Helpers for active record that allow for more abstract and fine-grained code.
|
76
|
+
test_files: []
|