kweerie 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: 28a36928eab4b34cff2a0fbdcf7e4614264f501d7e75f0b08ae6c28f151cd641
4
+ data.tar.gz: 8f0332f34bf28dc46c33cb671bb03e95330d08a9e15eed735be6ca64964d10db
5
+ SHA512:
6
+ metadata.gz: 026f1786bbb42976b0461951005f448e7b2169b6962a5f06737a9cc2fbdd99af93c84ea5d5ee277067788e349c6e73df98096154bebea79f6a2b9b5f450f0234
7
+ data.tar.gz: d3e8e06dcaa23ea12f205e623861ba08cd16b2be877e9008571efbe38a6f6a536efb717ed45ca977102c9b2fa2cf5ebec737a31faf6e8e4accf4e93f51412395
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-11-06
4
+
5
+ - Initial release
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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kweerie
4
+ VERSION = "0.1.0"
5
+ 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
@@ -0,0 +1,4 @@
1
+ module Kweerie
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []