Last active
January 12, 2016 22:42
-
-
Save jacaetevha/8660646 to your computer and use it in GitHub Desktop.
Prints (roughly) 5x7 cards on a standard 8.5x11 sheet of paper with the names of tables at the top, and columns of column data. The column data printed is just the name and its type. Column types have been abbreviated due to space constraints.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This script creates blocks of text on 8.5x11 paper that is suitable for | |
# cutting and pasting onto 5x7 index cards. The blocks of text are descriptions | |
# of your Rails database tables. Specifically, it prints out a representation | |
# of the column type next to the name of a column for each table. | |
# | |
# It attempts to layout cards smartly so that large tables are handled with | |
# some gracefulness. For instance, this script was tested with tables that | |
# have as few as 3 columns and tables up to 134 columns. If a table has more | |
# columns in it than can be easily represented on one 5x7 index card, it will | |
# use a full 8.5x11 page. | |
# | |
# Run this file with the "rails runner" command so that it has access to your | |
# database. | |
# | |
# rails runner location/to/database_cards.rb | |
require "rubygems" | |
require "prawn" | |
require "bundler/setup" | |
module Card | |
TYPES = { | |
"integer" => "INT", | |
"float" => "FLT", | |
"datetime" => "D/T", | |
"date" => "DATE", | |
"boolean" => "BOOL", | |
"string" => "STR", | |
"text" => "TXT", | |
"int4range" => "RNG", | |
"hstore" => "HSTR" | |
} | |
COMFORTABLE_ROW_COUNT = 23 | |
TIGHT_ROW_COUNT = 28 | |
SPACEY_ROW_COUNT = 19 | |
CARD_WIDTH = 72 * 7 # 7 inches | |
CARD_HEIGHT = 72 * 5 # 5 inches | |
class AbstractCard | |
attr_reader :table_name, :columns, :body | |
def initialize(table_name, columns) | |
@types_size = TYPES.values.max_by(&:size).size | |
@table_name = table_name | |
@columns = columns_as_string columns | |
@body = @columns.join("\n") | |
end | |
def boxes_per_page | |
height > CARD_HEIGHT ? 2 : 1 | |
end | |
def db_column_count | |
@columns.size | |
end | |
def draw_on(doc, width=doc.bounds.width) | |
doc.column_box([0, doc.cursor], columns: card_columns, width: width, height: height - 50) do | |
doc.text body, size: font_size, align: :left, leading: 0, inline_format: true | |
end | |
end | |
def height | |
if @columns.size > (TIGHT_ROW_COUNT * 4) | |
CARD_HEIGHT * 2 | |
else | |
CARD_HEIGHT | |
end | |
end | |
def title | |
"#{table_name.upcase} <sup>(#{db_column_count})</sup>" | |
end | |
def width | |
CARD_WIDTH | |
end | |
protected | |
def max_db_column_name_size | |
@columns.max_by(&:size).size | |
end | |
private | |
def columns_as_string(columns) | |
columns.map do |column| | |
type = (TYPES[column.type.to_s] || "??#{column.type}").ljust(@types_size, " ") | |
"<font name='Courier'><strong><sup><color rgb='AB66FF'>#{type} </color></sup></strong></font>#{column.name}" | |
end | |
end | |
end | |
class OneColumnCard < AbstractCard | |
def card_columns | |
1 | |
end | |
def font_size | |
max_db_column_name_size > 28 ? 10 : 12 | |
end | |
end | |
class TwoColumnCard < OneColumnCard | |
def card_columns | |
2 | |
end | |
end | |
class ThreeColumnCard < AbstractCard | |
def card_columns | |
3 | |
end | |
def font_size | |
10 | |
end | |
end | |
class ManyColumnCard < AbstractCard | |
ROW_COUNT_FOR_3_COLS = 51 | |
def card_columns | |
db_column_count > (ROW_COUNT_FOR_3_COLS * 3) ? 4 : 3 | |
end | |
def font_size | |
(card_columns == 4) ? (max_db_column_name_size > 27 ? 6 : 8) : 10 | |
end | |
end | |
def self.create(table_name, columns) | |
if columns.size <= SPACEY_ROW_COUNT | |
OneColumnCard.new table_name, columns | |
elsif columns.size <= COMFORTABLE_ROW_COUNT * 2 | |
TwoColumnCard.new table_name, columns | |
elsif columns.size <= COMFORTABLE_ROW_COUNT * 3 | |
ThreeColumnCard.new table_name, columns | |
else | |
ManyColumnCard.new table_name, columns | |
end | |
end | |
end | |
class Prawn::Document | |
SPACE_BETWEEN_COLUMN_NAMES = 2 | |
def self.generate_cards(outfile, cards) | |
generate(outfile, page_layout: :portrait) do | |
row = 2 | |
cards.each do |card| | |
if row == 0 | |
start_new_page | |
row = 2 | |
end | |
row -= draw_card(card, row) | |
end | |
end | |
end | |
def margin_box(margin, &block) | |
bounding_box [bounds.left + margin, bounds.top - margin], | |
width: bounds.width - (margin * 2), height: bounds.height - (margin * 2), | |
&block | |
end | |
def outline_box | |
stroke_rectangle bounds.top_left, bounds.width, bounds.height | |
end | |
def draw_card(card, row) | |
y_offset, height = if card.boxes_per_page > 1 | |
[bounds.height, card.height] | |
else | |
# here, we offset the height by a little bit in order to separate the bounding boxes | |
[(card.height * row + ((bounds.height - (2 * card.height))/2)), card.height - 10] | |
end | |
bounding_box [0, y_offset], width: card.width, height: height do | |
stroke_color '000000' | |
outline_box | |
margin_box 8 do | |
font "Helvetica" | |
text card.title, size: 18, inline_format: true | |
move_down 2 | |
stroke do | |
stroke_color '666666' | |
horizontal_rule | |
end | |
margin_box 12 do | |
font "Times-Roman" | |
move_down 20 | |
card.draw_on self | |
end | |
end | |
end | |
card.boxes_per_page | |
end | |
end | |
def run | |
connection = ActiveRecord::Base.connection | |
cards = (connection.tables - ['schema_migrations']).sort!.map! do |table_name| | |
Card.create table_name, connection.columns(table_name) | |
end | |
file = ARGV[0] || 'tmp/cards.pdf' | |
Prawn::Document.generate_cards(file, cards) | |
puts "cards written to #{file}" | |
end | |
run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment