kweerie 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +163 -0
- data/Rakefile +12 -0
- data/lib/generators/kweerie/kweerie_generator.rb +53 -0
- data/lib/kweerie/base.rb +75 -0
- data/lib/kweerie/configuration.rb +25 -0
- data/lib/kweerie/version.rb +5 -0
- data/lib/kweerie.rb +28 -0
- data/sig/kweerie.rbs +4 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 28a36928eab4b34cff2a0fbdcf7e4614264f501d7e75f0b08ae6c28f151cd641
|
4
|
+
data.tar.gz: 8f0332f34bf28dc46c33cb671bb03e95330d08a9e15eed735be6ca64964d10db
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 026f1786bbb42976b0461951005f448e7b2169b6962a5f06737a9cc2fbdd99af93c84ea5d5ee277067788e349c6e73df98096154bebea79f6a2b9b5f450f0234
|
7
|
+
data.tar.gz: d3e8e06dcaa23ea12f205e623861ba08cd16b2be877e9008571efbe38a6f6a536efb717ed45ca977102c9b2fa2cf5ebec737a31faf6e8e4accf4e93f51412395
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 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,163 @@
|
|
1
|
+
# Kweerie
|
2
|
+
|
3
|
+
Kweerie is a Ruby gem that helps you manage SQL queries in standalone files with parameter binding for PostgreSQL. It's designed to be a lightweight alternative to database views when you need to keep SQL logic separate from your Ruby code.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'kweerie'
|
11
|
+
```
|
12
|
+
|
13
|
+
Then execute:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Basic Usage
|
22
|
+
|
23
|
+
Create a new query class and its corresponding SQL file:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
# app/queries/user_search.rb
|
27
|
+
class UserSearch < Kweerie::Base
|
28
|
+
bind :name, as: '$1'
|
29
|
+
bind :email, as: '$2'
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
```sql
|
34
|
+
-- app/queries/user_search.sql
|
35
|
+
SELECT
|
36
|
+
users.id,
|
37
|
+
users.name,
|
38
|
+
users.email
|
39
|
+
FROM
|
40
|
+
users
|
41
|
+
WHERE
|
42
|
+
name ILIKE $1
|
43
|
+
AND email ILIKE $2;
|
44
|
+
```
|
45
|
+
|
46
|
+
Execute your query:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
results = UserSearch.with(
|
50
|
+
name: 'John%',
|
51
|
+
email: '%@example.com'
|
52
|
+
)
|
53
|
+
# => [{"id"=>9981, "name"=>"John Doe", "email"=>"johndoe@example.com"}]
|
54
|
+
```
|
55
|
+
|
56
|
+
### Rails Generator
|
57
|
+
|
58
|
+
If you're using Rails, you can use the generator to create new query files:
|
59
|
+
|
60
|
+
```bash
|
61
|
+
# Using underscored name
|
62
|
+
rails generate kweerie user_search email name
|
63
|
+
|
64
|
+
# Using CamelCase name
|
65
|
+
rails generate kweerie UserSearch email name
|
66
|
+
```
|
67
|
+
|
68
|
+
This will create both the Ruby class and SQL file with the appropriate structure.
|
69
|
+
|
70
|
+
### Configuration
|
71
|
+
|
72
|
+
By default, Kweerie uses ActiveRecord's connection if available. You can configure this and other options:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
# config/initializers/kweerie.rb
|
76
|
+
Kweerie.configure do |config|
|
77
|
+
# Use a custom connection provider
|
78
|
+
config.connection_provider = -> { MyCustomConnectionPool.connection }
|
79
|
+
|
80
|
+
# Configure where to look for SQL files
|
81
|
+
config.sql_paths = -> { ['db/queries', 'app/sql'] }
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
## Requirements
|
86
|
+
|
87
|
+
- Ruby 2.7 or higher
|
88
|
+
- PostgreSQL (this gem is PostgreSQL-specific and uses the `pg` gem)
|
89
|
+
- Rails 6+ (optional, needed for the generator and default ActiveRecord integration)
|
90
|
+
|
91
|
+
## Features
|
92
|
+
|
93
|
+
- ✅ Separate SQL files from Ruby code
|
94
|
+
- ✅ Strong parameter binding for SQL injection protection
|
95
|
+
- ✅ Rails generator for quick file creation
|
96
|
+
- ✅ Configurable connection handling
|
97
|
+
- ✅ Parameter validation
|
98
|
+
|
99
|
+
## Why Kweerie?
|
100
|
+
|
101
|
+
- **SQL Views Overkill**: When a database view is too heavy-handed but you still want to keep SQL separate from Ruby
|
102
|
+
- **Version Control**: Keep your SQL under version control alongside your Ruby code
|
103
|
+
- **Parameter Safety**: Built-in parameter binding prevents SQL injection
|
104
|
+
- **Simple Interface**: Clean, simple API for executing parameterized queries
|
105
|
+
- **Rails Integration**: Works seamlessly with Rails and ActiveRecord
|
106
|
+
|
107
|
+
## Development
|
108
|
+
|
109
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
|
110
|
+
|
111
|
+
## Contributing
|
112
|
+
|
113
|
+
1. Fork it
|
114
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
115
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
116
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
117
|
+
5. Create a new Pull Request
|
118
|
+
|
119
|
+
## License
|
120
|
+
|
121
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
122
|
+
|
123
|
+
## FAQ
|
124
|
+
|
125
|
+
**Q: Why PostgreSQL only?**
|
126
|
+
A: Kweerie uses PostgreSQL-specific features for parameter binding and result handling. Supporting other databases would require different parameter binding syntax and result handling.
|
127
|
+
|
128
|
+
**Q: Do I need Rails?**
|
129
|
+
A: No, Kweerie works with any Ruby application. Rails is only required if you want to use the generator or the automatic ActiveRecord integration.
|
130
|
+
|
131
|
+
**Q: Can I use this with views?**
|
132
|
+
A: Yes! You can write any valid PostgreSQL query, including queries that use views.
|
133
|
+
|
134
|
+
**Q: How do I handle optional parameters?**
|
135
|
+
A: Currently, all bound parameters are required. For optional parameters, you'll need to handle the conditionals in your SQL using COALESCE or similar PostgreSQL functions.
|
136
|
+
|
137
|
+
**Q: How do I convert types in parameters?**
|
138
|
+
A: PostgreSQL has robust type handling built-in, so you can handle type conversion directly in your SQL. Here are some examples:
|
139
|
+
|
140
|
+
```sql
|
141
|
+
-- Convert string to integer
|
142
|
+
WHERE id = $1::integer
|
143
|
+
|
144
|
+
-- Convert string to date
|
145
|
+
WHERE created_at > $1::date
|
146
|
+
|
147
|
+
-- Convert string to array
|
148
|
+
WHERE tags && $1::text[]
|
149
|
+
|
150
|
+
-- Convert to timestamp with timezone
|
151
|
+
WHERE created_at > $1::timestamptz
|
152
|
+
|
153
|
+
-- Multiple conversions in one query
|
154
|
+
SELECT *
|
155
|
+
FROM users
|
156
|
+
WHERE
|
157
|
+
created_at > $1::timestamptz
|
158
|
+
AND age > $2::integer
|
159
|
+
AND tags && $3::text[]
|
160
|
+
AND metadata @> $4::jsonb
|
161
|
+
```
|
162
|
+
|
163
|
+
This approach leverages PostgreSQL's native type casting system, which is both more efficient and more reliable than converting types in Ruby.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
|
5
|
+
class KweerieGenerator < Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
7
|
+
argument :parameters, type: :array, default: []
|
8
|
+
|
9
|
+
def create_query_file
|
10
|
+
# Convert parameters into bind statements
|
11
|
+
bind_statements = parameters.map.with_index(1) do |param, index|
|
12
|
+
" bind :#{param.underscore}, as: '$#{index}'"
|
13
|
+
end.join("\n")
|
14
|
+
|
15
|
+
# Create the query class
|
16
|
+
template_content = <<~RUBY
|
17
|
+
# frozen_string_literal: true
|
18
|
+
|
19
|
+
class #{class_name} < Kweerie::Base
|
20
|
+
#{bind_statements}
|
21
|
+
end
|
22
|
+
RUBY
|
23
|
+
|
24
|
+
# Ensure the queries directory exists
|
25
|
+
FileUtils.mkdir_p("app/queries")
|
26
|
+
|
27
|
+
# Create the Ruby file
|
28
|
+
create_file "app/queries/#{file_name}.rb", template_content
|
29
|
+
|
30
|
+
# Create the SQL file
|
31
|
+
create_file "app/queries/#{file_name}.sql", <<~SQL
|
32
|
+
-- Write your SQL query here
|
33
|
+
-- Available parameters: #{parameters.map { |p| "$#{parameters.index(p) + 1} (#{p})" }.join(", ")}
|
34
|
+
|
35
|
+
SELECT
|
36
|
+
-- your columns here
|
37
|
+
FROM
|
38
|
+
-- your tables here
|
39
|
+
WHERE
|
40
|
+
-- your conditions here
|
41
|
+
SQL
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def file_name
|
47
|
+
name.underscore
|
48
|
+
end
|
49
|
+
|
50
|
+
def class_name
|
51
|
+
name.classify
|
52
|
+
end
|
53
|
+
end
|
data/lib/kweerie/base.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kweerie
|
4
|
+
class Base
|
5
|
+
class << self
|
6
|
+
def inherited(subclass)
|
7
|
+
subclass.instance_variable_set(:@bindings, {})
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def bind(param_name, as:)
|
12
|
+
@bindings[param_name] = as
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :bindings
|
16
|
+
|
17
|
+
def sql_path
|
18
|
+
@sql_path ||= begin
|
19
|
+
subclass_file = "#{name.underscore}.sql"
|
20
|
+
possible_paths = Kweerie.configuration.sql_paths.call
|
21
|
+
|
22
|
+
sql_file = possible_paths.map do |path|
|
23
|
+
File.join(root_path, path, subclass_file)
|
24
|
+
end.find { |f| File.exist?(f) }
|
25
|
+
|
26
|
+
unless sql_file
|
27
|
+
raise SQLFileNotFound,
|
28
|
+
"Could not find SQL file for #{name} in paths: #{possible_paths.join(", ")}"
|
29
|
+
end
|
30
|
+
|
31
|
+
sql_file
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def sql_content
|
36
|
+
@sql_content ||= File.read(sql_path)
|
37
|
+
end
|
38
|
+
|
39
|
+
def with(params = {})
|
40
|
+
validate_params!(params)
|
41
|
+
param_values = order_params(params)
|
42
|
+
|
43
|
+
connection = Kweerie.configuration.connection_provider.call
|
44
|
+
result = connection.exec_params(sql_content, param_values)
|
45
|
+
result.to_a
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def validate_params!(params)
|
51
|
+
missing_params = bindings.keys - params.keys
|
52
|
+
raise ArgumentError, "Missing required parameters: #{missing_params.join(", ")}" if missing_params.any?
|
53
|
+
|
54
|
+
extra_params = params.keys - bindings.keys
|
55
|
+
return unless extra_params.any?
|
56
|
+
|
57
|
+
raise ArgumentError, "Unknown parameters provided: #{extra_params.join(", ")}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def order_params(params)
|
61
|
+
ordered_params = bindings.transform_values { |position| params[bindings.key(position)] }
|
62
|
+
ordered_params.values
|
63
|
+
end
|
64
|
+
|
65
|
+
def root_path
|
66
|
+
defined?(Rails) ? Rails.root : Dir.pwd
|
67
|
+
end
|
68
|
+
|
69
|
+
def using_activerecord?
|
70
|
+
defined?(ActiveRecord::Base) &&
|
71
|
+
Kweerie.configuration.connection_provider == Kweerie::Configuration.new.connection_provider
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kweerie
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :connection_provider, :sql_paths
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
# Default to using ActiveRecord's connection if available
|
9
|
+
@connection_provider = lambda {
|
10
|
+
unless defined?(ActiveRecord::Base)
|
11
|
+
raise ConfigurationError, "No connection provider configured and ActiveRecord is not available"
|
12
|
+
end
|
13
|
+
|
14
|
+
ActiveRecord::Base.connection.raw_connection
|
15
|
+
}
|
16
|
+
|
17
|
+
# Default SQL paths
|
18
|
+
@sql_paths = lambda {
|
19
|
+
paths = ["app/queries"]
|
20
|
+
paths.unshift("lib/queries") unless defined?(Rails)
|
21
|
+
paths
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/kweerie.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "kweerie/version"
|
4
|
+
|
5
|
+
module Kweerie
|
6
|
+
class Error < StandardError; end
|
7
|
+
class ConfigurationError < Error; end
|
8
|
+
class SQLFileNotFound < Error; end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_writer :configuration
|
12
|
+
|
13
|
+
def configuration
|
14
|
+
@configuration ||= Configuration.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def configure
|
18
|
+
yield(configuration)
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset_configuration!
|
22
|
+
@configuration = Configuration.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require_relative "kweerie/configuration"
|
28
|
+
require_relative "kweerie/base"
|
data/sig/kweerie.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kweerie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Toby
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pg
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '6.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '6.0'
|
55
|
+
description: A simple way to manage SQL files with parameter binding in Ruby applications
|
56
|
+
email:
|
57
|
+
- toby@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".rubocop.yml"
|
63
|
+
- CHANGELOG.md
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/generators/kweerie/kweerie_generator.rb
|
68
|
+
- lib/kweerie.rb
|
69
|
+
- lib/kweerie/base.rb
|
70
|
+
- lib/kweerie/configuration.rb
|
71
|
+
- lib/kweerie/version.rb
|
72
|
+
- sig/kweerie.rbs
|
73
|
+
homepage: https://github.com/tobyond/kweerie
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata:
|
77
|
+
homepage_uri: https://github.com/tobyond/kweerie
|
78
|
+
source_code_uri: https://github.com/tobyond/kweerie
|
79
|
+
allowed_push_host: https://rubygems.org
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 2.6.0
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubygems_version: 3.5.3
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: SQL file handler with parameter binding
|
99
|
+
test_files: []
|