sequel-pg-comment 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.yardopts +1 -0
- data/Gemfile +3 -0
- data/Guardfile +15 -0
- data/LICENCE +674 -0
- data/README.md +209 -0
- data/Rakefile +39 -0
- data/lib/sequel/extensions/pg_comment.rb +73 -0
- data/lib/sequel/extensions/pg_comment/alter_table_generator_methods.rb +126 -0
- data/lib/sequel/extensions/pg_comment/create_table_generator_methods.rb +159 -0
- data/lib/sequel/extensions/pg_comment/database_methods.rb +175 -0
- data/lib/sequel/extensions/pg_comment/dataset_methods.rb +22 -0
- data/lib/sequel/extensions/pg_comment/sql_generator.rb +257 -0
- data/sequel-pg-comment.gemspec +36 -0
- data/spec/alter_table_comment_spec.rb +124 -0
- data/spec/comment_for_spec.rb +34 -0
- data/spec/comment_on_spec.rb +56 -0
- data/spec/create_join_table_comment_spec.rb +20 -0
- data/spec/create_table_comment_spec.rb +193 -0
- data/spec/create_view_comment_spec.rb +18 -0
- data/spec/extension_spec.rb +11 -0
- data/spec/normalise_comment_spec.rb +48 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/sql_generator_spec.rb +96 -0
- metadata +242 -0
data/README.md
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
Databases are the ugly step-child of documentation. Explaining what each
|
2
|
+
column and table is actually *for* suffers from the "[documentation vicious
|
3
|
+
circle](http://www.hezmatt.org/~mpalmer/blog/2015/02/15/the-documentation-vicious-circle.html)"
|
4
|
+
-- nobody writes documentation, because nobody reads it, because it never
|
5
|
+
exists, therefore there's no point even looking for it. Unlike source code,
|
6
|
+
which allows comments to exist alongside the live-edited code, there's
|
7
|
+
typically *no way* to keep documentation at the "point of use" of an SQL
|
8
|
+
database. Sure, you can write docs in your migrations, or a wiki somewhere,
|
9
|
+
but when you're banging away at your SQL command line, who wants to go
|
10
|
+
rummaging around in a wiki?
|
11
|
+
|
12
|
+
As with all things, PostgreSQL to the rescue! The non-standard [`COMMENT`
|
13
|
+
command](http://www.postgresql.org/docs/current/interactive/sql-comment.html)
|
14
|
+
allows you to attach an arbitrary chunk of text to pretty much any object in
|
15
|
+
the database. Want to document a collation? `COMMENT ON COLLATION
|
16
|
+
<object_name> IS 'something something dark side'` and you're done!
|
17
|
+
|
18
|
+
If you're a lover of [Sequel](http://sequel.jeremyevans.net/), though, the
|
19
|
+
last thing you want to be doing is hand-writing SQL. Blech. That's old
|
20
|
+
school. You want to have your comments Right There in the migrations.
|
21
|
+
That's what this gem is all about.
|
22
|
+
|
23
|
+
|
24
|
+
# Usage
|
25
|
+
|
26
|
+
First, you need to enable the plugin:
|
27
|
+
|
28
|
+
Sequel::Database.extension :pg_comment
|
29
|
+
|
30
|
+
Then, you can attach a comment to anything you can create with [Sequel
|
31
|
+
schema
|
32
|
+
modifications](http://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html)
|
33
|
+
by adding a `:comment` option, like so:
|
34
|
+
|
35
|
+
create_table :comments, :comment => "Foo to you too" do
|
36
|
+
primary_key :id, :comment => "Auto-incrementing primary key"
|
37
|
+
String :data, :null => false, :comment => "Markdown"
|
38
|
+
foreign_key :user_id, :users, :comment => "The user who owns the comment"
|
39
|
+
|
40
|
+
index :user_id, :comment => "Find those user comments faster!"
|
41
|
+
end
|
42
|
+
|
43
|
+
For some object types, though, there's no syntactic sugar in Sequel, so
|
44
|
+
you've got to create them by hand. Never fear, though! You can comment on
|
45
|
+
any object using Ruby code, like so:
|
46
|
+
|
47
|
+
comment_on :collation, :my_collation, "Collate ALL THE THINGS!"
|
48
|
+
|
49
|
+
This is useful also for tables, where you might not want to insert a lengthy
|
50
|
+
comment in the top of your `create_table` block.
|
51
|
+
|
52
|
+
The `table.column` syntax that PgSQL requires for `COMMENT ON COLUMN` can be
|
53
|
+
simulated in the usual Sequel fashion, of using two underscores in the
|
54
|
+
symbol to separate the table name from the column name, like so:
|
55
|
+
|
56
|
+
comment_on :column, :foo__bar_id, %{
|
57
|
+
This is my column. There are many like it, but this one is mine.
|
58
|
+
}
|
59
|
+
# => COMMENT ON COLUMN "foo"."bar_id" IS 'This is my column. (etc)'
|
60
|
+
|
61
|
+
**NOTE**: The object you wish to comment on *must already exist* before you
|
62
|
+
call `comment_on`. The following example **WILL NOT WORK**:
|
63
|
+
|
64
|
+
comment_on :table, :foo, "This is an awesomely foo table"
|
65
|
+
create_table :foo, do
|
66
|
+
# ...
|
67
|
+
end
|
68
|
+
|
69
|
+
You have to put the `comment_on` *after* the `create_table`, like this:
|
70
|
+
|
71
|
+
create_table :foo, do
|
72
|
+
# ...
|
73
|
+
end
|
74
|
+
comment_on :table, :foo, "This is an awesomely foo table"
|
75
|
+
|
76
|
+
|
77
|
+
## Comment string tidy-up
|
78
|
+
|
79
|
+
On the whole, `sequel-pg-comment` makes no judgment on what you put in your
|
80
|
+
comments. Plain text, markdown, XML, or morse code -- it's all the same.
|
81
|
+
|
82
|
+
There is *one* manipulation that is done to multi-line comments, though, to
|
83
|
+
make it a bit easier to write lengthy treatises on the whichness of the why,
|
84
|
+
and that is to strip out leading whitespace. The rules are very simple:
|
85
|
+
|
86
|
+
1. Empty lines at the beginning and end of the comment are removed; and
|
87
|
+
|
88
|
+
1. Whatever whitespace is present before the *first* non-empty line of the
|
89
|
+
comment, will be stripped from the beginning of *every* line.
|
90
|
+
|
91
|
+
That means you can use a heredoc for your multi-line comments, and they'll
|
92
|
+
still look neat and tidy without having to play `gsub` tricks:
|
93
|
+
|
94
|
+
create_table :foo do
|
95
|
+
String :data, :comment => <<-EOF
|
96
|
+
This is a very lengthy comment. It goes for many lines
|
97
|
+
and has a great deal to say on any number of subjects. I
|
98
|
+
could have used lorem ipsum here, but I prefer to do things
|
99
|
+
the old-fashioned way. If you've read all of this example,
|
100
|
+
you probably stay to read the whole of the credits at the
|
101
|
+
cinema. Good for you! I do too. Wave next time, you
|
102
|
+
anti-social loner.
|
103
|
+
EOF
|
104
|
+
end
|
105
|
+
|
106
|
+
One caveat: it's common to use the `%( ... )` quoting style for lengthy
|
107
|
+
strings. That's fine, but make sure to put the first line of docs on its
|
108
|
+
own line, and not directly after the `%(`. For example, this will not work
|
109
|
+
so well:
|
110
|
+
|
111
|
+
# This WILL NOT trim leading whitespace from each line
|
112
|
+
comment_on :table, :foo, %(This is a long comment.
|
113
|
+
However, due to the way that pg-comment trims whitespace,
|
114
|
+
these lines will have leading indents, because the first line
|
115
|
+
didn't.
|
116
|
+
)
|
117
|
+
|
118
|
+
Instead, you'll want to do this:
|
119
|
+
|
120
|
+
# This WILL trim leading whitespace from each line
|
121
|
+
comment_on :table, :foo, %(
|
122
|
+
This, too, is a long comment. Because the first non-empty
|
123
|
+
line had leading spaces, all of these other lines will have
|
124
|
+
their leaving spaces stripped too.
|
125
|
+
)
|
126
|
+
|
127
|
+
As always, inconsistent use of tabs and spaces will end in disaster. So
|
128
|
+
don't do that. Remember: tabs are for indenting, spaces are for formatting.
|
129
|
+
|
130
|
+
|
131
|
+
## Quoting and escaping
|
132
|
+
|
133
|
+
In normal circumstances, if you follow some fairly simple rules and don't
|
134
|
+
need to put comments on a few gnarly types, you should never have to do any
|
135
|
+
SQL-specific escaping or quoting of the values you pass to the methods in
|
136
|
+
this extension. We work very hard to properly escape as much as we can.
|
137
|
+
|
138
|
+
The rules for quoting are:
|
139
|
+
|
140
|
+
1. If an object name is passed as a symbol, it will be escaped. For certain
|
141
|
+
types (`COLUMN`, `CONSTRAINT`, `RULE`, and `TRIGGER`), we split on the first
|
142
|
+
double underscore (ie `__`) and the part before the double underscore is the
|
143
|
+
table name, and the rest is the object name. Each part is quoted
|
144
|
+
separately.
|
145
|
+
|
146
|
+
2. If an object name is passed as a string, **NO QUOTING IS PERFORMED**. It
|
147
|
+
is assumed, in that instance, that you've already done all the quoting you
|
148
|
+
need yourself.
|
149
|
+
|
150
|
+
3. Comment strings should always be passed as strings.
|
151
|
+
|
152
|
+
4. Object types can be specified as either strings or symbols, with space-
|
153
|
+
or understore-separated words, in any mix of case, and they will be
|
154
|
+
correctly handled.
|
155
|
+
|
156
|
+
(Of course, the *real* rule for object naming is: *stick to alphanumerics and
|
157
|
+
underscores, fer cryin' out loud!*)
|
158
|
+
|
159
|
+
The relative complexity of these rules is due to the fact that there are a
|
160
|
+
few PostgreSQL object types which stubbornly resist attempts to
|
161
|
+
automatically escape their names in a safe manner. The most visible
|
162
|
+
offender is `FUNCTION`, which is both relatively commonly used, and has a
|
163
|
+
*particularly* complicated object name specification. For those types, you
|
164
|
+
have to do some quoting yourself, and pass the object name as a string.
|
165
|
+
|
166
|
+
I do apologise for the complexity and lack of *absolute* safety in all this
|
167
|
+
(although if someone can SQL inject your migrations, you're having a
|
168
|
+
*really* bad day), but I had absolutely no luck in producing an interface
|
169
|
+
that wasn't a complete nightmare to use, an implementation that wasn't a
|
170
|
+
complex mess, and that stuck fairly closely to the common idioms present in
|
171
|
+
Sequel proper. If you happen to have ideas on how to make this better, [I'm
|
172
|
+
all ears](mailto:theshed+sequel-pg-comment@hezmatt.org).
|
173
|
+
|
174
|
+
|
175
|
+
# Getting your comments back
|
176
|
+
|
177
|
+
It's great that this gem can help you to document your database, but that's
|
178
|
+
not much use if nobody can read them again. Within Sequel itself, you can
|
179
|
+
retrieve comments quite easily:
|
180
|
+
|
181
|
+
DB.comment_for(:foo) # => "Something something dark side"
|
182
|
+
|
183
|
+
For columns, you can either use the double underscore notation:
|
184
|
+
|
185
|
+
DB.comment_for(:foo__column) # => "Awwwwww yeah"
|
186
|
+
|
187
|
+
Or you can do the same thing from the dataset itself:
|
188
|
+
|
189
|
+
DB[:foo].comment_for(:column) # => "Awwwwww yeah"
|
190
|
+
|
191
|
+
There's currently no support for retrieving a database comment from a Sequel
|
192
|
+
model; pull requests implementing such a feature would be warmly welcomed.
|
193
|
+
|
194
|
+
The real value in database comments, though, comes when you use an
|
195
|
+
entity-relationship diagram tool like
|
196
|
+
[SchemaSpy](http://schemaspy.sourceforge.net/), which draws all sorts of
|
197
|
+
pretty pictures and lays out all of the schema information.
|
198
|
+
|
199
|
+
If you quickly need to get some docs when you're in `psql`, you can get it
|
200
|
+
out of the "additional detail" informational commands, like `\dt+ <table
|
201
|
+
name>`.
|
202
|
+
|
203
|
+
|
204
|
+
# Contributing
|
205
|
+
|
206
|
+
Bug reports should be sent to the [Github issue
|
207
|
+
tracker](https://github.com/mpalmer/sequel-pg-comment/issues), or
|
208
|
+
[e-mailed](mailto:theshed+sequel-pg-comment@hezmatt.org). Patches can be sent as a
|
209
|
+
Github pull request, or [e-mailed](mailto:theshed+sequel-pg-comment@hezmatt.org).
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
exec(*(["bundle", "exec", $PROGRAM_NAME] + ARGV)) if ENV['BUNDLE_GEMFILE'].nil?
|
2
|
+
|
3
|
+
Bundler.setup(:default, :development)
|
4
|
+
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
begin
|
8
|
+
Bundler.setup(:default, :development)
|
9
|
+
rescue Bundler::BundlerError => e
|
10
|
+
$stderr.puts e.message
|
11
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
12
|
+
exit e.status_code
|
13
|
+
end
|
14
|
+
|
15
|
+
Bundler::GemHelper.install_tasks
|
16
|
+
|
17
|
+
task :release do
|
18
|
+
sh "git release"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'yard'
|
22
|
+
|
23
|
+
YARD::Rake::YardocTask.new :doc do |yardoc|
|
24
|
+
yardoc.files = %w{lib/**/*.rb - README.md}
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Run guard"
|
28
|
+
task :guard do
|
29
|
+
require 'guard'
|
30
|
+
::Guard.start(:clear => true)
|
31
|
+
while ::Guard.running do
|
32
|
+
sleep 0.5
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
require 'rspec/core/rake_task'
|
37
|
+
RSpec::Core::RakeTask.new :test do |t|
|
38
|
+
t.pattern = "spec/**/*_spec.rb"
|
39
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Sequel::Extension; end #:nodoc:
|
2
|
+
|
3
|
+
# PostgreSQL-specific extension to set and retrieve comments on
|
4
|
+
# all database objects.
|
5
|
+
#
|
6
|
+
module Sequel::Extension::PgComment
|
7
|
+
# Strip indenting whitespace from a comment string.
|
8
|
+
#
|
9
|
+
# Two rules are applied by this method:
|
10
|
+
#
|
11
|
+
# 1. Empty lines (that is, lines which have *no* characters in them, not
|
12
|
+
# even whitespace) are removed from the beginning and end of the
|
13
|
+
# string.
|
14
|
+
#
|
15
|
+
# 2. Whatever whitespace characters may be present before the first line
|
16
|
+
# of text will be removed from the beginning of each subsequent line
|
17
|
+
# of the comment, if it exists.
|
18
|
+
#
|
19
|
+
# This means you can use heredocs and multi-line strings without having
|
20
|
+
# to play your own silly-buggers in order to not make the comments look
|
21
|
+
# like arse. If you like heredocs, you're encouraged to use this style:
|
22
|
+
#
|
23
|
+
# comment_for :table, :foo, <<-EOF
|
24
|
+
# This is my comment.
|
25
|
+
#
|
26
|
+
# And here's another line.
|
27
|
+
# EOF
|
28
|
+
#
|
29
|
+
# On the other hand, if percent-quotes are more your cup of tea, you can
|
30
|
+
# go at them like this:
|
31
|
+
#
|
32
|
+
# comment_for :table, :foo, %(
|
33
|
+
# This is my comment.
|
34
|
+
#
|
35
|
+
# And here's another line.
|
36
|
+
# )
|
37
|
+
#
|
38
|
+
# Be sure to not start the first line of your comment on the same line as
|
39
|
+
# the quote character, though, because that will mean the first line of
|
40
|
+
# the comment *doesn't* have any leading whitespace, and so whitespace
|
41
|
+
# won't be trimmed from the rest of the comment. So, don't do this:
|
42
|
+
#
|
43
|
+
# comment_for :table, :foo, %(This won't work at all well.
|
44
|
+
# Since the first line of text doesn't have leading whitespace,
|
45
|
+
# these lines won't have it stripped either, and everything will
|
46
|
+
# look weird.
|
47
|
+
# )
|
48
|
+
#
|
49
|
+
# @param comment [String] The comment to mangle for whitespace.
|
50
|
+
#
|
51
|
+
# @return [String] The normalised comment, with whitespace removed.
|
52
|
+
#
|
53
|
+
def self.normalise_comment(comment)
|
54
|
+
comment.tap do |s|
|
55
|
+
s.gsub!(/\A\n+/, '')
|
56
|
+
s.gsub!(/\n+\Z/, '')
|
57
|
+
|
58
|
+
(s =~ /\A(\s+)/ and lede = $1) or lede = ''
|
59
|
+
s.gsub!(/^#{lede}/, '')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
require_relative 'pg_comment/sql_generator'
|
65
|
+
require_relative 'pg_comment/database_methods'
|
66
|
+
require_relative 'pg_comment/dataset_methods'
|
67
|
+
require_relative 'pg_comment/create_table_generator_methods'
|
68
|
+
require_relative 'pg_comment/alter_table_generator_methods'
|
69
|
+
|
70
|
+
Sequel::Database.register_extension(:pg_comment) do |db|
|
71
|
+
db.extend Sequel::Extension::PgComment::DatabaseMethods
|
72
|
+
db.extend_datasets Sequel::Extension::PgComment::DatasetMethods
|
73
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
#:nodoc:
|
2
|
+
# Enhancements to the standard schema modification methods in a
|
3
|
+
# block-form `alter_table` method, to support setting comments via the
|
4
|
+
# `:comment` option.
|
5
|
+
#
|
6
|
+
# Note that not every schema modification method is enhanced in this module;
|
7
|
+
# some modifications are implemneted in terms of more fundamental methods,
|
8
|
+
# and so do not require their own method here. For example, `add_foreign_key`
|
9
|
+
# with a single column is handled by `add_column`, and so doesn't require its
|
10
|
+
# own implementation. Rest assured that all schema modification methods
|
11
|
+
# *should* accept a `:comment` option, and set a comment in the database. If
|
12
|
+
# you find one that doesn't, please file a bug.
|
13
|
+
#
|
14
|
+
module Sequel::Extension::PgComment::AlterTableGeneratorMethods
|
15
|
+
attr_reader :comments
|
16
|
+
|
17
|
+
include Sequel::Extension::PgComment
|
18
|
+
|
19
|
+
# Enhanced version of the `add_column` schema modification method,
|
20
|
+
# which supports setting a comment on the column.
|
21
|
+
#
|
22
|
+
# @option [String] :comment The comment to set on the column
|
23
|
+
# that is being added.
|
24
|
+
#
|
25
|
+
def add_column(*args)
|
26
|
+
super
|
27
|
+
|
28
|
+
if args.last.is_a?(Hash) && args.last[:comment]
|
29
|
+
comments << SqlGenerator.create(:column, args.first, args.last[:comment])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Enhanced version of the `add_composite_primary_key` schema modification
|
34
|
+
# method, which supports setting a comment on the index.
|
35
|
+
#
|
36
|
+
# @option [String] :comment The comment to set on the index that is being
|
37
|
+
# added.
|
38
|
+
#
|
39
|
+
def add_composite_primary_key(columns, opts)
|
40
|
+
super
|
41
|
+
|
42
|
+
if opts[:comment]
|
43
|
+
comments << PrefixSqlGenerator.new(:index, :_pkey, opts[:comment])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Enhanced version of the `add_composite_foreign_key` schema modification
|
48
|
+
# method, which supports setting a comment on the constraint.
|
49
|
+
#
|
50
|
+
# @option [String] :comment The comment to set on the constraint that is being
|
51
|
+
# added.
|
52
|
+
#
|
53
|
+
def add_composite_foreign_key(columns, table, opts)
|
54
|
+
super
|
55
|
+
|
56
|
+
if opts[:comment]
|
57
|
+
comments << if opts[:name]
|
58
|
+
SqlGenerator.create(:constraint, opts[:name], opts[:comment])
|
59
|
+
else
|
60
|
+
PrefixSqlGenerator.new(
|
61
|
+
:constraint,
|
62
|
+
"_#{columns.first}_fkey".to_sym,
|
63
|
+
opts[:comment]
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Enhanced version of the `add_index` schema modification method, which
|
70
|
+
# supports setting a comment on the index.
|
71
|
+
#
|
72
|
+
# @option [String] :comment The comment to set on the index that is being
|
73
|
+
# added.
|
74
|
+
#
|
75
|
+
def add_index(columns, opts = OPTS)
|
76
|
+
if opts[:comment]
|
77
|
+
comments << if opts[:name]
|
78
|
+
SqlGenerator.create(:index, opts[:name], opts[:comment])
|
79
|
+
else
|
80
|
+
PrefixSqlGenerator.new(
|
81
|
+
:index,
|
82
|
+
"_#{[columns].flatten.map(&:to_s).join("_")}_index".to_sym,
|
83
|
+
opts[:comment]
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Enhanced version of the `add_constraint` schema modification method,
|
90
|
+
# which supports setting a comment on the constraint.
|
91
|
+
#
|
92
|
+
# @option [String] :comment The comment to set on the constraint that is
|
93
|
+
# being added.
|
94
|
+
#
|
95
|
+
def add_constraint(name, *args, &block)
|
96
|
+
super
|
97
|
+
|
98
|
+
opts = args.last.is_a?(Hash) ? args.last : {}
|
99
|
+
|
100
|
+
if opts[:comment]
|
101
|
+
comments << SqlGenerator.create(:constraint, name, opts[:comment])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Enhanced version of the `add_unique_constraint` schema modification
|
106
|
+
# method, which supports setting a comment on the index.
|
107
|
+
#
|
108
|
+
# @option [String] :comment The comment to set on the index that is being
|
109
|
+
# added.
|
110
|
+
#
|
111
|
+
def add_unique_constraint(columns, opts = OPTS)
|
112
|
+
super
|
113
|
+
|
114
|
+
if opts[:comment]
|
115
|
+
comments << if opts[:name]
|
116
|
+
SqlGenerator.create(:index, opts[:name], opts[:comment])
|
117
|
+
else
|
118
|
+
PrefixSqlGenerator.new(
|
119
|
+
:index,
|
120
|
+
"_#{[columns].flatten.map(&:to_s).join("_")}_key".to_sym,
|
121
|
+
opts[:comment]
|
122
|
+
)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|