relation_to_struct 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5589ae299275b2682dcaabc687188d08bb39ce36
4
- data.tar.gz: 519b3a9f0a019ec9473ecd23f791c36f0c4ac831
3
+ metadata.gz: b36da83b17102b8a6f19d1bd7b55ba8486d2bd9b
4
+ data.tar.gz: 3693bc1c95c15877d4e58a66b5700210e4861ff6
5
5
  SHA512:
6
- metadata.gz: 7c112e6889dbb37a656f864e6d799b0df41b35f7a6cead1f0faf16de990233fd3bc379402d3ef3f6011bd0b420c3e9a01b8439da5e7252774192ce11a62d5ca9
7
- data.tar.gz: e08956b50dd9c1835c5ec133a4238f4c0ac5e1e499f0f28e862a651800dbd3ec5336a8aeecb674e527101321255d6ebc2eabe2a977fa30bf31229f6e956f06ec
6
+ metadata.gz: a1a43b34975434afd52f51ddd8ab1f3ba135e976248865a67ffc4ddc7fcfb6d9423c25168f4228c538b6c617d9d5a85559ebeebc99ff460f3a3df368cf973c8b
7
+ data.tar.gz: 7ea17f61be34643cadeaa7377b0ca481ef574d32c2327a8986bf915a4b6d5e0bdae260477547d51f0ee49c197032f1471d8f28d5e25a386fea0906ffa7f27081
data/README.md CHANGED
@@ -69,6 +69,32 @@ eos
69
69
  ActiveRecord::Base.tuple_from_sql(sql) # => [id, name]
