Skip to content

Commit 65b58f8

Browse files
authored
Fix the default array value from being escaped (#58)
The Postgres adapter supports `array: true` for various column types [1]. In #57, it was shown that annotaterb adds escaped string values. [1] https://guides.rubyonrails.org/active_record_postgresql.html#array
1 parent dc95dff commit 65b58f8

File tree

6 files changed

+212
-12
lines changed

6 files changed

+212
-12
lines changed

lib/annotate_rb/model_annotator/column_annotation.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module ColumnAnnotation
77
autoload :TypeBuilder, "annotate_rb/model_annotator/column_annotation/type_builder"
88
autoload :ColumnWrapper, "annotate_rb/model_annotator/column_annotation/column_wrapper"
99
autoload :AnnotationBuilder, "annotate_rb/model_annotator/column_annotation/annotation_builder"
10+
autoload :DefaultValueBuilder, "annotate_rb/model_annotator/column_annotation/default_value_builder"
1011
end
1112
end
1213
end

lib/annotate_rb/model_annotator/column_annotation/column_wrapper.rb

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,7 @@ def name
8888

8989
# Simple quoting for the default column value
9090
def quote(value)
91-
case value
92-
when NilClass then "NULL"
93-
when TrueClass then "TRUE"
94-
when FalseClass then "FALSE"
95-
when Float, Integer then value.to_s
96-
# BigDecimals need to be output in a non-normalized form and quoted.
97-
when BigDecimal then value.to_s("F")
98-
when Array then value.map { |v| quote(v) }
99-
else
100-
value.inspect
101-
end
91+
DefaultValueBuilder.new(value).build
10292
end
10393
end
10494
end
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# frozen_string_literal: true
2+
3+
module AnnotateRb
4+
module ModelAnnotator
5+
module ColumnAnnotation
6+
class DefaultValueBuilder
7+
def initialize(value)
8+
@value = value
9+
end
10+
11+
# @return [String]
12+
# Returns the value to get written to file by file.puts. Strings get written to file so escaped quoted strings
13+
# get written as quoted. For example, if `value: "\"some_string\""` then "some_string" gets written.
14+
# Same with arrays, if `value: "[\"a\", \"b\", \"c\"]"` then `["a", "b", "c"]` gets written.
15+
#
16+
# @example "\"some_string\""
17+
# @example "NULL"
18+
# @example "1.2"
19+
def build
20+
if @value.is_a?(Array)
21+
quote_array(@value)
22+
else
23+
quote(@value)
24+
end
25+
end
26+
27+
private
28+
29+
def quote(value)
30+
case value
31+
when NilClass then "NULL"
32+
when TrueClass then "TRUE"
33+
when FalseClass then "FALSE"
34+
when Float, Integer then value.to_s
35+
# BigDecimals need to be output in a non-normalized form and quoted.
36+
when BigDecimal then value.to_s("F")
37+
when String then value.inspect
38+
else
39+
value.to_s
40+
end
41+
end
42+
43+
def quote_array(value)
44+
content = value.map { |v| quote(v) }.join(", ")
45+
"[" + content + "]"
46+
end
47+
end
48+
end
49+
end
50+
end

spec/lib/annotate_rb/model_annotator/column_annotation/annotation_builder_spec.rb

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,78 @@
5454
end
5555
end
5656

57+
context "when the column is string column and an array (postgres)" do
58+
let(:column) { mock_column("notifications", :string, default: "{}", array: true, null: true) }
59+
let(:model) do
60+
instance_double(
61+
AnnotateRb::ModelAnnotator::ModelWrapper,
62+
primary_key: "something_else",
63+
retrieve_indexes_from_table: [],
64+
with_comments?: false,
65+
column_defaults: {
66+
"notifications" => []
67+
}
68+
)
69+
end
70+
let(:expected_result) do
71+
<<~COLUMN
72+
# notifications :string default([]), is an Array
73+
COLUMN
74+
end
75+
76+
it "returns the column annotation" do
77+
is_expected.to eq(expected_result)
78+
end
79+
end
80+
81+
context "when the column is string column and an array with a default (postgres)" do
82+
let(:column) { mock_column("notifications", :string, default: "{}", array: true, null: true) }
83+
let(:model) do
84+
instance_double(
85+
AnnotateRb::ModelAnnotator::ModelWrapper,
86+
primary_key: "something_else",
87+
retrieve_indexes_from_table: [],
88+
with_comments?: false,
89+
column_defaults: {
90+
"notifications" => ["something"]
91+
}
92+
)
93+
end
94+
let(:expected_result) do
95+
<<~COLUMN
96+
# notifications :string default(["something"]), is an Array
97+
COLUMN
98+
end
99+
100+
it "returns the column annotation" do
101+
is_expected.to eq(expected_result)
102+
end
103+
end
104+
105+
context "when the column is a string column with a default" do
106+
let(:column) { mock_column("notifications", :string, default: "alert", null: true) }
107+
let(:model) do
108+
instance_double(
109+
AnnotateRb::ModelAnnotator::ModelWrapper,
110+
primary_key: "something_else",
111+
retrieve_indexes_from_table: [],
112+
with_comments?: false,
113+
column_defaults: {
114+
"notifications" => "alert"
115+
}
116+
)
117+
end
118+
let(:expected_result) do
119+
<<~COLUMN
120+
# notifications :string default("alert")
121+
COLUMN
122+
end
123+
124+
it "returns the column annotation" do
125+
is_expected.to eq(expected_result)
126+
end
127+
end
128+
57129
context "when the column has a comment" do
58130
let(:max_size) { 20 }
59131

spec/lib/annotate_rb/model_annotator/column_annotation/column_wrapper_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
context "when the value is an array" do
6363
let(:value) { [BigDecimal("1.2")] }
6464
it "returns an array of which elements are converted to string" do
65-
is_expected.to eq(["1.2"])
65+
is_expected.to eq("[1.2]")
6666
end
6767
end
6868
end
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe AnnotateRb::ModelAnnotator::ColumnAnnotation::DefaultValueBuilder do
4+
describe "#build" do
5+
subject { described_class.new(value).build }
6+
7+
context "when value is a String" do
8+
let(:value) { "a random string" }
9+
10+
it { is_expected.to eq("\"a random string\"") }
11+
end
12+
13+
context "when value is nil" do
14+
let(:value) { nil }
15+
16+
it { is_expected.to eq("NULL") }
17+
end
18+
19+
context "when value is true" do
20+
let(:value) { true }
21+
22+
it { is_expected.to eq("TRUE") }
23+
end
24+
25+
context "when value is false" do
26+
let(:value) { false }
27+
28+
it { is_expected.to eq("FALSE") }
29+
end
30+
31+
context "when value is an Integer" do
32+
let(:value) { 42 }
33+
34+
it { is_expected.to eq("42") }
35+
end
36+
37+
context "when value is an Decimal" do
38+
let(:value) { 1.2 }
39+
40+
it { is_expected.to eq("1.2") }
41+
end
42+
43+
context "when value is a BigDecimal" do
44+
let(:value) { BigDecimal("1.2") }
45+
46+
it { is_expected.to eq("1.2") }
47+
end
48+
49+
context "when value is an Array" do
50+
context "array is empty" do
51+
let(:value) { [] }
52+
53+
it { is_expected.to eq("[]") }
54+
end
55+
56+
context "array has a String" do
57+
let(:value) { ["string"] }
58+
59+
it { is_expected.to eq("[\"string\"]") }
60+
end
61+
62+
context "array has Strings" do
63+
let(:value) { ["a", "string"] }
64+
65+
it { is_expected.to eq("[\"a\", \"string\"]") }
66+
end
67+
68+
context "array has Numbers" do
69+
let(:value) { [42, 1.2] }
70+
71+
it { is_expected.to eq("[42, 1.2]") }
72+
end
73+
74+
context "array has BigDecimals" do
75+
let(:value) { [BigDecimal("0.1"), BigDecimal("0.2")] }
76+
77+
it { is_expected.to eq("[0.1, 0.2]") }
78+
end
79+
80+
context "array has Booleans" do
81+
let(:value) { [true, false] }
82+
83+
it { is_expected.to eq("[TRUE, FALSE]") }
84+
end
85+
end
86+
end
87+
end

0 commit comments

Comments
 (0)