Skip to content

Commit

Permalink
Add support for virtual columns (#163)
Browse files Browse the repository at this point in the history
One of these changes was support for [virtual/generated
columns](https://guides.rubyonrails.org/active_record_postgresql.html#generated-columns).
At the moment, the following table would create a slightly confusing
annotation
```ruby
create_table :users do |t|
  t.string :name
  t.virtual :name_upcased, type: :string, as: "upper(name)", stored: true
end
```

```ruby
# Table name: users
#
#  name                :string
#  name_upcased :string
class User < ApplicationRecord
```

With these changes, this would generate the following:
```ruby
# Table name: users
#
#  name                :string
#  name_upcased :virtual(string)
class User < ApplicationRecord
```

I wasn't a 100% sure where you'd want these changes exactly. I'm happy
to make further changes, or add some more tests.

One additional change could be to also place the function in the
annotation, but this would maybe better in a further PR. I've tried this
in the past and noticed that it can become unwieldy once the function
becomes more complicated
```ruby
# Table name: users
#
#  name.                :string
#  name_upcased :virtual(string)    upcase(name)
class User < ApplicationRecord
```

---------

Co-authored-by: Andrew W. Lee <[email protected]>
  • Loading branch information
robbevp and drwl authored Feb 17, 2025
1 parent 8d805c6 commit a66bc10
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ def build
end
end

# Check if the column is a virtual column and print the function
if @options[:show_virtual_columns] && @column.virtual?
# Any whitespace in the function gets reduced to a single space
attrs << @column.default_function.gsub(/\s+/, " ").strip
end

attrs
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ def name
@column.name
end

def virtual?
@column.respond_to?(:virtual?) && @column.virtual?
end

def default_function
@column.default_function
end

private

# Simple quoting for the default column value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def build

if is_decimal_type
formatted_column_type = "decimal(#{@column.precision}, #{@column.scale})"
elsif @options[:show_virtual_columns] && @column.virtual?
formatted_column_type = "virtual(#{column_type})"
elsif is_special_type
# Do nothing. Kept as a code fragment in case we need to do something here.
elsif @column.limit && !@options[:format_yard]
Expand Down
1 change: 1 addition & 0 deletions lib/annotate_rb/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def from(options = {}, state = {})
show_check_constraints: false, # ModelAnnotator
show_foreign_keys: true, # ModelAnnotator
show_indexes: true, # ModelAnnotator
show_virtual_columns: false, # ModelAnnotator
simple_indexes: false, # ModelAnnotator
sort: false, # ModelAnnotator
timestamp: false, # RouteAnnotator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,26 @@
it { is_expected.to match_array(expected_result) }
end

context "when the show_virtual_columns option is false with a virtual column" do
let(:column) { mock_column("name", :string, virtual?: true, default_function: "first_name || ' ' || last_name") }
let(:options) do
AnnotateRb::Options.new({show_virtual_columns: false})
end
let(:expected_result) { ["not null"] }

it { is_expected.to match_array(expected_result) }
end

context "when the show_virtual_columns option is true with a virtual column" do
let(:column) { mock_column("name", :string, virtual?: true, default_function: "first_name || ' ' || last_name") }
let(:options) do
AnnotateRb::Options.new({show_virtual_columns: true})
end
let(:expected_result) { ["first_name || ' ' || last_name", "not null"] }

it { is_expected.to match_array(expected_result) }
end

context "column defaults in sqlite" do
# Mocked representations when using the sqlite adapter
context "with an integer default value of 0" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,28 @@
it { is_expected.to eq(expected_result) }
end

context "with a virtual column without option enabled" do
context "when show_virtual_columns if false" do
let(:column) { mock_column("name", :string, virtual?: true) }
let(:options) do
AnnotateRb::Options.new({show_virtual_columns: false})
end
let(:expected_result) { "string" }

it { is_expected.to eq(expected_result) }
end

context "when show_virtual_columns if true" do
let(:column) { mock_column("name", :string, virtual?: true) }
let(:options) do
AnnotateRb::Options.new({show_virtual_columns: true})
end
let(:expected_result) { "virtual(string)" }

it { is_expected.to eq(expected_result) }
end
end

context 'when "format_yard" is specified in options' do
context "with a string column with a limit" do
let(:column) { mock_column("name", :string, limit: 50) }
Expand Down

0 comments on commit a66bc10

Please sign in to comment.