stairwell 0.3.0 → 0.6.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 +8 -15
- data/README.md +16 -0
- data/Rakefile +8 -6
- data/bin/console +5 -6
- data/lib/stairwell/bind_transformer.rb +17 -15
- data/lib/stairwell/query.rb +28 -37
- 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 +31 -14
- data/stairwell.gemspec +17 -15
- data/test.db +0 -0
- metadata +37 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3a4932425e6273ba11f06755995cc39d7045566808bd60870e5ed3ae866bc1a
|
4
|
+
data.tar.gz: 5694267cbbc05428eb8161c244d7d9ead34c21ee6999156a3046c08a736e524d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdcb61bf3e077028063dc94ecdef023aaccc709fd326cdce4354af181be15c84fbd805b492a324bd6d2117a84c6981b481835cf5ebb879ae84fda9ae2077cbca
|
7
|
+
data.tar.gz: cbb08179152c8bb7205c2d469afe0794e8505757bd65b248e53e7d6f6639fd42fa87a0523bd42993a64e97c47363d702bb90d48f72540ed63468624a49a5b4f4
|
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,9 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
stairwell (0.
|
4
|
+
stairwell (0.6.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/
|
@@ -26,36 +28,27 @@ GEM
|
|
26
28
|
tzinfo (~> 2.0)
|
27
29
|
base64 (0.1.1)
|
28
30
|
bigdecimal (3.1.4)
|
29
|
-
coderay (1.1.3)
|
30
31
|
concurrent-ruby (1.2.2)
|
31
32
|
connection_pool (2.4.1)
|
32
33
|
drb (2.1.1)
|
33
34
|
ruby2_keywords
|
34
35
|
i18n (1.14.1)
|
35
36
|
concurrent-ruby (~> 1.0)
|
36
|
-
method_source (1.0.0)
|
37
|
-
mini_portile2 (2.8.5)
|
38
37
|
minitest (5.20.0)
|
39
38
|
mutex_m (0.1.2)
|
40
|
-
|
41
|
-
coderay (~> 1.1)
|
42
|
-
method_source (~> 1.0)
|
43
|
-
rake (12.3.3)
|
39
|
+
rake (13.1.0)
|
44
40
|
ruby2_keywords (0.0.5)
|
45
|
-
sqlite3 (1.6.7)
|
46
|
-
mini_portile2 (~> 2.8.0)
|
41
|
+
sqlite3 (1.6.7-x86_64-darwin)
|
47
42
|
timeout (0.4.0)
|
48
43
|
tzinfo (2.0.6)
|
49
44
|
concurrent-ruby (~> 1.0)
|
45
|
+
zeitwerk (2.6.12)
|
50
46
|
|
51
47
|
PLATFORMS
|
52
|
-
|
48
|
+
x86_64-darwin-20
|
53
49
|
|
54
50
|
DEPENDENCIES
|
55
|
-
minitest (~> 5.0)
|
56
|
-
pry
|
57
|
-
rake (~> 12.0)
|
58
51
|
stairwell!
|
59
52
|
|
60
53
|
BUNDLED WITH
|
61
|
-
2.
|
54
|
+
2.4.10
|
data/README.md
CHANGED
@@ -83,6 +83,22 @@ They types of the binds are validated too.
|
|
83
83
|
The names binds in your sql are also validated.
|
84
84
|
All types are quoted using ActiveRecord quoting, which will be different depending on your database type (Mysql, postgres etc.)
|
85
85
|
|
86
|
+
## SQL files
|
87
|
+
Support for sql files is being trialed. If you would like to reference raw sql file instead of quoting the sql in the ruby file, you can. You just have to place the file adjacent to the ruby file and name them the same eq: ruby file is `users_count.rb` the file would need to be in the same directory and named `users_count.sql`. You also need to set the path in your config:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
# in ruby project:
|
91
|
+
Stairwell.configure do |config|
|
92
|
+
config.path = './app/queries/'
|
93
|
+
end
|
94
|
+
|
95
|
+
# in Rails:
|
96
|
+
Stairwell.configure do |config|
|
97
|
+
config.path = "#{Rails.root}/app/queries"
|
98
|
+
end
|
99
|
+
|
100
|
+
```
|
101
|
+
|
86
102
|
## Supported Types
|
87
103
|
|
88
104
|
| Type | Values Accepted | Info |
|
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,53 @@
|
|
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
|
+
set_sql_string_from_file
|
13
|
+
raise InvalidBindCount, 'Incorrect amount of args passed' if args.keys.sort != all_validations.keys.sort
|
14
|
+
|
15
|
+
@type_hash = args.each_with_object({}) do |(name, value), hash|
|
16
|
+
hash[name] = TypeObjectAssigner.run(name:, value:, all_validations:)
|
17
|
+
end
|
18
|
+
transformer.run
|
28
19
|
end
|
29
20
|
|
30
21
|
def query(string)
|
31
|
-
@sql_string
|
22
|
+
@sql_string ||= string.squish
|
32
23
|
end
|
33
24
|
|
34
25
|
private
|
35
26
|
|
36
|
-
|
37
|
-
raise InvalidBindCount.new("Incorrect amount of args passed") unless correct_args?
|
27
|
+
attr_reader :type_hash, :all_validations, :sql_string
|
38
28
|
|
39
|
-
|
40
|
-
|
41
|
-
|
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)
|
29
|
+
def transformer
|
30
|
+
BindTransformer.new(sql_string, type_hash)
|
31
|
+
end
|
46
32
|
|
47
|
-
|
33
|
+
def set_sql_string_from_file
|
34
|
+
return unless Stairwell.configuration.path && File.exist?(associated_sql_file)
|
48
35
|
|
49
|
-
|
50
|
-
|
51
|
-
|
36
|
+
file = File.read(associated_sql_file)
|
37
|
+
@sql_string = file&.squish
|
38
|
+
end
|
52
39
|
|
53
|
-
|
54
|
-
|
55
|
-
|
40
|
+
def associated_sql_file
|
41
|
+
"#{Stairwell.configuration.path}#{camelized_class}.sql"
|
42
|
+
end
|
56
43
|
|
57
|
-
|
58
|
-
|
59
|
-
|
44
|
+
def camelized_class
|
45
|
+
self.to_s.gsub(/::/, '/')
|
46
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
47
|
+
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
48
|
+
.tr("-", "_")
|
49
|
+
.downcase
|
50
|
+
end
|
60
51
|
end
|
61
52
|
end
|
62
53
|
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,40 @@ 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
|
|
23
|
+
class << self
|
24
|
+
def configuration
|
25
|
+
@configuration ||= Configuration.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def configure
|
29
|
+
yield configuration
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
24
33
|
# for development and testing
|
25
34
|
unless defined?(Rails)
|
26
|
-
ActiveRecord::Base.establish_connection(
|
35
|
+
::ActiveRecord::Base.establish_connection(
|
27
36
|
adapter: 'sqlite3',
|
28
37
|
database: 'test.db'
|
29
38
|
)
|
30
39
|
end
|
31
40
|
end
|
41
|
+
|
42
|
+
require 'date'
|
43
|
+
require 'zeitwerk'
|
44
|
+
|
45
|
+
loader = Zeitwerk::Loader.for_gem
|
46
|
+
loader.ignore("#{__dir__}/kamal/sshkit_with_ext.rb")
|
47
|
+
loader.setup
|
48
|
+
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.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- tobyond
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-13 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,7 +117,7 @@ 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
|
- - ">="
|