database_logic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a6980677389882e319a1c4159b2954c31166402218d1485313e280a746458ddc
4
+ data.tar.gz: f482be43d5b8e545dc6fa66dee1cea114989eedc6ff4c7ec61ad7217aceb875b
5
+ SHA512:
6
+ metadata.gz: 97c6e0e74c3f5cd8d4fb7bc4c08bc4a2babc638a380f08e2500306bb7ca96b39e76330a981075e3e2243a5e6c8729582bc2c23e1b080284e50d2374234b554c1
7
+ data.tar.gz: 9b575316c6b3b788d89dfbcbf194fab94bc0d5d18a248b514b3b5f357cbe841d3bcec2ef35ccb12da7b3ef47bf2acc16cbbefb0677129d5a231833059ffa55ba
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2021-06-19
4
+
5
+ - Initial release
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at TODO: Write your email address. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in database_logic.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 CheckThisOut
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # DatabaseLogic
2
+
3
+ Yeah, yeah, database layer logic is a big no-no in the Rails community. But if you're building something serious, sooner or later you will need to break the rules. There are several gems out there that come up with different solutions, yet they are very opinionated and sometimes get in the way. This simple gem tries to solve these issues.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'database_logic'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install database_logic
20
+
21
+ ## Usage
22
+
23
+ First, let's try creating a view. For this, we will need an User model, as follows:
24
+
25
+ `rails g model User first_name:string last_name:string balance_in_cents:integer address:string city:string`
26
+
27
+ this will generate:
28
+
29
+ ```ruby
30
+ # app/db/migrate/20210619153721_create_users.rb
31
+ class CreateUsers < ActiveRecord::Migration[6.0]
32
+ def change
33
+ create_table :users do |t|
34
+ t.string :first_name
35
+ t.string :last_name
36
+ t.integer :balance_in_cents
37
+ t.string :address
38
+ t.string :city
39
+ t.timestamps
40
+ end
41
+ end
42
+ end
43
+ ```
44
+
45
+ So far so good! Let's apply our changes:
46
+
47
+ `rails db:migrate`
48
+
49
+ Now let's assume we want a view that shows users' first name and last name merged into `full_name`, so we will use the view generator to create our first view file:
50
+
51
+ `rails g database_logic:view users_full`
52
+
53
+ This will generate:
54
+
55
+ ```
56
+ # app/sql/views/20210519160509_users_full.sql
57
+ # ... some default garbage from a template...
58
+ ```
59
+ Let us edit this file, delete the gibberish and add our view SQL:
60
+
61
+ ```
62
+ create or replace view [DB].users_full as
63
+ select concat(first_name, ' ', last_name) as full_name, balance_in_cents/100 as balance, concat(address, ' ', city) as full_address from [DB].users;
64
+ ```
65
+
66
+ After saving the SQL file let's apply our changes:
67
+
68
+ `rake database_logic:views:create`
69
+
70
+ ```
71
+ MariaDB [app_dev]> describe users_full;
72
+ +--------------+---------------+------+-----+---------+-------+
73
+ | Field | Type | Null | Key | Default | Extra |
74
+ +--------------+---------------+------+-----+---------+-------+
75
+ | full_name | varchar(511) | YES | | NULL | |
76
+ | balance | decimal(14,4) | YES | | NULL | |
77
+ | full_address | varchar(511) | YES | | NULL | |
78
+ +--------------+---------------+------+-----+---------+-------+
79
+ ```
80
+
81
+ So far so good! At this point we might not want to have to run a rake task after each addition/change, so we want to .enhance the db:migrate task as follows:
82
+
83
+ ```ruby
84
+ # lib/tasks/dblogic.rake
85
+
86
+ # on drop, drop SQL logic too
87
+ Rake::Task["db:drop"].enhance ["database_logic:drop"]
88
+
89
+ # on migration, re-create all SQL logic
90
+ Rake::Task["db:migrate"].enhance do
91
+ Rake::Task["database_logic:recreate"].execute
92
+ end
93
+ ```
94
+
95
+ But in real life, users have Transactions, so let's also create a Transaction model, as follows:
96
+
97
+ `rails g model Transaction user_id:integer amount_in_cents:integer kind:string`
98
+
99
+ this will generate:
100
+
101
+ ```ruby
102
+ # app/db/migrate/20210619153732_create_transactions.rb
103
+ class CreateTransactions < ActiveRecord::Migration[6.0]
104
+ def change
105
+ create_table :transactions do |t|
106
+ t.integer :user_id
107
+ t.integer :amount_in_cents
108
+ t.string :kind
109
+ t.timestamps
110
+ end
111
+ end
112
+ end
113
+ ```
114
+
115
+ And now, let's say we want our "balance" column to update _on the database side_ whenever we add Transactions. Without bloating our models with AR hooks/callbacks, and without database inconsistencies caused by the delays that plague db to app communication, that is.
116
+
117
+ First, we will create a trigger:
118
+
119
+ `rails g database_logic:trigger after insert transactions`
120
+
121
+ This will generate:
122
+ ```
123
+ # app/sql/triggers/20213619173650_update_balance.sql
124
+ # ... some default garbage from a template...
125
+ ```
126
+
127
+ We want to edit this file and add our trigger SQL:
128
+ ```
129
+ create trigger update_balance after insert on [DB].transactions
130
+ for each row
131
+ begin
132
+ update users set balance_in_cents = balance_in_cents+NEW.amount_in_cents;
133
+ end;
134
+ ```
135
+
136
+ Finally, let's migrate: `rake db:migrate`. Our `rake db:migrate` enhancement automagically ran our SQL so we're good to go!
137
+
138
+ Let's try it out, in a rails console (`rails c`)
139
+ ```
140
+ 2.5.5 :007 > User.first.balance_in_cents;
141
+ => 0
142
+
143
+ 2.5.5 :008 > Transaction.create(user_id: 1, amount_in_cents: 123, kind: "Transfer")
144
+ => #<Transaction id: 81, user_id: 1, amount_in_cents: 123, kind: "Transfer", created_at: "2021-06-19 18:05:32", updated_at: "2021-06-19 18:05:32">
145
+ 2.5.5 :009 > User.first.balance_in_cents;
146
+ User Load (1.6ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
147
+ => 123
148
+ ```
149
+
150
+ Noice!
151
+
152
+ Functions, procedures and events work in the same manner, you can go on and play with them on your own!
153
+
154
+ ## Gotchas
155
+
156
+ You may want to use alphanumeric names for your SQL function/... names, the generators try normalizing them but just in case, try not to use names like _$up'erk3wl ha{er#name_ or similar gibberish.
157
+
158
+
159
+ ## Roadmap
160
+ * specs
161
+ * a way to select which database to use, when there are multiple
162
+
163
+
164
+ ## Contributing
165
+
166
+ Bug reports are welcome and pull requests are more than welcome on GitHub at https://github.com/freecrap/database_logic. Contributors are expected to adhere to the [code of conduct](https://github.com/freecrap/database_logic/blob/master/CODE_OF_CONDUCT.md). I haven't edited this file yet so until I do please don't be naughty.
167
+
168
+ ## License
169
+
170
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
9
+
10
+
11
+ # nick
12
+ #require 'database_logic'
13
+ #path = File.expand_path(__dir__)
14
+ #Dir.glob("#{path}/tasks/*.rake").each { |f| import f }
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "database_logic"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/database_logic/version"
4
+
5
+ # TODO write this shit
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "database_logic"
9
+ spec.version = DatabaseLogic::VERSION
10
+ spec.authors = ["Nick"]
11
+ spec.email = ["nick@flinkwise.com"]
12
+
13
+ spec.summary = "Helps working with database logic."
14
+ spec.description = "This gem lets you write SQL functions, procedures, scheduled events, views and triggers/notifications.."
15
+ spec.homepage = "https://github.com"
16
+ spec.license = "MIT"
17
+ spec.required_ruby_version = ">= 2.4.0"
18
+
19
+ #spec.metadata["allowed_push_host"] = "to do Set to 'http://mygemserver.com'"
20
+
21
+ spec.metadata["homepage_uri"] = spec.homepage
22
+ spec.metadata["source_code_uri"] = "https://www.github.com"
23
+ spec.metadata["changelog_uri"] = "https://www.github.com/changelog.md"
24
+
25
+ # Specify which files should be added to the gem when it is released.
26
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
28
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ # Uncomment to register a new dependency of your gem
35
+ # spec.add_dependency "example-gem", "~> 1.0"
36
+
37
+ # For more information and examples about making a new gem, checkout our
38
+ # guide at: https://bundler.io/guides/creating_gem.html
39
+ end
data/lib/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'database_logic'
2
+
3
+ path = File.expand_path(__dir__)
4
+ Dir.glob("#{path}/tasks/*.rake").each { |f| import f }
5
+
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "database_logic/version"
4
+
5
+ module DatabaseLogic
6
+ class Error < StandardError; end
7
+
8
+ # Your code goes here...
9
+ require 'database_logic/railtie' if defined?(Rails)
10
+ end
@@ -0,0 +1,14 @@
1
+ # lib/railtie.rb
2
+ require 'database_logic'
3
+ require 'rails'
4
+
5
+ module DatabaseLogic
6
+ class Railtie < Rails::Railtie
7
+ railtie_name :database_logic
8
+
9
+ rake_tasks do
10
+ path = File.expand_path(__dir__)
11
+ Dir.glob("#{path}/tasks/*.rake").each { |f| load f }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+
2
+
3
+ namespace :database_logic do
4
+
5
+ namespace :events do
6
+ desc "Create events"
7
+ task :create => :environment do
8
+ Dir.glob( Rails.root.join("app/sql/events/*.sql") ).sort.each do |f|
9
+ event_name = File.basename(f).split("_").drop(1).join("_").split(".").first
10
+ p " + Creating #{event_name}..."
11
+ ActiveRecord::Base.connection.execute( File.read( f ).gsub("[DB]", ActiveRecord::Base.connection_config[:database]) )
12
+ end
13
+ end
14
+
15
+ desc "Drop events"
16
+ task :drop => :environment do
17
+ Dir.glob( Rails.root.join("app/sql/events/*.sql") ).sort.each do |f|
18
+ event_name = File.basename(f).split("_").drop(1).join("_").split(".").first
19
+ p " - Dropping #{event_name}..."
20
+ ActiveRecord::Base.connection.execute "drop event if exists #{ event_name }"
21
+ end
22
+ end
23
+
24
+ desc "Recreate events (drop & create)"
25
+ task :recreate => [:drop, :create] do
26
+ p "Done!"
27
+ end
28
+ end # procedures
29
+ end
@@ -0,0 +1,30 @@
1
+
2
+
3
+ namespace :database_logic do
4
+
5
+ namespace :functions do
6
+ desc "Create stored functions"
7
+ task :create => :environment do
8
+ Dir.glob( Rails.root.join("app/sql/functions/*.sql") ).sort.each do |f|
9
+ function_name = File.basename(f).split("_").drop(1).join("_").split(".").first
10
+ p " + Creating #{function_name}..."
11
+ ActiveRecord::Base.connection.execute( File.read( f ).gsub("[DB]", ActiveRecord::Base.connection_config[:database] ) )
12
+ end
13
+ end
14
+
15
+ desc "Drop stored functions"
16
+ task :drop => :environment do
17
+ Dir.glob( Rails.root.join("app/sql/functions/*.sql") ).sort.each do |f|
18
+ function_name = File.basename(f).split("_").drop(1).join("_").split(".").first
19
+ p " - Dropping #{function_name}..."
20
+ ActiveRecord::Base.connection.execute "drop function if exists #{ function_name }"
21
+ end
22
+ end
23
+
24
+ desc "Recreate stored functions (drop & create)"
25
+ task :recreate => [:drop, :create] do
26
+ p "Done!"
27
+ end
28
+ end # functions
29
+
30
+ end
@@ -0,0 +1,36 @@
1
+
2
+
3
+ namespace :database_logic do
4
+ # remove all SQL?
5
+ desc "Create all stores SQL"
6
+ task :create => :environment do
7
+ ["events", "triggers", "procedures", "views", "functions"].reverse.each do |t|
8
+ Rake::Task["database_logic:#{t}:create"].invoke
9
+ end
10
+ end
11
+
12
+
13
+ desc "Drop all stored SQL"
14
+ task :drop => :environment do
15
+ ["events", "triggers", "procedures", "views", "functions"].each do |t|
16
+ Rake::Task["database_logic:#{t}:drop"].invoke
17
+ end
18
+ end
19
+
20
+
21
+ desc "Drop and re-create all stored SQL"
22
+ task :recreate => :environment do
23
+ Rake::Task["database_logic:drop"].invoke
24
+ Rake::Task["database_logic:create"].invoke
25
+ end
26
+ end
27
+
28
+
29
+ # on drop, drop SQL too
30
+ #Rake::Task["db:drop"].enhance ["database_logic:drop"]
31
+
32
+
33
+ # on migration, re-run all SQL
34
+ #Rake::Task["db:migrate"].enhance do
35
+ # Rake::Task["database_logic:recreate"].execute
36
+ #end
@@ -0,0 +1,31 @@
1
+
2
+
3
+ namespace :database_logic do
4
+
5
+ namespace :procedures do
6
+ desc "Create stored procedures"
7
+ task :create => :environment do
8
+ Dir.glob( Rails.root.join("app/sql/procedures/*.sql") ).sort.each do |f|
9
+ procedure_name = File.basename(f).split("_").drop(1).join("_").split(".").first
10
+ p " + Creating #{procedure_name}..."
11
+ ActiveRecord::Base.connection.execute( File.read( f ).gsub("[DB]", ActiveRecord::Base.connection_config[:database] ) )
12
+ end
13
+ end
14
+
15
+ desc "Drop stored procedures"
16
+ task :drop => :environment do
17
+ Dir.glob( Rails.root.join("app/sql/procedures/*.sql") ).sort.each do |f|
18
+ procedure_name = File.basename(f).split("_").drop(1).join("_").split(".").first
19
+ p " - Dropping #{procedure_name}..."
20
+ ActiveRecord::Base.connection.execute "drop procedure if exists #{ procedure_name }"
21
+ end
22
+ end
23
+
24
+ desc "Recreate stored procedures (drop & create)"
25
+ task :recreate => [:drop, :create] do
26
+ p "Done!"
27
+ end
28
+ end # procedures
29
+
30
+
31
+ end
@@ -0,0 +1,30 @@
1
+
2
+
3
+ namespace :database_logic do
4
+
5
+ namespace :triggers do
6
+ desc "Create triggers"
7
+ task :create => :environment do
8
+ Dir.glob( Rails.root.join("app/sql/triggers/*.sql") ).sort.each do |f|
9
+ trigger_name = File.basename(f).split("_").drop(1).join("_").split(".").first
10
+ p " + Creating #{trigger_name}..."
11
+ ActiveRecord::Base.connection.execute( File.read( f ).gsub("[DB]", ActiveRecord::Base.connection_config[:database]) )
12
+ end
13
+ end
14
+
15
+ desc "Drop triggers"
16
+ task :drop => :environment do
17
+ Dir.glob( Rails.root.join("app/sql/triggers/*.sql") ).sort.each do |f|
18
+ trigger_name = File.basename(f).split("_").drop(1).join("_").split(".").first
19
+ p " - Dropping #{trigger_name}..."
20
+ ActiveRecord::Base.connection.execute "drop trigger if exists #{ trigger_name }"
21
+ end
22
+ end
23
+
24
+ desc "Recreate triggers (drop & create)"
25
+ task :recreate => [:drop, :create] do
26
+ p "Done!"
27
+ end
28
+ end # triggers
29
+
30
+ end
@@ -0,0 +1,32 @@
1
+
2
+
3
+ namespace :database_logic do
4
+
5
+ namespace :views do
6
+ desc "Create views"
7
+ task :create => :environment do
8
+ p "Creating views"
9
+ Dir.glob( Rails.root.join("app/sql/views/*.sql") ).sort.each do |f|
10
+ view_name = File.basename(f).split("_").drop(1).join("_").split(".").first
11
+ p " + Creating #{view_name}..."
12
+ ActiveRecord::Base.connection.execute( File.read( f ).gsub("[DB]", ActiveRecord::Base.connection_config[:database]) )
13
+ end
14
+ end
15
+
16
+ desc "Drop views"
17
+ task :drop => :environment do
18
+ Dir.glob( Rails.root.join("app/sql/views/*.sql") ).sort.each do |f|
19
+ view_name = File.basename(f).split("_").drop(1).join("_").split(".").first
20
+ p " - Dropping #{view_name}..."
21
+ ActiveRecord::Base.connection.execute "drop view if exists #{ view_name }"
22
+ end
23
+ end
24
+
25
+ desc "Recreate views (drop & create)"
26
+ task :recreate => [:drop, :create] do
27
+ p "Done!"
28
+ end
29
+ end # views
30
+
31
+
32
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatabaseLogic
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,82 @@
1
+ require 'rails/generators'
2
+ module DatabaseLogic
3
+ module Generators
4
+ class EventGenerator < Rails::Generators::Base
5
+
6
+ argument :name, type: :string
7
+ argument :frequency, type: :string
8
+ argument :time_unit, type: :string
9
+
10
+ def generate(asset_type = :event)
11
+ ensure_path_exists! asset_type
12
+ validate_file_name! asset_type
13
+ validate_asset_name! asset_type
14
+
15
+ @frequency = frequency
16
+ validate_frequency!
17
+
18
+ @time_unit = time_unit
19
+ validate_time_unit!
20
+ create_asset! asset_type
21
+ end
22
+
23
+
24
+ private
25
+
26
+ def create_asset!(asset_type)
27
+ @name = serialized_asset_name
28
+ create_file file_name_with_path(asset_type.to_s.pluralize), ERB.new( File.read( "#{ File.dirname(File.realpath(__FILE__)) }/templates/#{asset_type}.erb.sqlt" ) ).result(binding)
29
+ end
30
+
31
+
32
+ def ensure_path_exists!(asset_dir)
33
+ FileUtils.mkdir_p Rails.root.join("app/sql/#{ asset_dir.to_s.pluralize }")
34
+ end
35
+
36
+
37
+ def timestamp
38
+ Time.now.strftime("%Y%M%d%H%M%S")
39
+ end
40
+
41
+
42
+ def file_name_with_timestamp
43
+ "#{ timestamp }_#{ serialized_asset_name }"
44
+ end
45
+
46
+
47
+ def file_name_with_path( dir )
48
+ Rails.root.join "app/sql/#{ dir }/#{ file_name_with_timestamp }.sql"
49
+ end
50
+
51
+
52
+ def asset_name
53
+ name
54
+ end
55
+
56
+
57
+ def serialized_asset_name
58
+ asset_name.parameterize.underscore
59
+ end
60
+
61
+
62
+ def validate_frequency!
63
+ raise Error.new("Event frequency must be between 1 and 65535") if @frequency.to_i <= 0 || @frequency.to_i > 65535
64
+ end
65
+
66
+
67
+ def validate_time_unit!
68
+ raise Error.new("A scheduled event must execute every second|minute|hour|day|week|month") if !["second", "minute", "hour", "day", "week", "month"].include?(@time_unit.to_s)
69
+ end
70
+
71
+
72
+ def validate_asset_name!( asset_type )
73
+ raise Error.new("#{ asset_type.to_s.capitalize } named #{serialized_asset_name} already exists, please choose another name") if Dir.glob( Rails.root.join( "app/sql/#{ asset_type.to_s.pluralize }/*_#{serialized_asset_name}.sql") ).size > 0
74
+ end
75
+
76
+
77
+ def validate_file_name!( asset_type )
78
+ raise Error.new("File #{file_name_with_timestamp} already exists") if File.exist?( file_name_with_path( asset_type.to_s.pluralize ) )
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,64 @@
1
+ require 'rails/generators'
2
+ module DatabaseLogic
3
+ module Generators
4
+ class FunctionGenerator < Rails::Generators::Base
5
+
6
+ argument :name, type: :string
7
+
8
+ def generate(asset_type = :function)
9
+ ensure_path_exists! asset_type
10
+ validate_file_name! asset_type
11
+ validate_asset_name! asset_type
12
+ create_asset! asset_type
13
+ end
14
+
15
+
16
+ private
17
+
18
+ def create_asset!(asset_type)
19
+ @name = serialized_asset_name
20
+ create_file file_name_with_path(asset_type.to_s.pluralize), ERB.new( File.read( "#{ File.dirname(File.realpath(__FILE__)) }/templates/#{asset_type}.erb.sqlt" ) ).result(binding)
21
+ end
22
+
23
+
24
+ def ensure_path_exists!(asset_dir)
25
+ FileUtils.mkdir_p Rails.root.join("app/sql/#{ asset_dir.to_s.pluralize }")
26
+ end
27
+
28
+
29
+ def timestamp
30
+ Time.now.strftime("%Y%M%d%H%M%S")
31
+ end
32
+
33
+
34
+ def file_name_with_timestamp
35
+ "#{ timestamp }_#{ serialized_asset_name }"
36
+ end
37
+
38
+
39
+ def file_name_with_path( dir )
40
+ Rails.root.join "app/sql/#{ dir }/#{ file_name_with_timestamp }.sql"
41
+ end
42
+
43
+
44
+ def asset_name
45
+ name
46
+ end
47
+
48
+
49
+ def serialized_asset_name
50
+ asset_name.parameterize.underscore
51
+ end
52
+
53
+
54
+ def validate_asset_name!( asset_type )
55
+ raise Error.new("#{ asset_type.to_s.capitalize } named #{serialized_asset_name} already exists, please choose another name") if Dir.glob( Rails.root.join( "app/sql/#{ asset_type.to_s.pluralize }/*_#{serialized_asset_name}.sql") ).size > 0
56
+ end
57
+
58
+
59
+ def validate_file_name!( asset_type )
60
+ raise Error.new("File #{file_name_with_timestamp} already exists") if File.exist?( file_name_with_path( asset_type.to_s.pluralize ) )
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,64 @@
1
+ require 'rails/generators'
2
+ module DatabaseLogic
3
+ module Generators
4
+ class ProcedureGenerator < Rails::Generators::Base
5
+
6
+ argument :name, type: :string
7
+
8
+ def generate(asset_type = :procedure)
9
+ ensure_path_exists! asset_type
10
+ validate_file_name! asset_type
11
+ validate_asset_name! asset_type
12
+ create_asset! asset_type
13
+ end
14
+
15
+
16
+ private
17
+
18
+ def create_asset!(asset_type)
19
+ @name = serialized_asset_name
20
+ create_file file_name_with_path(asset_type.to_s.pluralize), ERB.new( File.read( "#{ File.dirname(File.realpath(__FILE__)) }/templates/#{asset_type}.erb.sqlt" ) ).result(binding)
21
+ end
22
+
23
+
24
+ def ensure_path_exists!(asset_dir)
25
+ FileUtils.mkdir_p Rails.root.join("app/sql/#{ asset_dir.to_s.pluralize }")
26
+ end
27
+
28
+
29
+ def timestamp
30
+ Time.now.strftime("%Y%M%d%H%M%S")
31
+ end
32
+
33
+
34
+ def file_name_with_timestamp
35
+ "#{ timestamp }_#{ serialized_asset_name }"
36
+ end
37
+
38
+
39
+ def file_name_with_path( dir )
40
+ Rails.root.join "app/sql/#{ dir }/#{ file_name_with_timestamp }.sql"
41
+ end
42
+
43
+
44
+ def asset_name
45
+ name
46
+ end
47
+
48
+
49
+ def serialized_asset_name
50
+ asset_name.parameterize.underscore
51
+ end
52
+
53
+
54
+ def validate_asset_name!( asset_type )
55
+ raise Error.new("#{ asset_type.to_s.capitalize } named #{serialized_asset_name} already exists, please choose another name") if Dir.glob( Rails.root.join( "app/sql/#{ asset_type.to_s.pluralize }/*_#{serialized_asset_name}.sql") ).size > 0
56
+ end
57
+
58
+
59
+ def validate_file_name!( asset_type )
60
+ raise Error.new("File #{file_name_with_timestamp} already exists") if File.exist?( file_name_with_path( asset_type.to_s.pluralize ) )
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,5 @@
1
+ create event <%= @name %> on schedule every <%= @frequency %> <%= @time_unit %> on completion preserve do
2
+ begin
3
+ # your SQL goes in here
4
+ # select * from <%= @db %>.table ...
5
+ end;
@@ -0,0 +1,8 @@
1
+ create function [DB].<%= @name %>(some_parameter int) returns int
2
+ deterministic
3
+ reads sql data
4
+ begin
5
+ # do something here
6
+ return 0
7
+ end;
8
+
@@ -0,0 +1,6 @@
1
+ create procedure [DB].<%= name %>(IN parameter int)
2
+ begin
3
+
4
+ # your SQL goes here
5
+
6
+ end;
@@ -0,0 +1,5 @@
1
+ create trigger <%= name %> <%= @run_when %> <%= @sql_action %> on [DB].<%= @table %>
2
+ for each row
3
+ begin
4
+ # do something with NEW.<%= @table.singularize %>_ATTRIBUTE...
5
+ end;
@@ -0,0 +1,16 @@
1
+ create or replace view [DB].<%= @name %> as
2
+ # add your SQL here
3
+ # select * from [DB].table where...
4
+
5
+ # Tip: you can also back views with read-only models, eg:
6
+
7
+ # class YourModel < ApplicationRecord
8
+ # self.table_name = "<%= @name %>"
9
+ #
10
+ # def readonly?
11
+ # true
12
+ # end
13
+ # end
14
+
15
+
16
+ # this is useful for instance for reporting purpose where your view may also be used by external applications
@@ -0,0 +1,93 @@
1
+ require 'rails/generators'
2
+ module DatabaseLogic
3
+ module Generators
4
+ class TriggerGenerator < Rails::Generators::Base
5
+
6
+ argument :name, type: :string
7
+ argument :run_when, type: :string
8
+ argument :sql_action, type: :string
9
+ argument :table, type: :string
10
+
11
+ # <%= name %> <%= @when %> <%= @sql_action %> on <%= @table %>
12
+
13
+ def generate(asset_type = :trigger)
14
+ ensure_path_exists! asset_type
15
+ validate_file_name! asset_type
16
+ validate_asset_name! asset_type
17
+
18
+ @when = run_when
19
+ validate_when!
20
+
21
+ @sql_action = sql_action
22
+ validate_sql_action!
23
+
24
+ @table = table
25
+ validate_table!
26
+
27
+ create_asset! asset_type
28
+ end
29
+
30
+
31
+ private
32
+
33
+ def create_asset!(asset_type)
34
+ @name = serialized_asset_name
35
+ create_file file_name_with_path(asset_type.to_s.pluralize), ERB.new( File.read( "#{ File.dirname(File.realpath(__FILE__)) }/templates/#{asset_type}.erb.sqlt" ) ).result(binding)
36
+ end
37
+
38
+
39
+ def ensure_path_exists!(asset_dir)
40
+ FileUtils.mkdir_p Rails.root.join("app/sql/#{ asset_dir.to_s.pluralize }")
41
+ end
42
+
43
+
44
+ def timestamp
45
+ Time.now.strftime("%Y%M%d%H%M%S")
46
+ end
47
+
48
+
49
+ def file_name_with_timestamp
50
+ "#{ timestamp }_#{ serialized_asset_name }"
51
+ end
52
+
53
+
54
+ def file_name_with_path( dir )
55
+ Rails.root.join "app/sql/#{ dir }/#{ file_name_with_timestamp }.sql"
56
+ end
57
+
58
+
59
+ def asset_name
60
+ name
61
+ end
62
+
63
+
64
+ def serialized_asset_name
65
+ asset_name.parameterize.underscore
66
+ end
67
+
68
+
69
+ def validate_table!
70
+ raise Error.new("Must specify a table to attach trigger to") if @table.empty?
71
+ # TODO check if table really exists
72
+ end
73
+
74
+ def validate_sql_action!
75
+ raise Error.new("Trigger must run either on insert, update, create or delete") if !["insert", "update", "create", "delete"].include?(@sql_action.downcase)
76
+ end
77
+
78
+ def validate_when!
79
+ raise Error.new("Trigger must run either before or after") if !["before", "after"].include?(@run_when.downcase)
80
+ end
81
+
82
+
83
+ def validate_asset_name!( asset_type )
84
+ raise Error.new("#{ asset_type.to_s.capitalize } named #{serialized_asset_name} already exists, please choose another name") if Dir.glob(Rails.root.join("app/sql/#{ asset_type.to_s.pluralize }/*_#{serialized_asset_name}.sql")).size > 0
85
+ end
86
+
87
+
88
+ def validate_file_name!( asset_type )
89
+ raise Error.new("File #{file_name_with_timestamp} already exists") if File.exist?( file_name_with_path( asset_type.to_s.pluralize ) )
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,64 @@
1
+ require 'rails/generators'
2
+ module DatabaseLogic
3
+ module Generators
4
+ class ViewGenerator < Rails::Generators::Base
5
+
6
+ argument :name, type: :string
7
+
8
+ def generate(asset_type = :view)
9
+ ensure_path_exists! asset_type
10
+ validate_file_name! asset_type
11
+ validate_asset_name! asset_type
12
+ create_asset! asset_type
13
+ end
14
+
15
+
16
+ private
17
+
18
+ def create_asset!(asset_type)
19
+ @name = serialized_asset_name
20
+ create_file file_name_with_path(asset_type.to_s.pluralize), ERB.new( File.read( "#{ File.dirname(File.realpath(__FILE__)) }/templates/#{asset_type}.erb.sqlt" ) ).result(binding)
21
+ end
22
+
23
+
24
+ def ensure_path_exists!(asset_dir)
25
+ FileUtils.mkdir_p Rails.root.join("app/sql/#{ asset_dir.to_s.pluralize }")
26
+ end
27
+
28
+
29
+ def timestamp
30
+ Time.now.strftime("%Y%M%d%H%M%S")
31
+ end
32
+
33
+
34
+ def file_name_with_timestamp
35
+ "#{ timestamp }_#{ serialized_asset_name }"
36
+ end
37
+
38
+
39
+ def file_name_with_path( dir )
40
+ Rails.root.join "app/sql/#{ dir }/#{ file_name_with_timestamp }.sql"
41
+ end
42
+
43
+
44
+ def asset_name
45
+ name
46
+ end
47
+
48
+
49
+ def serialized_asset_name
50
+ asset_name.parameterize.underscore
51
+ end
52
+
53
+
54
+ def validate_asset_name!( asset_type )
55
+ raise Error.new("#{ asset_type.to_s.capitalize } named #{serialized_asset_name} already exists, please choose another name") if Dir.glob(Rails.root.join("app/sql/#{ asset_type.to_s.pluralize }/*_#{serialized_asset_name}.sql")).size > 0
56
+ end
57
+
58
+
59
+ def validate_file_name!( asset_type )
60
+ raise Error.new("File #{file_name_with_timestamp} already exists") if File.exist?( file_name_with_path( asset_type.to_s.pluralize ) )
61
+ end
62
+ end
63
+ end
64
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: database_logic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-06-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: This gem lets you write SQL functions, procedures, scheduled events,
14
+ views and triggers/notifications..
15
+ email:
16
+ - nick@flinkwise.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".gitignore"
22
+ - ".rspec"
23
+ - CHANGELOG.md
24
+ - CODE_OF_CONDUCT.md
25
+ - Gemfile
26
+ - LICENSE
27
+ - README.md
28
+ - Rakefile
29
+ - bin/console
30
+ - bin/setup
31
+ - database_logic.gemspec
32
+ - lib/Rakefile
33
+ - lib/database_logic.rb
34
+ - lib/database_logic/railtie.rb
35
+ - lib/database_logic/tasks/events.rake
36
+ - lib/database_logic/tasks/functions.rake
37
+ - lib/database_logic/tasks/generic.rake
38
+ - lib/database_logic/tasks/procedures.rake
39
+ - lib/database_logic/tasks/triggers.rake
40
+ - lib/database_logic/tasks/views.rake
41
+ - lib/database_logic/version.rb
42
+ - lib/generators/database_logic/event_generator.rb
43
+ - lib/generators/database_logic/function_generator.rb
44
+ - lib/generators/database_logic/procedure_generator.rb
45
+ - lib/generators/database_logic/templates/event.erb.sqlt
46
+ - lib/generators/database_logic/templates/function.erb.sqlt
47
+ - lib/generators/database_logic/templates/procedure.erb.sqlt
48
+ - lib/generators/database_logic/templates/trigger.erb.sqlt
49
+ - lib/generators/database_logic/templates/view.erb.sqlt
50
+ - lib/generators/database_logic/trigger_generator.rb
51
+ - lib/generators/database_logic/view_generator.rb
52
+ homepage: https://github.com
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ homepage_uri: https://github.com
57
+ source_code_uri: https://www.github.com
58
+ changelog_uri: https://www.github.com/changelog.md
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 2.4.0
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.0.8
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: Helps working with database logic.
78
+ test_files: []