70
70
  ```
71
71
 
72
+ ```
73
+ sql = <<-eos
74
+ SELECT 1
75
+ eos
76
+
77
+ ActiveRecord::Base.run_sql(sql) # => <no defined result>
78
+ ```
79
+
80
+ ```
81
+ sql = <<-eos
82
+ INSERT INTO foos(bar) VALUES(1)
83
+ eos
84
+
85
+ ActiveRecord::Base.run_sql(sql) # => <number of rows modified>
86
+ ```
87
+
88
+ ## Project Policy/Philosophy
89
+
90
+ Executing database queries should be clearly explicit in your application code. Implicit queries (e.g., in association accesses) is an anti-pattern that results in problems like N+1 querying.
91
+
92
+ ### Query Caching
93
+
94
+ Query caching is another problem downstream from implicit querying. Because queries are happening "behind the scenes" and there's no obvious place for explicit result caching, it seems desirable to cache at the query level. But this approach applies caching at the wrong level: your application code must still expend all of the effort required to build a SQL query and (potentially) interpret results. Caching queries automatically (as Rails does by default in a web request) can easily lead to gotchas because the framework has no way of determining when caching is actually safe (both from a business logic and query contents perspective).
95
+
96
+ For this reason, **all methods added to `ActiveRecord::Base` explicitly disable query caching**. Rails defaults are respected, however, on extensions to `ActiveRecord::Relation` since it's not as obvious that those queries are intended to be explicit.
97
+
72
98
  ## Contributing
73
99
 
74
100
  1. Fork it ( https://github.com/jcoleman/relation_to_struct/fork )
@@ -8,7 +8,9 @@ module RelationToStruct::ActiveRecordBaseExtension
8
8
 
9
9
  def structs_from_sql(struct_class, sql, binds=[])
10
10
  sanitized_sql = _sanitize_sql_for_relation_to_struct(sql)
11
- result = connection.select_all(sanitized_sql, "Structs SQL Load", binds)
11
+ result = ActiveRecord::Base.uncached do
12
+ connection.select_all(sanitized_sql, "Structs SQL Load", binds)
13
+ end
12
14
 
13
15
  if result.columns.size != result.columns.uniq.size
14
16
  raise ArgumentError, 'Expected column names to be unique'
@@ -31,13 +33,17 @@ module RelationToStruct::ActiveRecordBaseExtension
31
33
 
32
34
  def pluck_from_sql(sql, binds=[])
33
35
  sanitized_sql = _sanitize_sql_for_relation_to_struct(sql)
34
- result = connection.select_all(sanitized_sql, "Pluck SQL Load", binds)
36
+ result = ActiveRecord::Base.uncached do
37
+ connection.select_all(sanitized_sql, "Pluck SQL Load", binds)
38
+ end
35
39
  result.cast_values()
36
40
  end
37
41
 
38
42
  def value_from_sql(sql, binds=[])
39
43
  sanitized_sql = _sanitize_sql_for_relation_to_struct(sql)
40
- result = connection.select_all(sanitized_sql, "Value SQL Load", binds)
44
+ result = ActiveRecord::Base.uncached do
45
+ connection.select_all(sanitized_sql, "Value SQL Load", binds)
46
+ end
41
47
  raise ArgumentError, 'Expected exactly one column to be selected' unless result.columns.size == 1
42
48
 
43
49
  values = result.cast_values()
@@ -53,7 +59,9 @@ module RelationToStruct::ActiveRecordBaseExtension
53
59
 
54
60
  def tuple_from_sql(sql, binds=[])
55
61
  sanitized_sql = _sanitize_sql_for_relation_to_struct(sql)
56
- result = connection.select_all(sanitized_sql, "Value SQL Load", binds)
62
+ result = ActiveRecord::Base.uncached do
63
+ connection.select_all(sanitized_sql, "Value SQL Load", binds)
64
+ end
57
65
  values = result.cast_values()
58
66
 
59
67
  case values.size
@@ -65,6 +73,15 @@ module RelationToStruct::ActiveRecordBaseExtension
65
73
  raise ArgumentError, 'Expected only a single result to be returned'
66
74
  end
67
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
68
85
  end
69
86
  end
70
87
 
@@ -1,3 +1,3 @@
1
1
  module RelationToStruct
2
- VERSION = "1.4.1"
2
+ VERSION = "1.5.0"
3
3
  end
@@ -16,6 +16,20 @@ describe ActiveRecord::Base do
16
16
  sql = "SELECT 1 * 23, 25"
17
17
  expect(ActiveRecord::Base.pluck_from_sql(sql)).to eq([[23, 25]])
18
18
  end
19
+
20
+ it 'bypasses the statement cache' do
21
+ sql = "SELECT random()"
22
+ value_1 = value_2 = nil
23
+
24
+ # Simulate a standard web request in Rails, since
25
+ # Rails enabled caching by default.
26
+ ActiveRecord::Base.cache do
27
+ value_1 = ActiveRecord::Base.pluck_from_sql(sql)
28
+ value_2 = ActiveRecord::Base.pluck_from_sql(sql)
29
+ end
30
+
31
+ expect(value_1).not_to eq(value_2)
32
+ end
19
33
  end
20
34
 
21
35
  describe "#value_from_sql" do
@@ -54,6 +68,20 @@ describe ActiveRecord::Base do
54
68
  skip "DB selection doesn't support ARRAY[]"
55
69
  end
56
70
  end
71
+
72
+ it 'bypasses the statement cache' do
73
+ sql = "SELECT random()"
74
+ value_1 = value_2 = nil
75
+
76
+ # Simulate a standard web request in Rails, since
77
+ # Rails enabled caching by default.
78
+ ActiveRecord::Base.cache do
79
+ value_1 = ActiveRecord::Base.value_from_sql(sql)
80
+ value_2 = ActiveRecord::Base.value_from_sql(sql)
81
+ end
82
+
83
+ expect(value_1).not_to eq(value_2)
84
+ end
57
85
  end
58
86
 
59
87
  describe "#tuple_from_sql" do
@@ -112,6 +140,20 @@ describe ActiveRecord::Base do
112
140
  skip "DB selection doesn't support ARRAY[]"
113
141
  end
114
142
  end
143
+
144
+ it 'bypasses the statement cache' do
145
+ sql = "SELECT random()"
146
+ value_1 = value_2 = nil
147
+
148
+ # Simulate a standard web request in Rails, since
149
+ # Rails enabled caching by default.
150
+ ActiveRecord::Base.cache do
151
+ value_1 = ActiveRecord::Base.tuple_from_sql(sql)
152
+ value_2 = ActiveRecord::Base.tuple_from_sql(sql)
153
+ end
154
+
155
+ expect(value_1).not_to eq(value_2)
156
+ end
115
157
  end
116
158
 
117
159
  describe "#structs_from_sql" do
@@ -169,5 +211,58 @@ describe ActiveRecord::Base do
169
211
  ActiveRecord::Base.structs_from_sql(test_struct, 'SELECT 1 AS value_a, 2 AS value_c FROM economists')
170
212
  end.to raise_error(ArgumentError, 'Expected column names (and their order) to match struct attribute names')
171
213
  end
214
+
215
+ it 'bypasses the statement cache' do
216
+ test_struct = Struct.new(:r)
217
+ sql = "SELECT random() AS r"
218
+ value_1 = value_2 = nil
219
+
220
+ # Simulate a standard web request in Rails, since
221
+ # Rails enabled caching by default.
222
+ ActiveRecord::Base.cache do
223
+ value_1 = ActiveRecord::Base.structs_from_sql(test_struct, sql)
224
+ value_2 = ActiveRecord::Base.structs_from_sql(test_struct, sql)
225
+ end
226
+
227
+ expect(value_1).not_to eq(value_2)
228
+ end
229
+ end
230
+
231
+ describe "#run_sql" do
232
+ it 'executes the provided SQL' do
233
+ sql = "INSERT INTO economic_schools(name) VALUES ('Chicago')"
234
+ expect do
235
+ ActiveRecord::Base.run_sql(sql)
236
+ end.to change { ActiveRecord::Base.value_from_sql("SELECT COUNT(*) FROM economic_schools") }.by(1)
237
+ end
238
+
239
+ it 'supports binds' do
240
+ sql = ["INSERT INTO economic_schools(name) VALUES (?)", "Chicago"]
241
+ expect do
242
+ ActiveRecord::Base.run_sql(sql)
243
+ end.to change { ActiveRecord::Base.value_from_sql("SELECT COUNT(*) FROM economic_schools") }.by(1)
244
+ end
245
+
246
+ it 'uses the exec_update API to avoid turning things in an ActiveRecord::Result' do
247
+ expect(ActiveRecord::Base.connection).to receive(:exec_update).exactly(:once)
248
+ sql = "SELECT 1"
249
+ ActiveRecord::Base.run_sql(sql)
250
+ end
251
+
252
+ it 'returns the number of rows modified for an INSERT' do
253
+ sql = "INSERT INTO economic_schools(name) VALUES ('Chicago'), ('Distributism')"
254
+ expect(ActiveRecord::Base.run_sql(sql)).to eq(2)
255
+ end
256
+
257
+ it 'bypasses the statement cache' do
258
+ # Simulate a standard web request in Rails, since
259
+ # Rails enabled caching by default.
260
+ ActiveRecord::Base.cache do
261
+ expect do
262
+ ActiveRecord::Base.run_sql("INSERT INTO economic_schools(name) VALUES ('Chicago')")
263
+ ActiveRecord::Base.run_sql("INSERT INTO economic_schools(name) VALUES ('Distributism')")
264
+ end.to change { ActiveRecord::Base.value_from_sql("SELECT COUNT(*) FROM economic_schools") }.by(2)
265
+ end
266
+ end
172
267
  end
173
268
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: relation_to_struct
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Coleman