Skip to content

Commit

Permalink
Merge pull request #9 from otorain/feat/0.6.0
Browse files Browse the repository at this point in the history
Add :comment_only option; Add the Inspectable module for more concise usage; Code refactor
  • Loading branch information
otorain authored Aug 27, 2023
2 parents a009a7b + 6751f5f commit 00378b5
Show file tree
Hide file tree
Showing 26 changed files with 315 additions and 134 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ jobs:
bundler-cache: true
- name: Install dependencies
run: bundle install
- name: Run test
- name: Running test
run: bundle exec rspec
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
*.gem
*.lock
.ruby-version
coverage
5 changes: 3 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ gemspec

group :test do
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
gem "rspec"
gem "rspec-rails"
gem 'simplecov', require: false, group: :test
end

gem "sqlite3"
gem "rspec"
gem "rspec-rails"


# Start debugger with binding.b [https://github.com/ruby/debug]
Expand Down
85 changes: 70 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
# TableInspector
This is a Rails gem that allows you to print the definition of a table. While some gems (like [annotate_models](https://github.com/ctran/annotate_models)) are used to embed the table schema in the model file, they can be difficult to maintain and add unnecessary noise. This gem provides an alternative way to check the schema of a table without using `annotate_models`.
![Test coverage](https://img.shields.io/badge/Test_coverage-99.65-green)
![Release version](https://img.shields.io/badge/Release-v0.6.0-green)
![Workflow badge](https://github.com/otorain/table_inspector/actions/workflows/run_test.yml/badge.svg)

This is a Rails gem that allows you to print the structure of a database table by providing a model class.
It functions similar to [annotate_models](https://github.com/ctran/annotate_models), but offers an alternative way to checking the table schema.
The print function is based on [terminal-table](https://github.com/tj/terminal-table)

This doc is about version 0.6.0. For version 0.5.0, please see [here](https://github.com/otorain/table_inspector/tree/v0.5.5)

## Installation
Add this line to your application's Gemfile:

```ruby
gem "table_inspector", "~> 0.6.0"
```

And then execute:
```bash
$ bundle
```

Or install it yourself as:
```bash
$ gem install table_inspector
```

## Usage
Assuming there is a model call `User` which has `id` and `name` column, and has a unique index for `name`.
For print the definition of User, we can use:
For print the table structure of User, we can use:
```ruby
require "table_inspector"

Expand All @@ -12,7 +37,9 @@ TableInspector.scan User

![TableInspect scan table](/img/table_inspector_scan_table_3.png)

It will print the table definition and all indexes.
It will print the scheme of table and indexes.
(If the table content is too long, It may be printed messy. You can adjust the scaling of the terminal window to fix it.
Alternatively, you can use `TableInspector.scan(User, :name)` to print a specific column)

Or you can use `TableInspector.ascan` to print more colorful table(`ascan` means `awesome scan`) :
```ruby
Expand All @@ -29,29 +56,57 @@ TableInspector.scan User, :name

It will print the column definition and which indexes that contains this column.

You can print SQL type of column in the database by providing the `sql_type: true` option:
**It is recommended to include `TableInspector::Inspectable` in `app/models/application_record.rb` and use `ti` or `ati` method to print the table definition:**
```ruby
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true

# Add this line
include TableInspector::Inspectable

# ...
end
```
and call:
```ruby
# Same as TableInspector.scan User
User.ti

# Same as TableInspector.ascan User
User.ati
```
The module `TableInspector::Inspectable` only defines two class methods: `ti` and `ati`, which delegate to `TableInspector.scan` and `TableInspector.ascan` respectively.

You can print the database column type by providing the `sql_type: true` option:
```ruby
User.ti sql_type: true
# or
TableInspector.scan User, sql_type: true
```
![Table Inspector scan table column with sql type](/img/table_inspector_scan_table_with_sql_type_3.png)

## Installation
Add this line to your application's Gemfile:

To print comment column only for the table, use `comment_only: true` option:
```ruby
gem "table_inspector"
User.ti comment_only: true
# or
TableInspector.scan User, comment_only: true
```
![Table Inspector scan table comment only](/img/table_inspector_scan_table_comment_only.png)
If the `sql_type: true` option is also provided, the sql_type option will be omitted.

And then execute:
```bash
$ bundle
```
## Style
To change the style of the table by setting the `TableInspector.style` in `config/initializers/table_inspector.rb`(create it if not exists):
```ruby
# config/initializers/table_inspector.rb

Or install it yourself as:
```bash
$ gem install table_inspector
TableInspector.style = { border: :unicode }
# TableInspector.style = { border: :ascii } # default border style
# TableInspector.style = { border: :unicode_round }
# TableInspector.style = { border: :unicode_thick_edge }
# TableInspector.style = { border: :markdown }
```
You can use other options available in `TerminalTable#style` of the [terminal-table](https://github.com/tj/terminal-table)

## Contributing
Contribution directions go here.
Expand Down
Binary file added img/table_inspector_scan_table_comment_only.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 26 additions & 28 deletions lib/table_inspector.rb
Original file line number Diff line number Diff line change
@@ -1,70 +1,68 @@
require "table_inspector/version"
require "table_inspector/railtie"
require "terminal-table"
require "table_inspector/text"
require "table_inspector/table"
require "table_inspector/grid"
require "table_inspector/terminal_table"
require "table_inspector/indexes"
require "table_inspector/text"
require "table_inspector/column"
require "table_inspector/presenter"
require "table_inspector/model_validator"
require "table_inspector/column_validator"
require "table_inspector/presenter_option"
require "table_inspector/inspectable"

module TableInspector
extend self
mattr_accessor :style

def ascan(klass, column_name = nil, sql_type: false)
klass = classify!(klass)

return unless klass
return unless validate!(klass, column_name)

render(klass, column_name, sql_type, colorize: true)
# options:
# - sql_type: pass "true" print sql type, pass "false" not print sql type, default is false
# - comment_only: pass "true" print comment only, pass "false" will print all column info, default is false
def ascan(klass, column_name = nil, **options)
scan(klass, column_name, colorize: true, **options)
end

def scan(klass, column_name = nil, sql_type: false)
# options:
# - sql_type: pass "true" print sql type, pass "false" not print sql type, default is false
# - comment_only: pass "true" print comment only, pass "false" will print all column info, default is false
# - colorize: pass "true" print colorful scheme, pass "false" will print scheme without color, default is false
def scan(klass, column_name = nil, colorize: false, **options)
klass = classify!(klass)

return unless klass
return unless validate!(klass, column_name)

render(klass, column_name, sql_type)
presenter_options = PresenterOption.new({ **options, colorize: colorize })
render(klass, column_name, presenter_options)
end

private

def classify!(klass)
begin
unless klass.is_a?(Class)
klass = klass.to_s.classify.constantize
end
rescue NameError
puts invalid_model_name_hint(klass.inspect)
return
end

klass
return klass if klass.is_a?(Class)
klass.to_s.classify.constantize
rescue NameError
puts invalid_model_name_hint(klass.inspect)
end

def validate!(klass, column_name)
model_validator = ModelValidator.new(klass)
unless column_name
return model_validator.validate!
end
return model_validator.validate! unless column_name

column_validator = ColumnValidator.new(klass, column_name)
model_validator.validate! && column_validator.validate!
end

def render(klass, column_name, sql_type, colorize: false)
def render(klass, column_name, presenter_option)
if column_name
Column.new(klass, column_name, sql_type: sql_type, colorize: colorize).render
Column.new(klass, column_name, presenter_option).render
else
Table.new(klass, sql_type: sql_type, colorize: colorize).render
Table.new(klass, presenter_option).render
end
end

def invalid_model_name_hint(klass)
"'#{klass}' can be transform to a model class."
"'#{klass}' cannot be transformed to a valid model class."
end
end
33 changes: 19 additions & 14 deletions lib/table_inspector/column.rb
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@

module TableInspector
class Column
attr_reader :column, :klass, :sql_type, :presenter
attr_reader :column, :klass, :presenter
delegate :break_line, :bold, to: TableInspector::Text

def initialize(klass, column_name, sql_type: false, colorize: false )
@column = klass.columns.find {|column| column.name == column_name.to_s}
def initialize(klass, column_name, presenter_option)
@column = klass.columns.find { |column| column.name == column_name.to_s }
@klass = klass
@sql_type = sql_type
@colorize = colorize
@presenter = Presenter.new(klass, sql_type: sql_type, colorize: colorize)
@presenter = Presenter.new(klass, presenter_option)
end

def render
Text.break_line
break_line # empty line
render_title
render_body
Text.break_line
Indexes.new(klass, column.name, colorize: @colorize).render
Text.break_line
break_line # empty line
render_indexes
break_line # empty line
end

private

def render_title
Grid.new do |grid|
grid.add_row(["#{Text.bold('Table')}: #{klass.table_name}", "#{Text.bold('Column')}: #{column.name}"])
TerminalTable.new do |terminal_table|
table_name = bold('Table') + ": " + klass.table_name
column_name = bold('Column') + ": " + column.name
terminal_table.add_row([table_name, column_name])
end.render
end

def render_body
Grid.new(headings: presenter.headings) do |grid|
grid.add_row(@presenter.extract_meta(column).values)
TerminalTable.new(headings: presenter.headings) do |terminal_table|
terminal_table.add_row(@presenter.extract_meta(column).values)
end.render
end

def render_indexes
Indexes.new(klass, column.name, colorize: presenter.option.colorize).render
end
end
end
2 changes: 1 addition & 1 deletion lib/table_inspector/column_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def column_exists?
end

def column_is_not_exists_hint
puts "Column '#{column_name}' doesn't exists in table '#{klass.table_name}'!"
puts "Column '#{column_name}' doesn't exist in table '#{klass.table_name}'!"
end
end
end
43 changes: 24 additions & 19 deletions lib/table_inspector/indexes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def render
render_title

if column_name
render_indexes_with_specific_column
render_indexes_of_column
else
render_indexes
end
Expand All @@ -22,48 +22,53 @@ def render
private

def render_title
Grid.new do |grid|
grid.add_row([Text.bold("Indexes")])
TerminalTable.new do |terminal_table|
terminal_table.add_row([Text.bold("Indexes")])
end.render
end

def render_indexes_with_specific_column
Grid.new(headings: headings) do |grid|
indexes_with_specific_column.each do |index|
grid.add_row(compose_index_data(index))
def render_indexes_of_column
TerminalTable.new(headings: headings) do |terminal_table|
indexes_of_column.each do |index|
terminal_table.add_row(compose_index_data(index))
end
end.render
end

def render_indexes
Grid.new(headings: headings) do |grid|
TerminalTable.new(headings: headings) do |terminal_table|
indexes.each do |index|
grid.add_row(compose_index_data(index))
terminal_table.add_row(compose_index_data(index))
end
end.render
end

def compose_index_data(index)
if @colorize
compose_index_data_with_highlight(index)
compose_index_data_with_color(index)
else
compose_index_data_without_highlight(index)
compose_index_data_without_color(index)
end
end

def compose_index_data_with_highlight(index)
def compose_index_data_with_color(index)
index_columns_text = index.columns.join(', ')
unique_text = index.unique ? "UNIQUE" : ""

[
index.name,
"[#{Text.yellow(index.columns.join(', '))}]",
index.unique ? "UNIQUE" : ""
"[#{Text.yellow(index_columns_text)}]",
unique_text
]
end

def compose_index_data_without_highlight(index)
def compose_index_data_without_color(index)
index_columns_text = index.columns.join(', ')
unique_text = index.unique ? "UNIQUE" : ""
[
index.name,
"[#{index.columns.join(', ')}]",
index.unique ? "UNIQUE" : ""
"[#{index_columns_text}]",
unique_text
]
end

Expand All @@ -75,8 +80,8 @@ def indexes
@indexes ||= connection.indexes(klass.table_name)
end

def indexes_with_specific_column
indexes.select{|index| index.columns.include?(column_name.to_s) }
def indexes_of_column
indexes.select{ |index| index.columns.include?(column_name.to_s) }
end

def connection
Expand Down
Loading

0 comments on commit 00378b5

Please sign in to comment.