relation_to_struct 1.5.0 → 1.7.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 +5 -5
- data/Appraisals +6 -1
- data/README.md +9 -1
- data/gemfiles/rails_5_0.gemfile +2 -1
- data/gemfiles/rails_5_1.gemfile +2 -1
- data/gemfiles/rails_5_2.gemfile +2 -1
- data/gemfiles/rails_6_0.gemfile +8 -0
- data/gemfiles/rails_6_1.gemfile +8 -0
- data/gemfiles/rails_7_0.gemfile +8 -0
- data/lib/relation_to_struct/active_record_base_extension.rb +9 -79
- data/lib/relation_to_struct/active_record_connection_adapter_extension.rb +80 -0
- data/lib/relation_to_struct/active_record_relation_extension.rb +1 -3
- data/lib/relation_to_struct/version.rb +1 -1
- data/lib/relation_to_struct.rb +1 -0
- data/relation_to_struct.gemspec +7 -7
- data/spec/active_record_connection_adapter_spec.rb +270 -0
- data/spec/active_record_helper/setup.rb +6 -1
- data/spec/active_record_relation_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- metadata +26 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f11031515a2afe3d55c8cfb98a664eb984079514c727233c74f341be071a825d
|
4
|
+
data.tar.gz: 4b97b2856f8529291ea4db5a97bc95b6b58ce5d4b4fbb3eaba066b54a3b317bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48ab91406c6acb33100fc8849b2f7ded38ca7a32b199f090d4bd388219b22a615c89f0853d211fa64b2f9b57ba83cf17de4499dc114f2a70578f449c3f389029
|
7
|
+
data.tar.gz: 9f004fded83df12de47eb94047e0245989f700edd63256af1ee9049b7657126603a0460d49ff54ee1ea67267f4ecfac8b0b7843ee28b8ae33af384ae46b0b53a
|
data/Appraisals
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
-
%w(5.0 5.1 5.2).each do |version|
|
1
|
+
%w(5.0 5.1 5.2 6.0 6.1 7.0).each do |version|
|
2
2
|
appraise "rails-#{version.gsub(/\./, "-")}" do
|
3
3
|
gem "rails", "~> #{version}.0"
|
4
|
+
if Gem::Version.new(version) >= Gem::Version.new("6.0.0")
|
5
|
+
gem "sqlite3", "~> 1.4.0"
|
6
|
+
else
|
7
|
+
gem "sqlite3", "~> 1.3.0"
|
8
|
+
end
|
4
9
|
end
|
5
10
|
end
|
data/README.md
CHANGED
@@ -36,6 +36,8 @@ relation.to_structs(UserPostsSummary) # => array of structs
|
|
36
36
|
|
37
37
|
### From raw SQL
|
38
38
|
|
39
|
+
Note: In order to provide a consistent user experience regardless of the abstraction level used by your code, all of the following methods are available on both `ActiveRecord::Base` and `ActiveRecord::Base.connection`.
|
40
|
+
|
39
41
|
```
|
40
42
|
UserPostsSummary = Struct.new(:user_name, :post_count)
|
41
43
|
sql = <<-eos
|
@@ -99,7 +101,13 @@ For this reason, **all methods added to `ActiveRecord::Base` explicitly disable
|
|
99
101
|
|
100
102
|
1. Fork it ( https://github.com/jcoleman/relation_to_struct/fork )
|
101
103
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
102
|
-
3. Test your changes (`bundle install && appraisal install && rake`)
|
104
|
+
3. Test your changes (`bundle install && bundle exec appraisal install && bundle exec rake`)
|
103
105
|
4. Commit your changes (`git commit -am 'Add some feature'`)
|
104
106
|
5. Push to the branch (`git push origin my-new-feature`)
|
105
107
|
6. Create a new Pull Request
|
108
|
+
|
109
|
+
## Releasing
|
110
|
+
|
111
|
+
1. Bump version in `lib/relation_to_struct/version.rb` and commit.
|
112
|
+
2. Run `rake build` to build the `*.gem` file.
|
113
|
+
3. Run `rake release` to publish the gem to Rubygems.
|
data/gemfiles/rails_5_0.gemfile
CHANGED
data/gemfiles/rails_5_1.gemfile
CHANGED
data/gemfiles/rails_5_2.gemfile
CHANGED
@@ -1,88 +1,18 @@
|
|
1
1
|
module RelationToStruct::ActiveRecordBaseExtension
|
2
|
-
extend ::ActiveSupport::Concern
|
3
|
-
|
4
2
|
module ClassMethods
|
5
3
|
def _sanitize_sql_for_relation_to_struct(sql)
|
6
4
|
sanitized_sql = ActiveRecord::VERSION::MAJOR >= 5 ? sanitize_sql(sql) : sanitize_sql(sql, nil)
|
7
5
|
end
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
if result.columns != struct_class.members.collect(&:to_s)
|
20
|
-
raise ArgumentError, 'Expected column names (and their order) to match struct attribute names'
|
21
|
-
end
|
22
|
-
|
23
|
-
if result.columns.size == 1
|
24
|
-
result.cast_values().map do |tuple|
|
25
|
-
struct_class.new(tuple)
|
26
|
-
end
|
27
|
-
else
|
28
|
-
result.cast_values().map do |tuple|
|
29
|
-
struct_class.new(*tuple)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def pluck_from_sql(sql, binds=[])
|
35
|
-
sanitized_sql = _sanitize_sql_for_relation_to_struct(sql)
|
36
|
-
result = ActiveRecord::Base.uncached do
|
37
|
-
connection.select_all(sanitized_sql, "Pluck SQL Load", binds)
|
38
|
-
end
|
39
|
-
result.cast_values()
|
40
|
-
end
|
41
|
-
|
42
|
-
def value_from_sql(sql, binds=[])
|
43
|
-
sanitized_sql = _sanitize_sql_for_relation_to_struct(sql)
|
44
|
-
result = ActiveRecord::Base.uncached do
|
45
|
-
connection.select_all(sanitized_sql, "Value SQL Load", binds)
|
46
|
-
end
|
47
|
-
raise ArgumentError, 'Expected exactly one column to be selected' unless result.columns.size == 1
|
48
|
-
|
49
|
-
values = result.cast_values()
|
50
|
-
case values.size
|
51
|
-
when 0
|
52
|
-
nil
|
53
|
-
when 1
|
54
|
-
values[0]
|
55
|
-
else
|
56
|
-
raise ArgumentError, 'Expected only a single result to be returned'
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def tuple_from_sql(sql, binds=[])
|
61
|
-
sanitized_sql = _sanitize_sql_for_relation_to_struct(sql)
|
62
|
-
result = ActiveRecord::Base.uncached do
|
63
|
-
connection.select_all(sanitized_sql, "Value SQL Load", binds)
|
64
|
-
end
|
65
|
-
values = result.cast_values()
|
66
|
-
|
67
|
-
case values.size
|
68
|
-
when 0
|
69
|
-
nil
|
70
|
-
when 1
|
71
|
-
result.columns.size == 1 ? values : values[0]
|
72
|
-
else
|
73
|
-
raise ArgumentError, 'Expected only a single result to be returned'
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def run_sql(sql, binds=[])
|
78
|
-
sanitized_sql = _sanitize_sql_for_relation_to_struct(sql)
|
79
|
-
# We don't need to build a result set unnecessarily; using
|
80
|
-
# interface this also ensures we're clearing the result set
|
81
|
-
# for manually memory managed object (e.g., when using the
|
82
|
-
# PostgreSQL adaptor).
|
83
|
-
connection.exec_update(sanitized_sql, "Run SQL", binds)
|
84
|
-
end
|
7
|
+
delegate(
|
8
|
+
:structs_from_sql,
|
9
|
+
:pluck_from_sql,
|
10
|
+
:value_from_sql,
|
11
|
+
:tuple_from_sql,
|
12
|
+
:run_sql,
|
13
|
+
:to => :connection
|
14
|
+
)
|
85
15
|
end
|
86
16
|
end
|
87
17
|
|
88
|
-
::ActiveRecord::Base.send(:
|
18
|
+
::ActiveRecord::Base.singleton_class.send(:prepend, RelationToStruct::ActiveRecordBaseExtension::ClassMethods)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module RelationToStruct::ActiveRecordConnectionAdapterExtension
|
2
|
+
def structs_from_sql(struct_class, sql, binds=[])
|
3
|
+
sanitized_sql = ActiveRecord::Base._sanitize_sql_for_relation_to_struct(sql)
|
4
|
+
result = ActiveRecord::Base.uncached do
|
5
|
+
select_all(sanitized_sql, "Structs SQL Load", binds)
|
6
|
+
end
|
7
|
+
|
8
|
+
if result.columns.size != result.columns.uniq.size
|
9
|
+
raise ArgumentError, 'Expected column names to be unique'
|
10
|
+
end
|
11
|
+
|
12
|
+
if result.columns != struct_class.members.collect(&:to_s)
|
13
|
+
raise ArgumentError, 'Expected column names (and their order) to match struct attribute names'
|
14
|
+
end
|
15
|
+
|
16
|
+
if result.columns.size == 1
|
17
|
+
result.cast_values().map do |tuple|
|
18
|
+
struct_class.new(tuple)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
result.cast_values().map do |tuple|
|
22
|
+
struct_class.new(*tuple)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def pluck_from_sql(sql, binds=[])
|
28
|
+
sanitized_sql = ActiveRecord::Base._sanitize_sql_for_relation_to_struct(sql)
|
29
|
+
result = ActiveRecord::Base.uncached do
|
30
|
+
select_all(sanitized_sql, "Pluck SQL Load", binds)
|
31
|
+
end
|
32
|
+
result.cast_values()
|
33
|
+
end
|
34
|
+
|
35
|
+
def value_from_sql(sql, binds=[])
|
36
|
+
sanitized_sql = ActiveRecord::Base._sanitize_sql_for_relation_to_struct(sql)
|
37
|
+
result = ActiveRecord::Base.uncached do
|
38
|
+
select_all(sanitized_sql, "Value SQL Load", binds)
|
39
|
+
end
|
40
|
+
raise ArgumentError, 'Expected exactly one column to be selected' unless result.columns.size == 1
|
41
|
+
|
42
|
+
values = result.cast_values()
|
43
|
+
case values.size
|
44
|
+
when 0
|
45
|
+
nil
|
46
|
+
when 1
|
47
|
+
values[0]
|
48
|
+
else
|
49
|
+
raise ArgumentError, 'Expected only a single result to be returned'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def tuple_from_sql(sql, binds=[])
|
54
|
+
sanitized_sql = ActiveRecord::Base._sanitize_sql_for_relation_to_struct(sql)
|
55
|
+
result = ActiveRecord::Base.uncached do
|
56
|
+
select_all(sanitized_sql, "Value SQL Load", binds)
|
57
|
+
end
|
58
|
+
values = result.cast_values()
|
59
|
+
|
60
|
+
case values.size
|
61
|
+
when 0
|
62
|
+
nil
|
63
|
+
when 1
|
64
|
+
result.columns.size == 1 ? values : values[0]
|
65
|
+
else
|
66
|
+
raise ArgumentError, 'Expected only a single result to be returned'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def run_sql(sql, binds=[])
|
71
|
+
sanitized_sql = ActiveRecord::Base._sanitize_sql_for_relation_to_struct(sql)
|
72
|
+
# We don't need to build a result set unnecessarily; using
|
73
|
+
# interface this also ensures we're clearing the result set
|
74
|
+
# for manually memory managed object (e.g., when using the
|
75
|
+
# PostgreSQL adaptor).
|
76
|
+
exec_update(sanitized_sql, "Run SQL", binds)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:prepend, RelationToStruct::ActiveRecordConnectionAdapterExtension)
|
@@ -1,6 +1,4 @@
|
|
1
1
|
module RelationToStruct::ActiveRecordRelationExtension
|
2
|
-
extend ::ActiveSupport::Concern
|
3
|
-
|
4
2
|
def to_structs(struct_class)
|
5
3
|
raise ArgumentError, 'Expected select_values to be present' unless self.select_values.present?
|
6
4
|
|
@@ -37,4 +35,4 @@ module RelationToStruct::ActiveRecordRelationExtension
|
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
40
|
-
::ActiveRecord::Relation.send(:
|
38
|
+
::ActiveRecord::Relation.send(:prepend, RelationToStruct::ActiveRecordRelationExtension)
|
data/lib/relation_to_struct.rb
CHANGED
data/relation_to_struct.gemspec
CHANGED
@@ -18,14 +18,14 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_development_dependency "appraisal", "
|
22
|
-
spec.add_development_dependency "bundler", "
|
23
|
-
spec.add_development_dependency "rake", "
|
24
|
-
spec.add_development_dependency "sqlite3", "
|
25
|
-
spec.add_development_dependency "rspec", "
|
21
|
+
spec.add_development_dependency "appraisal", ">= 2.1"
|
22
|
+
spec.add_development_dependency "bundler", ">= 1.7"
|
23
|
+
spec.add_development_dependency "rake", ">= 10.0"
|
24
|
+
spec.add_development_dependency "sqlite3", ">= 1.3"
|
25
|
+
spec.add_development_dependency "rspec", ">= 3.2"
|
26
26
|
spec.add_development_dependency "pry-byebug"
|
27
27
|
spec.add_development_dependency "pg"
|
28
28
|
|
29
|
-
spec.add_dependency "activerecord", ">= 5.0", "<
|
30
|
-
spec.add_dependency "activesupport", ">= 5.0", "<
|
29
|
+
spec.add_dependency "activerecord", ">= 5.0", "< 7.1"
|
30
|
+
spec.add_dependency "activesupport", ">= 5.0", "< 7.1"
|
31
31
|
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveRecord::ConnectionAdapters::AbstractAdapter do
|
4
|
+
before(:each) do
|
5
|
+
Economist.delete_all
|
6
|
+
EconomicSchool.delete_all
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:connection) { ActiveRecord::Base.connection }
|
10
|
+
|
11
|
+
describe "#pluck_from_sql" do
|
12
|
+
it 'allows plucking with SQL directly' do
|
13
|
+
sql = "SELECT 1 * 23"
|
14
|
+
expect(connection.pluck_from_sql(sql)).to eq([23])
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'allows plucking multiple columns with SQL directly' do
|
18
|
+
sql = "SELECT 1 * 23, 25"
|
19
|
+
expect(connection.pluck_from_sql(sql)).to eq([[23, 25]])
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'bypasses the statement cache' do
|
23
|
+
sql = "SELECT random()"
|
24
|
+
value_1 = value_2 = nil
|
25
|
+
|
26
|
+
# Simulate a standard web request in Rails, since
|
27
|
+
# Rails enabled caching by default.
|
28
|
+
ActiveRecord::Base.cache do
|
29
|
+
value_1 = connection.pluck_from_sql(sql)
|
30
|
+
value_2 = connection.pluck_from_sql(sql)
|
31
|
+
end
|
32
|
+
|
33
|
+
expect(value_1).not_to eq(value_2)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#value_from_sql" do
|
38
|
+
it 'allows selecting a value with SQL directly' do
|
39
|
+
sql = "SELECT 1 * 23"
|
40
|
+
expect(connection.value_from_sql(sql)).to eq(23)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'raises an error when multiple rows are selected' do
|
44
|
+
expect do
|
45
|
+
sql = "SELECT * FROM (VALUES (1), (2)) t"
|
46
|
+
connection.value_from_sql(sql)
|
47
|
+
end.to raise_error(ArgumentError, 'Expected only a single result to be returned')
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'raises an error when multiple columns are selected' do
|
51
|
+
expect do
|
52
|
+
sql = "SELECT 1, 2"
|
53
|
+
connection.value_from_sql(sql)
|
54
|
+
end.to raise_error(ArgumentError, 'Expected exactly one column to be selected')
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'supports binds' do
|
58
|
+
sql = ["SELECT 1 * ?", 5]
|
59
|
+
expect(connection.value_from_sql(sql)).to eq(5)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'supports arrays' do
|
63
|
+
if active_record_supports_arrays?
|
64
|
+
Economist.create!(name: 'F.A. Hayek')
|
65
|
+
Economist.create!(name: 'Ludwig von Mises')
|
66
|
+
|
67
|
+
result = connection.value_from_sql('SELECT ARRAY_AGG(name ORDER BY id) AS names FROM economists')
|
68
|
+
expect(result).to eq(['F.A. Hayek', 'Ludwig von Mises'])
|
69
|
+
else
|
70
|
+
skip "DB selection doesn't support ARRAY[]"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'bypasses the statement cache' do
|
75
|
+
sql = "SELECT random()"
|
76
|
+
value_1 = value_2 = nil
|
77
|
+
|
78
|
+
# Simulate a standard web request in Rails, since
|
79
|
+
# Rails enabled caching by default.
|
80
|
+
ActiveRecord::Base.cache do
|
81
|
+
value_1 = connection.value_from_sql(sql)
|
82
|
+
value_2 = connection.value_from_sql(sql)
|
83
|
+
end
|
84
|
+
|
85
|
+
expect(value_1).not_to eq(value_2)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#tuple_from_sql" do
|
90
|
+
it 'allows selecting one value with SQL directly' do
|
91
|
+
sql = "SELECT 1"
|
92
|
+
expect(connection.tuple_from_sql(sql)).to eq([1])
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'allows selecting multiple values with SQL directly' do
|
96
|
+
sql = "SELECT 1, 23"
|
97
|
+
expect(connection.tuple_from_sql(sql)).to eq([1, 23])
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'raises an error when multiple rows are selected' do
|
101
|
+
expect do
|
102
|
+
sql = "SELECT * FROM (VALUES (1, 3), (2, 4)) t"
|
103
|
+
connection.tuple_from_sql(sql)
|
104
|
+
end.to raise_error(ArgumentError, 'Expected only a single result to be returned')
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'supports binds' do
|
108
|
+
sql = ["SELECT ?, ?", 5, 6]
|
109
|
+
expect(connection.tuple_from_sql(sql)).to eq([5, 6])
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'supports a single array' do
|
113
|
+
if active_record_supports_arrays?
|
114
|
+
Economist.create!(name: 'F.A. Hayek')
|
115
|
+
Economist.create!(name: 'Ludwig von Mises')
|
116
|
+
|
117
|
+
result = connection.tuple_from_sql(<<-SQL)
|
118
|
+
SELECT ARRAY_AGG(name ORDER BY id) AS names
|
119
|
+
FROM economists
|
120
|
+
SQL
|
121
|
+
expected_names = ['F.A. Hayek', 'Ludwig von Mises']
|
122
|
+
expect(result).to eq([expected_names])
|
123
|
+
else
|
124
|
+
skip "DB selection doesn't support ARRAY[]"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'supports multiple arrays' do
|
129
|
+
if active_record_supports_arrays?
|
130
|
+
Economist.create!(name: 'F.A. Hayek')
|
131
|
+
Economist.create!(name: 'Ludwig von Mises')
|
132
|
+
|
133
|
+
result = connection.tuple_from_sql(<<-SQL)
|
134
|
+
SELECT
|
135
|
+
ARRAY_AGG(name ORDER BY id) AS names,
|
136
|
+
ARRAY_AGG(CHAR_LENGTH(name) ORDER BY id) AS lengths
|
137
|
+
FROM economists
|
138
|
+
SQL
|
139
|
+
expected_names = ['F.A. Hayek', 'Ludwig von Mises']
|
140
|
+
expect(result).to eq([expected_names, expected_names.map(&:size)])
|
141
|
+
else
|
142
|
+
skip "DB selection doesn't support ARRAY[]"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'bypasses the statement cache' do
|
147
|
+
sql = "SELECT random()"
|
148
|
+
value_1 = value_2 = nil
|
149
|
+
|
150
|
+
# Simulate a standard web request in Rails, since
|
151
|
+
# Rails enabled caching by default.
|
152
|
+
ActiveRecord::Base.cache do
|
153
|
+
value_1 = connection.tuple_from_sql(sql)
|
154
|
+
value_2 = connection.tuple_from_sql(sql)
|
155
|
+
end
|
156
|
+
|
157
|
+
expect(value_1).not_to eq(value_2)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "#structs_from_sql" do
|
162
|
+
it 'ActiveRecord::Base should respond to :structs_from_sql' do
|
163
|
+
expect(connection.respond_to?(:structs_from_sql)).to eq(true)
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'allows querying with SQL directly' do
|
167
|
+
test_struct = Struct.new(:number)
|
168
|
+
sql = "SELECT 1 * 23 AS number"
|
169
|
+
expect(connection.structs_from_sql(test_struct, sql)).to eq([test_struct.new(23)])
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'properly casts a single array column' do
|
173
|
+
if active_record_supports_arrays?
|
174
|
+
Economist.create!(name: 'F.A. Hayek')
|
175
|
+
Economist.create!(name: 'Ludwig von Mises')
|
176
|
+
|
177
|
+
test_struct = Struct.new(:names)
|
178
|
+
structs_results = connection.structs_from_sql(test_struct, 'SELECT ARRAY_AGG(name ORDER BY id) AS names FROM economists')
|
179
|
+
expect(structs_results.first.names).to eq(['F.A. Hayek', 'Ludwig von Mises'])
|
180
|
+
else
|
181
|
+
skip "DB selection doesn't support ARRAY[]"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'raises an error when column count does not match struct size' do
|
186
|
+
expect do
|
187
|
+
test_struct = Struct.new(:id, :name, :extra_field)
|
188
|
+
connection.structs_from_sql(test_struct, 'SELECT id, name FROM economists')
|
189
|
+
end.to raise_error(ArgumentError, 'Expected column names (and their order) to match struct attribute names')
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'raises an error when column names are not unique' do
|
193
|
+
expect do
|
194
|
+
test_struct = Struct.new(:id, :id2)
|
195
|
+
connection.structs_from_sql(test_struct, 'SELECT id, id FROM economists')
|
196
|
+
end.to raise_error(ArgumentError, 'Expected column names to be unique')
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'raises an error when the column names do not match the struct attribute names' do
|
200
|
+
Economist.create!(name: 'F.A. Hayek')
|
201
|
+
expect do
|
202
|
+
test_struct = Struct.new(:value_a, :value_b)
|
203
|
+
connection.structs_from_sql(test_struct, 'SELECT 1 AS value_a, 2 AS value_b FROM economists')
|
204
|
+
end.not_to raise_error
|
205
|
+
|
206
|
+
expect do
|
207
|
+
test_struct = Struct.new(:value_a, :value_b)
|
208
|
+
connection.structs_from_sql(test_struct, 'SELECT 1 AS value_b, 2 AS value_a FROM economists')
|
209
|
+
end.to raise_error(ArgumentError, 'Expected column names (and their order) to match struct attribute names')
|
210
|
+
|
211
|
+
expect do
|
212
|
+
test_struct = Struct.new(:value_a, :value_b)
|
213
|
+
connection.structs_from_sql(test_struct, 'SELECT 1 AS value_a, 2 AS value_c FROM economists')
|
214
|
+
end.to raise_error(ArgumentError, 'Expected column names (and their order) to match struct attribute names')
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'bypasses the statement cache' do
|
218
|
+
test_struct = Struct.new(:r)
|
219
|
+
sql = "SELECT random() AS r"
|
220
|
+
value_1 = value_2 = nil
|
221
|
+
|
222
|
+
# Simulate a standard web request in Rails, since
|
223
|
+
# Rails enabled caching by default.
|
224
|
+
ActiveRecord::Base.cache do
|
225
|
+
value_1 = connection.structs_from_sql(test_struct, sql)
|
226
|
+
value_2 = connection.structs_from_sql(test_struct, sql)
|
227
|
+
end
|
228
|
+
|
229
|
+
expect(value_1).not_to eq(value_2)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe "#run_sql" do
|
234
|
+
it 'executes the provided SQL' do
|
235
|
+
sql = "INSERT INTO economic_schools(name) VALUES ('Chicago')"
|
236
|
+
expect do
|
237
|
+
ActiveRecord::Base.run_sql(sql)
|
238
|
+
end.to change { connection.value_from_sql("SELECT COUNT(*) FROM economic_schools") }.by(1)
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'supports binds' do
|
242
|
+
sql = ["INSERT INTO economic_schools(name) VALUES (?)", "Chicago"]
|
243
|
+
expect do
|
244
|
+
ActiveRecord::Base.run_sql(sql)
|
245
|
+
end.to change { connection.value_from_sql("SELECT COUNT(*) FROM economic_schools") }.by(1)
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'uses the exec_update API to avoid turning things in an ActiveRecord::Result' do
|
249
|
+
expect(ActiveRecord::Base.connection).to receive(:exec_update).exactly(:once)
|
250
|
+
sql = "SELECT 1"
|
251
|
+
connection.run_sql(sql)
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'returns the number of rows modified for an INSERT' do
|
255
|
+
sql = "INSERT INTO economic_schools(name) VALUES ('Chicago'), ('Distributism')"
|
256
|
+
expect(connection.run_sql(sql)).to eq(2)
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'bypasses the statement cache' do
|
260
|
+
# Simulate a standard web request in Rails, since
|
261
|
+
# Rails enabled caching by default.
|
262
|
+
ActiveRecord::Base.cache do
|
263
|
+
expect do
|
264
|
+
connection.run_sql("INSERT INTO economic_schools(name) VALUES ('Chicago')")
|
265
|
+
connection.run_sql("INSERT INTO economic_schools(name) VALUES ('Distributism')")
|
266
|
+
end.to change { connection.value_from_sql("SELECT COUNT(*) FROM economic_schools") }.by(2)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -14,7 +14,12 @@ ActiveRecord::Base.configurations = {
|
|
14
14
|
}
|
15
15
|
|
16
16
|
env = ENV['DATABASE'] ||= 'sqlite'
|
17
|
-
config =
|
17
|
+
config =
|
18
|
+
if ActiveRecord::VERSION::MAJOR < 7
|
19
|
+
ActiveRecord::Base.configurations[env]
|
20
|
+
else
|
21
|
+
ActiveRecord::Base.configurations.configs_for(env_name: env).first
|
22
|
+
end
|
18
23
|
|
19
24
|
case env
|
20
25
|
when 'postgresql'
|
@@ -57,7 +57,7 @@ describe ActiveRecord::Relation do
|
|
57
57
|
it 'properly casts values from arbitrary calculated columns' do
|
58
58
|
hayek = Economist.create!(name: 'F.A. Hayek')
|
59
59
|
scope = Economist.all
|
60
|
-
pluck_results = scope.pluck("date('now')")
|
60
|
+
pluck_results = scope.pluck(Arel.sql("date('now')"))
|
61
61
|
pluck_column_klass = pluck_results.first.class
|
62
62
|
|
63
63
|
date_struct = Struct.new(:date)
|
data/spec/spec_helper.rb
CHANGED
@@ -14,7 +14,7 @@ def active_record_supports_arrays?
|
|
14
14
|
Economist.create!(name: 'F.A. Hayek')
|
15
15
|
Economist.create!(name: 'Ludwig von Mises')
|
16
16
|
|
17
|
-
pluck_results = Economist.select('name').order('id').limit(1).pluck('array[name]')
|
17
|
+
pluck_results = Economist.select('name').order('id').limit(1).pluck(Arel.sql('array[name]')) rescue nil
|
18
18
|
if pluck_results
|
19
19
|
raise StandardError, "Unexpected array query results" unless pluck_results == [['F.A. Hayek']] # Verify ActiveRecord interface.
|
20
20
|
supports_arrays = true
|
metadata
CHANGED
@@ -1,83 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: relation_to_struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Coleman
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: appraisal
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '2.1'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.7'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.7'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '10.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '10.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: sqlite3
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '1.3'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.3'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '3.2'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.2'
|
83
83
|
- !ruby/object:Gem::Dependency
|
@@ -117,7 +117,7 @@ dependencies:
|
|
117
117
|
version: '5.0'
|
118
118
|
- - "<"
|
119
119
|
- !ruby/object:Gem::Version
|
120
|
-
version: '
|
120
|
+
version: '7.1'
|
121
121
|
type: :runtime
|
122
122
|
prerelease: false
|
123
123
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -127,7 +127,7 @@ dependencies:
|
|
127
127
|
version: '5.0'
|
128
128
|
- - "<"
|
129
129
|
- !ruby/object:Gem::Version
|
130
|
-
version: '
|
130
|
+
version: '7.1'
|
131
131
|
- !ruby/object:Gem::Dependency
|
132
132
|
name: activesupport
|
133
133
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,7 +137,7 @@ dependencies:
|
|
137
137
|
version: '5.0'
|
138
138
|
- - "<"
|
139
139
|
- !ruby/object:Gem::Version
|
140
|
-
version: '
|
140
|
+
version: '7.1'
|
141
141
|
type: :runtime
|
142
142
|
prerelease: false
|
143
143
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -147,7 +147,7 @@ dependencies:
|
|
147
147
|
version: '5.0'
|
148
148
|
- - "<"
|
149
149
|
- !ruby/object:Gem::Version
|
150
|
-
version: '
|
150
|
+
version: '7.1'
|
151
151
|
description: ''
|
152
152
|
email:
|
153
153
|
- jtc331@gmail.com
|
@@ -165,12 +165,17 @@ files:
|
|
165
165
|
- gemfiles/rails_5_0.gemfile
|
166
166
|
- gemfiles/rails_5_1.gemfile
|
167
167
|
- gemfiles/rails_5_2.gemfile
|
168
|
+
- gemfiles/rails_6_0.gemfile
|
169
|
+
- gemfiles/rails_6_1.gemfile
|
170
|
+
- gemfiles/rails_7_0.gemfile
|
168
171
|
- lib/relation_to_struct.rb
|
169
172
|
- lib/relation_to_struct/active_record_base_extension.rb
|
173
|
+
- lib/relation_to_struct/active_record_connection_adapter_extension.rb
|
170
174
|
- lib/relation_to_struct/active_record_relation_extension.rb
|
171
175
|
- lib/relation_to_struct/version.rb
|
172
176
|
- relation_to_struct.gemspec
|
173
177
|
- spec/active_record_base_spec.rb
|
178
|
+
- spec/active_record_connection_adapter_spec.rb
|
174
179
|
- spec/active_record_helper/economic_school.rb
|
175
180
|
- spec/active_record_helper/economist.rb
|
176
181
|
- spec/active_record_helper/schema.rb
|
@@ -182,7 +187,7 @@ homepage: ''
|
|
182
187
|
licenses:
|
183
188
|
- MIT
|
184
189
|
metadata: {}
|
185
|
-
post_install_message:
|
190
|
+
post_install_message:
|
186
191
|
rdoc_options: []
|
187
192
|
require_paths:
|
188
193
|
- lib
|
@@ -197,13 +202,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
202
|
- !ruby/object:Gem::Version
|
198
203
|
version: '0'
|
199
204
|
requirements: []
|
200
|
-
|
201
|
-
|
202
|
-
signing_key:
|
205
|
+
rubygems_version: 3.1.6
|
206
|
+
signing_key:
|
203
207
|
specification_version: 4
|
204
208
|
summary: Return struct results from ActiveRecord relation queries
|
205
209
|
test_files:
|
206
210
|
- spec/active_record_base_spec.rb
|
211
|
+
- spec/active_record_connection_adapter_spec.rb
|
207
212
|
- spec/active_record_helper/economic_school.rb
|
208
213
|
- spec/active_record_helper/economist.rb
|
209
214
|
- spec/active_record_helper/schema.rb
|