stairwell 0.2.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.bin/bundle +109 -0
- data/.bin/coderay +29 -0
- data/.bin/pry +29 -0
- data/.bin/rake +27 -0
- data/Gemfile +3 -5
- data/Gemfile.lock +36 -30
- data/README.md +7 -1
- data/Rakefile +8 -6
- data/bin/console +5 -6
- data/lib/stairwell/bind_transformer.rb +17 -15
- data/lib/stairwell/query.rb +12 -41
- data/lib/stairwell/type_object_assigner.rb +35 -0
- data/lib/stairwell/types/base_type.rb +26 -11
- data/lib/stairwell/types/boolean_type.rb +7 -5
- data/lib/stairwell/types/column_name_type.rb +10 -8
- data/lib/stairwell/types/date_time_type.rb +7 -5
- data/lib/stairwell/types/date_type.rb +7 -5
- data/lib/stairwell/types/float_type.rb +7 -5
- data/lib/stairwell/types/in_type.rb +17 -15
- data/lib/stairwell/types/integer_type.rb +7 -5
- data/lib/stairwell/types/null_type.rb +7 -5
- data/lib/stairwell/types/string_type.rb +7 -5
- data/lib/stairwell/types/table_name_type.rb +10 -8
- data/lib/stairwell/version.rb +3 -1
- data/lib/stairwell.rb +21 -14
- data/stairwell.gemspec +17 -15
- data/test.db +0 -0
- metadata +38 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9eb7f5f503d74ee340bc8e9011dcf587604c20d6fd959a5b006def17769b71a
|
4
|
+
data.tar.gz: 6bcbbc5866685ef8d767428a33b5862b58952a849e37237f869622ba720806d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc068d002df5eddc9026ae6ce34c786697712c2b475255fdcf5e0a0e8eaedbe9b3dc138f505ca6328342cc2e7a99648fd78087ae26b6946a481ddaefec35d943
|
7
|
+
data.tar.gz: 8d9b86448fe37776e4aef1c4a7620946af302f016516c06fe931975e6d00b9128ee1ce2d087567850bc384ff0fa147fbbf82a93cb6a2e94de9daf09918e009c4
|
data/.bin/bundle
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'bundle' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "rubygems"
|
12
|
+
|
13
|
+
m = Module.new do
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def invoked_as_script?
|
17
|
+
File.expand_path($0) == File.expand_path(__FILE__)
|
18
|
+
end
|
19
|
+
|
20
|
+
def env_var_version
|
21
|
+
ENV["BUNDLER_VERSION"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def cli_arg_version
|
25
|
+
return unless invoked_as_script? # don't want to hijack other binstubs
|
26
|
+
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
|
27
|
+
bundler_version = nil
|
28
|
+
update_index = nil
|
29
|
+
ARGV.each_with_index do |a, i|
|
30
|
+
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
31
|
+
bundler_version = a
|
32
|
+
end
|
33
|
+
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
34
|
+
bundler_version = $1
|
35
|
+
update_index = i
|
36
|
+
end
|
37
|
+
bundler_version
|
38
|
+
end
|
39
|
+
|
40
|
+
def gemfile
|
41
|
+
gemfile = ENV["BUNDLE_GEMFILE"]
|
42
|
+
return gemfile if gemfile && !gemfile.empty?
|
43
|
+
|
44
|
+
File.expand_path("../Gemfile", __dir__)
|
45
|
+
end
|
46
|
+
|
47
|
+
def lockfile
|
48
|
+
lockfile =
|
49
|
+
case File.basename(gemfile)
|
50
|
+
when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
|
51
|
+
else "#{gemfile}.lock"
|
52
|
+
end
|
53
|
+
File.expand_path(lockfile)
|
54
|
+
end
|
55
|
+
|
56
|
+
def lockfile_version
|
57
|
+
return unless File.file?(lockfile)
|
58
|
+
lockfile_contents = File.read(lockfile)
|
59
|
+
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
60
|
+
Regexp.last_match(1)
|
61
|
+
end
|
62
|
+
|
63
|
+
def bundler_requirement
|
64
|
+
@bundler_requirement ||=
|
65
|
+
env_var_version ||
|
66
|
+
cli_arg_version ||
|
67
|
+
bundler_requirement_for(lockfile_version)
|
68
|
+
end
|
69
|
+
|
70
|
+
def bundler_requirement_for(version)
|
71
|
+
return "#{Gem::Requirement.default}.a" unless version
|
72
|
+
|
73
|
+
bundler_gem_version = Gem::Version.new(version)
|
74
|
+
|
75
|
+
bundler_gem_version.approximate_recommendation
|
76
|
+
end
|
77
|
+
|
78
|
+
def load_bundler!
|
79
|
+
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
80
|
+
|
81
|
+
activate_bundler
|
82
|
+
end
|
83
|
+
|
84
|
+
def activate_bundler
|
85
|
+
gem_error = activation_error_handling do
|
86
|
+
gem "bundler", bundler_requirement
|
87
|
+
end
|
88
|
+
return if gem_error.nil?
|
89
|
+
require_error = activation_error_handling do
|
90
|
+
require "bundler/version"
|
91
|
+
end
|
92
|
+
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
93
|
+
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
|
94
|
+
exit 42
|
95
|
+
end
|
96
|
+
|
97
|
+
def activation_error_handling
|
98
|
+
yield
|
99
|
+
nil
|
100
|
+
rescue StandardError, LoadError => e
|
101
|
+
e
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
m.load_bundler!
|
106
|
+
|
107
|
+
if m.invoked_as_script?
|
108
|
+
load Gem.bin_path("bundler", "bundle")
|
109
|
+
end
|
data/.bin/coderay
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'coderay' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("coderay", "coderay")
|
data/.bin/pry
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'pry' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("pry", "pry")
|
data/.bin/rake
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("rake", "rake")
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,48 +1,54 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
stairwell (0.
|
4
|
+
stairwell (0.5.0)
|
5
5
|
activerecord (>= 4.2.11)
|
6
|
+
rake
|
6
7
|
sqlite3
|
8
|
+
zeitwerk (~> 2.5)
|
7
9
|
|
8
10
|
GEM
|
9
11
|
remote: https://rubygems.org/
|
10
12
|
specs:
|
11
|
-
activemodel (
|
12
|
-
activesupport (=
|
13
|
-
activerecord (
|
14
|
-
activemodel (=
|
15
|
-
activesupport (=
|
16
|
-
|
13
|
+
activemodel (7.1.1)
|
14
|
+
activesupport (= 7.1.1)
|
15
|
+
activerecord (7.1.1)
|
16
|
+
activemodel (= 7.1.1)
|
17
|
+
activesupport (= 7.1.1)
|
18
|
+
timeout (>= 0.4.0)
|
19
|
+
activesupport (7.1.1)
|
20
|
+
base64
|
21
|
+
bigdecimal
|
17
22
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
connection_pool (>= 2.2.5)
|
24
|
+
drb
|
25
|
+
i18n (>= 1.6, < 2)
|
26
|
+
minitest (>= 5.1)
|
27
|
+
mutex_m
|
28
|
+
tzinfo (~> 2.0)
|
29
|
+
base64 (0.1.1)
|
30
|
+
bigdecimal (3.1.4)
|
31
|
+
concurrent-ruby (1.2.2)
|
32
|
+
connection_pool (2.4.1)
|
33
|
+
drb (2.1.1)
|
34
|
+
ruby2_keywords
|
35
|
+
i18n (1.14.1)
|
25
36
|
concurrent-ruby (~> 1.0)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
thread_safe (~> 0.1)
|
36
|
-
zeitwerk (2.4.0)
|
37
|
+
minitest (5.20.0)
|
38
|
+
mutex_m (0.1.2)
|
39
|
+
rake (13.1.0)
|
40
|
+
ruby2_keywords (0.0.5)
|
41
|
+
sqlite3 (1.6.7-x86_64-darwin)
|
42
|
+
timeout (0.4.0)
|
43
|
+
tzinfo (2.0.6)
|
44
|
+
concurrent-ruby (~> 1.0)
|
45
|
+
zeitwerk (2.6.12)
|
37
46
|
|
38
47
|
PLATFORMS
|
39
|
-
|
48
|
+
x86_64-darwin-20
|
40
49
|
|
41
50
|
DEPENDENCIES
|
42
|
-
minitest (~> 5.0)
|
43
|
-
pry
|
44
|
-
rake (~> 12.0)
|
45
51
|
stairwell!
|
46
52
|
|
47
53
|
BUNDLED WITH
|
48
|
-
2.
|
54
|
+
2.4.10
|
data/README.md
CHANGED
@@ -18,6 +18,12 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
$ gem install stairwell
|
20
20
|
|
21
|
+
## Why?
|
22
|
+
Although ActiveRecord serves as an excellent tool for the majority of database queries, certain scenarios call for more customized queries.
|
23
|
+
This project was initially conceived to help transition a development team and their project from PHP to Ruby. This PHP project had thousands of complex SQL queries, thus the necessity of making SQL a first-class citizen in the ruby project enabled a smoother transition.
|
24
|
+
So, why not Arel? Arel is a powerful tool, but it's worth noting that it is considered a private API and is likely to remain so for the forseeable future.
|
25
|
+
Does this approach make queries less composable? Yes, if you are used to chaining your arel queries and AR scopes then you're probably not going to use this. However, it provides an interface that enables you to leverage SQL securely in your Ruby projects without the need to reinvent the wheel.
|
26
|
+
|
21
27
|
## Usage
|
22
28
|
|
23
29
|
Define a class in your app that inherits from `Stairwell::Query`. We're going to assume you are in a rails app, but this will work in any ruby app, although ActiveRecord is a dependency of this gem.
|
@@ -57,7 +63,7 @@ binds = {
|
|
57
63
|
name: "First",
|
58
64
|
age: 99,
|
59
65
|
active: true,
|
60
|
-
gpa: 4.2
|
66
|
+
gpa: 4.2,
|
61
67
|
date_joined: "2008-08-28",
|
62
68
|
created_at: "2008-08-28 23:41:18",
|
63
69
|
favorite_numbers: [4, 7, 100]
|
data/Rakefile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
3
5
|
|
4
6
|
Rake::TestTask.new(:test) do |t|
|
5
|
-
t.libs <<
|
6
|
-
t.libs <<
|
7
|
-
t.test_files = FileList[
|
7
|
+
t.libs << 'test'
|
8
|
+
t.libs << 'lib'
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
10
|
end
|
9
11
|
|
10
|
-
task :
|
12
|
+
task default: :test
|
data/bin/console
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'stairwell'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
8
9
|
|
9
10
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
require "pry"
|
11
|
-
Pry.start
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
require 'irb'
|
13
|
+
IRB.start(__FILE__)
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Stairwell
|
2
4
|
class BindTransformer
|
3
|
-
|
4
|
-
attr_accessor :sql, :bind_hash, :depleting_bind_hash, :converted_sql
|
5
|
+
attr_reader :sql, :bind_hash, :depleting_bind_hash, :converted_sql
|
5
6
|
|
6
7
|
def initialize(sql, bind_hash)
|
7
8
|
@sql = sql
|
@@ -16,32 +17,33 @@ module Stairwell
|
|
16
17
|
# and error will raise
|
17
18
|
# The sql string will then have the appropropriate values substituted
|
18
19
|
# with quoted values to ensure safety.
|
19
|
-
|
20
|
-
def transform
|
20
|
+
def run
|
21
21
|
convert_sql
|
22
22
|
validate_bind_hash
|
23
23
|
converted_sql
|
24
24
|
end
|
25
25
|
|
26
|
+
private
|
27
|
+
|
26
28
|
def convert_sql
|
27
|
-
@converted_sql
|
28
|
-
replace =
|
29
|
+
@converted_sql = sql.gsub(/(:?):([a-zA-Z]\w*)/) do |_|
|
30
|
+
replace = ::Regexp.last_match(2).to_sym
|
29
31
|
validate_sql(replace)
|
30
32
|
bind_hash[replace].quote
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
def validate_sql(attr)
|
37
|
-
raise SqlBindMismatch, ":#{attr} in your query is missing from your args" unless bind_hash[attr]
|
36
|
+
def validate_sql(attr)
|
37
|
+
raise SqlBindMismatch, ":#{attr} in your query is missing from your args" unless bind_hash[attr]
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
depleting_bind_hash.delete(attr)
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
end
|
42
|
+
def validate_bind_hash
|
43
|
+
return if depleting_bind_hash.empty?
|
45
44
|
|
45
|
+
raise SqlBindMismatch,
|
46
|
+
":#{depleting_bind_hash.keys.join(', ')} in your bind hash is missing from your query: #{sql}"
|
47
|
+
end
|
46
48
|
end
|
47
49
|
end
|
data/lib/stairwell/query.rb
CHANGED
@@ -1,62 +1,33 @@
|
|
1
|
-
|
2
|
-
require "stairwell/types/boolean_type"
|
3
|
-
require "stairwell/types/column_name_type"
|
4
|
-
require "stairwell/types/date_time_type"
|
5
|
-
require "stairwell/types/date_type"
|
6
|
-
require "stairwell/types/float_type"
|
7
|
-
require "stairwell/types/integer_type"
|
8
|
-
require "stairwell/types/in_type"
|
9
|
-
require "stairwell/types/string_type"
|
10
|
-
require "stairwell/types/null_type"
|
11
|
-
require "stairwell/types/table_name_type"
|
1
|
+
# frozen_string_literal: true
|
12
2
|
|
13
3
|
module Stairwell
|
14
4
|
class Query
|
15
|
-
|
16
5
|
class << self
|
17
|
-
attr_accessor :bind_hash, :all_validations, :sql_string
|
18
|
-
|
19
6
|
def validate_type(*args)
|
20
7
|
@all_validations ||= {}
|
21
8
|
@all_validations.merge!(Hash[*args])
|
22
9
|
end
|
23
10
|
|
24
11
|
def sql(**args)
|
25
|
-
|
26
|
-
|
27
|
-
|
12
|
+
raise InvalidBindCount, 'Incorrect amount of args passed' if args.keys.sort != all_validations.keys.sort
|
13
|
+
|
14
|
+
@type_hash = args.each_with_object({}) do |(name, value), hash|
|
15
|
+
hash[name] = TypeObjectAssigner.run(name:, value:, all_validations:)
|
16
|
+
end
|
17
|
+
transformer.run
|
28
18
|
end
|
29
19
|
|
30
20
|
def query(string)
|
31
|
-
@sql_string = string
|
21
|
+
@sql_string = string.squish
|
32
22
|
end
|
33
23
|
|
34
24
|
private
|
35
25
|
|
36
|
-
|
37
|
-
raise InvalidBindCount.new("Incorrect amount of args passed") unless correct_args?
|
38
|
-
|
39
|
-
bind_hash.each do |bind_name, bind_value|
|
40
|
-
type = all_validations[bind_name]
|
41
|
-
if type.is_a?(Array)
|
42
|
-
type = type.first
|
43
|
-
type_object = Types::InType.new(bind_value, type)
|
44
|
-
end
|
45
|
-
type_object ||= Object.const_get(TYPE_CLASSES[type]).new(bind_value)
|
46
|
-
|
47
|
-
raise InvalidBindType.new("#{bind_name} is not #{all_validations[bind_name]}") unless type_object.valid?
|
26
|
+
attr_reader :type_hash, :all_validations, :sql_string
|
48
27
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
def transformed_sql_string
|
54
|
-
BindTransformer.new(sql_string.squish!, bind_hash).transform
|
55
|
-
end
|
56
|
-
|
57
|
-
def correct_args?
|
58
|
-
bind_hash.keys.sort == all_validations.keys.sort
|
59
|
-
end
|
28
|
+
def transformer
|
29
|
+
BindTransformer.new(sql_string, type_hash)
|
30
|
+
end
|
60
31
|
end
|
61
32
|
end
|
62
33
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stairwell
|
4
|
+
class TypeObjectAssigner
|
5
|
+
attr_reader :name, :value, :all_validations
|
6
|
+
|
7
|
+
def self.run(...)
|
8
|
+
new(...).run
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(name:, value:, all_validations:)
|
12
|
+
@name = name
|
13
|
+
@value = value
|
14
|
+
@all_validations = all_validations
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
type_object
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def proposed_type
|
24
|
+
all_validations[name]
|
25
|
+
end
|
26
|
+
|
27
|
+
def type_object
|
28
|
+
if proposed_type.is_a?(Array)
|
29
|
+
Types::InType.new(value, proposed_type.first)
|
30
|
+
else
|
31
|
+
Object.const_get(TYPE_CLASSES[proposed_type]).new(value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,17 +1,32 @@
|
|
1
|
-
|
2
|
-
class BaseType
|
3
|
-
attr_reader :value
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class BaseType
|
6
|
+
attr_reader :value
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
def initialize(value)
|
9
|
+
@value = value
|
10
|
+
validate!
|
11
|
+
end
|
12
|
+
|
13
|
+
def quote
|
14
|
+
connection.quote(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def connection
|
18
|
+
@connection ||= ActiveRecord::Base.connection
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate!
|
22
|
+
return if valid?
|
23
|
+
|
24
|
+
raise InvalidBindType, "#{value} is not #{type_name}"
|
25
|
+
end
|
12
26
|
|
13
|
-
|
14
|
-
|
27
|
+
def type_name
|
28
|
+
self.class.name.split('::').last.gsub('Type', '').downcase
|
29
|
+
end
|
15
30
|
end
|
16
31
|
end
|
17
32
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Stairwell
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class BooleanType < BaseType
|
6
|
+
def valid?
|
7
|
+
value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
8
|
+
end
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -1,13 +1,15 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Stairwell
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class ColumnNameType < BaseType
|
6
|
+
def valid?
|
7
|
+
value.is_a?(String)
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
def quote
|
11
|
+
connection.quote_column_name(value)
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Stairwell
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class DateTimeType < BaseType
|
6
|
+
def valid?
|
7
|
+
value.is_a?(String)
|
8
|
+
end
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Stairwell
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class DateType < BaseType
|
6
|
+
def valid?
|
7
|
+
value.is_a?(String)
|
8
|
+
end
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Stairwell
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class FloatType < BaseType
|
6
|
+
def valid?
|
7
|
+
value.is_a?(Float)
|
8
|
+
end
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -1,28 +1,30 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Stairwell
|
4
|
-
|
5
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class InType
|
6
|
+
attr_reader :value, :type
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
def initialize(value, type)
|
9
|
+
@value = value
|
10
|
+
@type = type
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
def quote
|
14
|
+
contained_values.map(&:quote).join(', ')
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
def valid?
|
18
|
+
value.is_a?(Array) && contained_values.all?(&:valid?)
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
+
private
|
21
22
|
|
22
23
|
def contained_values
|
23
24
|
value.map do |contained|
|
24
25
|
Object.const_get(Stairwell::TYPE_CLASSES[type]).new(contained)
|
25
26
|
end
|
26
27
|
end
|
28
|
+
end
|
27
29
|
end
|
28
30
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Stairwell
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class IntegerType < BaseType
|
6
|
+
def valid?
|
7
|
+
value.is_a?(Integer)
|
8
|
+
end
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Stairwell
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class NullType < BaseType
|
6
|
+
def valid?
|
7
|
+
value.is_a?(NilClass)
|
8
|
+
end
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Stairwell
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class StringType < BaseType
|
6
|
+
def valid?
|
7
|
+
value.is_a?(String)
|
8
|
+
end
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -1,13 +1,15 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Stairwell
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module Stairwell
|
4
|
+
module Types
|
5
|
+
class TableNameType < BaseType
|
6
|
+
def valid?
|
7
|
+
value.is_a?(String)
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
def quote
|
11
|
+
connection.quote_table_name(value)
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
data/lib/stairwell/version.rb
CHANGED
data/lib/stairwell.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require "stairwell/version"
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
5
4
|
|
6
5
|
module Stairwell
|
7
6
|
class Error < StandardError; end
|
@@ -10,22 +9,30 @@ module Stairwell
|
|
10
9
|
class SqlBindMismatch < StandardError; end
|
11
10
|
|
12
11
|
TYPE_CLASSES = {
|
13
|
-
string:
|
14
|
-
integer:
|
15
|
-
boolean:
|
16
|
-
float:
|
17
|
-
date:
|
18
|
-
date_time:
|
19
|
-
null:
|
20
|
-
column_name:
|
21
|
-
table_name:
|
12
|
+
string: 'Stairwell::Types::StringType',
|
13
|
+
integer: 'Stairwell::Types::IntegerType',
|
14
|
+
boolean: 'Stairwell::Types::BooleanType',
|
15
|
+
float: 'Stairwell::Types::FloatType',
|
16
|
+
date: 'Stairwell::Types::DateType',
|
17
|
+
date_time: 'Stairwell::Types::DateTimeType',
|
18
|
+
null: 'Stairwell::Types::NullType',
|
19
|
+
column_name: 'Stairwell::Types::ColumnNameType',
|
20
|
+
table_name: 'Stairwell::Types::TableNameType'
|
22
21
|
}.freeze
|
23
22
|
|
24
23
|
# for development and testing
|
25
24
|
unless defined?(Rails)
|
26
|
-
ActiveRecord::Base.establish_connection(
|
25
|
+
::ActiveRecord::Base.establish_connection(
|
27
26
|
adapter: 'sqlite3',
|
28
27
|
database: 'test.db'
|
29
28
|
)
|
30
29
|
end
|
31
30
|
end
|
31
|
+
|
32
|
+
require 'date'
|
33
|
+
require 'zeitwerk'
|
34
|
+
|
35
|
+
loader = Zeitwerk::Loader.for_gem
|
36
|
+
loader.ignore("#{__dir__}/kamal/sshkit_with_ext.rb")
|
37
|
+
loader.setup
|
38
|
+
loader.eager_load # We need all commands loaded.
|
data/stairwell.gemspec
CHANGED
@@ -1,35 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'lib/stairwell/version'
|
2
4
|
|
3
5
|
Gem::Specification.new do |spec|
|
4
|
-
spec.name =
|
6
|
+
spec.name = 'stairwell'
|
5
7
|
spec.version = Stairwell::VERSION
|
6
|
-
spec.authors = [
|
8
|
+
spec.authors = ['tobyond']
|
7
9
|
|
8
|
-
spec.summary =
|
9
|
-
spec.description =
|
10
|
-
spec.homepage =
|
11
|
-
spec.license =
|
12
|
-
spec.required_ruby_version = Gem::Requirement.new(
|
10
|
+
spec.summary = 'stairwell for sql'
|
11
|
+
spec.description = 'I actually like SQL, and want to use it in my Ruby projects.'
|
12
|
+
spec.homepage = 'https://github.com/tobyond/stairwell'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.2.0')
|
13
15
|
|
14
16
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
15
17
|
|
16
|
-
spec.metadata[
|
17
|
-
spec.metadata[
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/tobyond/stairwell'
|
18
20
|
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
19
21
|
|
20
22
|
# Specify which files should be added to the gem when it is released.
|
21
23
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
-
spec.files
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
25
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
26
|
end
|
25
|
-
spec.bindir =
|
27
|
+
spec.bindir = 'exe'
|
26
28
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
-
spec.require_paths = [
|
29
|
+
spec.require_paths = ['lib']
|
28
30
|
|
29
31
|
spec.add_dependency 'activerecord', '>= 4.2.11'
|
32
|
+
spec.add_dependency 'rake'
|
33
|
+
spec.add_dependency 'zeitwerk', '~> 2.5'
|
30
34
|
|
31
35
|
# for development and testing
|
32
|
-
unless defined?(Rails)
|
33
|
-
spec.add_dependency 'sqlite3'
|
34
|
-
end
|
36
|
+
spec.add_dependency 'sqlite3' unless defined?(Rails)
|
35
37
|
end
|
data/test.db
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stairwell
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- tobyond
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 4.2.11
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: zeitwerk
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.5'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: sqlite3
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,12 +66,16 @@ dependencies:
|
|
38
66
|
- - ">="
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '0'
|
41
|
-
description:
|
69
|
+
description: I actually like SQL, and want to use it in my Ruby projects.
|
42
70
|
email:
|
43
71
|
executables: []
|
44
72
|
extensions: []
|
45
73
|
extra_rdoc_files: []
|
46
74
|
files:
|
75
|
+
- ".bin/bundle"
|
76
|
+
- ".bin/coderay"
|
77
|
+
- ".bin/pry"
|
78
|
+
- ".bin/rake"
|
47
79
|
- ".gitignore"
|
48
80
|
- ".travis.yml"
|
49
81
|
- Gemfile
|
@@ -56,6 +88,7 @@ files:
|
|
56
88
|
- lib/stairwell.rb
|
57
89
|
- lib/stairwell/bind_transformer.rb
|
58
90
|
- lib/stairwell/query.rb
|
91
|
+
- lib/stairwell/type_object_assigner.rb
|
59
92
|
- lib/stairwell/types/base_type.rb
|
60
93
|
- lib/stairwell/types/boolean_type.rb
|
61
94
|
- lib/stairwell/types/column_name_type.rb
|
@@ -84,14 +117,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
117
|
requirements:
|
85
118
|
- - ">="
|
86
119
|
- !ruby/object:Gem::Version
|
87
|
-
version: 2.
|
120
|
+
version: 3.2.0
|
88
121
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
122
|
requirements:
|
90
123
|
- - ">="
|
91
124
|
- !ruby/object:Gem::Version
|
92
125
|
version: '0'
|
93
126
|
requirements: []
|
94
|
-
rubygems_version: 3.
|
127
|
+
rubygems_version: 3.4.10
|
95
128
|
signing_key:
|
96
129
|
specification_version: 4
|
97
130
|
summary: stairwell for sql
|