activerecord-comments 0.0.3
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 +22 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +26 -0
- data/Rakefile +1 -0
- data/activerecord-comments.gemspec +28 -0
- data/bin/activerecord-comments +44 -0
- data/lib/active_record/comments.rb +42 -0
- data/lib/active_record/comments/connection_adapters/abstract_adapter.rb +43 -0
- data/lib/active_record/comments/connection_adapters/abstract_sqlite_adapter.rb +70 -0
- data/lib/active_record/comments/connection_adapters/comment_definition.rb +24 -0
- data/lib/active_record/comments/connection_adapters/mysql2_adapter.rb +9 -0
- data/lib/active_record/comments/connection_adapters/mysql_adapter.rb +79 -0
- data/lib/active_record/comments/connection_adapters/postgresql_adapter.rb +77 -0
- data/lib/active_record/comments/connection_adapters/sqlite3_adapter.rb +28 -0
- data/lib/active_record/comments/connection_adapters/sqlite_adapter.rb +26 -0
- data/lib/active_record/comments/version.rb +5 -0
- data/spec/activerecord_comments_spec.rb +98 -0
- data/spec/config/database.yml +18 -0
- data/spec/spec_helper.rb +25 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e72374f3910386b8d9db461607ac65df0eb162f9
|
4
|
+
data.tar.gz: c662619926513774697a3b2cfad8d6546a14ac22
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d1f359c48d85d25e8bcd5823e619b221f71878ae95ec9a4bff7dc4b81d155dcf21a60cfbe6d48444ba3606c0a3214d55ef503501170ba8bfd2bbff1148fac3c2
|
7
|
+
data.tar.gz: d0d79995bc495bf4f8e28501ec04415f3dad0904ded665de73b7b1f6668177d9be531cb38b9fb4aed6f845120ea942050a14ec2ffbcd4db609d3e559e2a4e7e1
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Marton Somogyi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# ActiveRecord::Comments
|
2
|
+
|
3
|
+
Manage comments for SQL schemas. Set, get and remove comments on tables and columns. ActiveRecord bindings and executable with CLI.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'activerecord-comments'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install activerecord-comments
|
18
|
+
|
19
|
+
## Executable usage
|
20
|
+
|
21
|
+
Set the DB environment variable to select from database configurations. Current value: "postgres"
|
22
|
+
-a, --action ACTION Action to perform: set, retrieve, remove
|
23
|
+
-t, --table TABLE Select table
|
24
|
+
-c, --column COLUMN Select column
|
25
|
+
-m, --comment COMMENT Comment to save
|
26
|
+
--conf DBCONF Database config YAML (default: /Users/<user>/activerecord_comments/config/database.yml)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require File.expand_path('../lib/active_record/comments/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = 'activerecord-comments'
|
6
|
+
spec.version = ActiveRecord::Comments::VERSION
|
7
|
+
spec.authors = ['Marton Somogyi']
|
8
|
+
spec.email = ['msomogyi@whitepages.com']
|
9
|
+
spec.summary = %q{Comments for SQL schemas}
|
10
|
+
spec.description = %q{Manage comments for SQL tables and columns}
|
11
|
+
spec.homepage = 'https://github.com/mcbuddha/activerecord-comments'
|
12
|
+
spec.license = 'MIT'
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ['lib']
|
18
|
+
|
19
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
20
|
+
spec.add_development_dependency 'rake', '~> 10'
|
21
|
+
spec.add_development_dependency 'rspec', '~> 3'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'pg', '~> 0.17'
|
24
|
+
spec.add_development_dependency 'mysql2', '~> 0'
|
25
|
+
spec.add_development_dependency 'sqlite3', '~> 0'
|
26
|
+
|
27
|
+
spec.add_runtime_dependency 'activerecord', '~> 3'
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'pry'
|
3
|
+
require 'yaml'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
require 'active_record/comments'
|
7
|
+
|
8
|
+
ENV['DB'] ||= 'postgres'
|
9
|
+
|
10
|
+
TYPES = %w(table column)
|
11
|
+
ACTIONS = %w(set retrieve remove)
|
12
|
+
|
13
|
+
options = {dbconf: File.expand_path('./config/database.yml', Dir.pwd)}
|
14
|
+
OptionParser.new do |opts|
|
15
|
+
opts.banner = <<USAGE
|
16
|
+
Set the DB environment variable to select from database configurations. Current value: "#{ENV['DB']}"
|
17
|
+
USAGE
|
18
|
+
|
19
|
+
opts.on('-a', '--action ACTION', ACTIONS, "Action to perform: #{ACTIONS.join(', ')}") { |action| options[:action] = action }
|
20
|
+
opts.on('-t', '--table TABLE', 'Select table') { |table| options[:table] = table }
|
21
|
+
opts.on('-c', '--column COLUMN', 'Select column') { |column| options[:column] = column }
|
22
|
+
opts.on('-m', '--comment COMMENT', 'Comment to save') { |comment| options[:comment] = comment }
|
23
|
+
opts.on('--conf DBCONF', "Database config YAML (default: #{options[:dbconf]})") { |dbconf| options[:dbconf] = dbconf }
|
24
|
+
end.parse!
|
25
|
+
|
26
|
+
abort 'You must specify a table' unless options[:table]
|
27
|
+
abort 'You must specify an action' unless options[:action]
|
28
|
+
abort 'You must specify a comment' if options[:action] == 'set' && options[:comment].nil?
|
29
|
+
|
30
|
+
DBCONF = YAML::load IO.read options[:dbconf] rescue abort "Error reading #{options[:dbconf]}: #{$!}"
|
31
|
+
|
32
|
+
ActiveRecord::Base.establish_connection(DBCONF[ENV['DB']]).connection rescue abort "Couldn't connect to #{options[:dbconf]}[#{ENV['DB']}]: #{$!}"
|
33
|
+
|
34
|
+
command = "#{options[:action]}_#{options[:column] ? 'column':'table'}_comment"
|
35
|
+
args = [options[:table].to_sym]
|
36
|
+
args << options[:column].to_sym if options[:column]
|
37
|
+
args << options[:comment] if options[:comment] and options[:action] != 'retrieve'
|
38
|
+
|
39
|
+
begin
|
40
|
+
result = ActiveRecord::Base.connection.send command, *args
|
41
|
+
puts result if options[:action] == 'retrieve'
|
42
|
+
rescue
|
43
|
+
abort "Error while executing query: #{$!}"
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
4
|
+
require 'active_record/comments/version'
|
5
|
+
|
6
|
+
module ActiveRecord::Comments; end
|
7
|
+
|
8
|
+
require 'active_record/comments/connection_adapters/comment_definition'
|
9
|
+
require 'active_record/comments/connection_adapters/abstract_adapter'
|
10
|
+
require 'active_record/comments/connection_adapters/postgresql_adapter'
|
11
|
+
require 'active_record/comments/connection_adapters/abstract_sqlite_adapter'
|
12
|
+
require 'active_record/comments/connection_adapters/sqlite_adapter'
|
13
|
+
require 'active_record/comments/connection_adapters/sqlite3_adapter'
|
14
|
+
require 'active_record/comments/connection_adapters/mysql_adapter'
|
15
|
+
require 'active_record/comments/connection_adapters/mysql2_adapter'
|
16
|
+
|
17
|
+
module ActiveRecord::Comments
|
18
|
+
def self.setup
|
19
|
+
_cls = ->(l) { l.join('::').constantize }
|
20
|
+
|
21
|
+
ar_names = %w(ActiveRecord ConnectionAdapters AbstractAdapter)
|
22
|
+
ar_class = _cls.(ar_names)
|
23
|
+
arc_class = _cls.([self.name, *ar_names[1..-1]])
|
24
|
+
ar_class.send :include, arc_class unless ar_class.ancestors.include? arc_class
|
25
|
+
|
26
|
+
adapters = %w(PostgreSQL Mysql Mysql2)
|
27
|
+
adapters << (::ActiveRecord::VERSION::MAJOR <= 3 ? 'SQLite' : 'SQLite3')
|
28
|
+
adapters.each do |adapter|
|
29
|
+
begin require "active_record/connection_adapters/#{adapter.downcase}_adapter"
|
30
|
+
rescue LoadError => err; next; end
|
31
|
+
|
32
|
+
to_inject = "#{adapter}Adapter"
|
33
|
+
ar_class = _cls.([*ar_names[0..-2], to_inject])
|
34
|
+
arc_class = _cls.([self.name, *ar_names[1..-2], to_inject])
|
35
|
+
ar_class.module_eval do
|
36
|
+
ar_class.send :include, arc_class unless ar_class.ancestors.include?(arc_class)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
ActiveRecord::Comments.setup
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module ActiveRecord::Comments::ConnectionAdapters
|
2
|
+
module AbstractAdapter
|
3
|
+
def set_table_comment(table_name, comment_text)
|
4
|
+
end
|
5
|
+
|
6
|
+
def set_column_comment(table_name, column_name, comment_text)
|
7
|
+
end
|
8
|
+
|
9
|
+
def comments_supported?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
# SQLite style - embedded comments
|
14
|
+
def inline_comments?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
# PostgreSQL style - comment specific commands
|
19
|
+
def independent_comments?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove_table_comment(table_name)
|
24
|
+
set_table_comment(table_name, nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
def remove_column_comment(table_name, column_name)
|
28
|
+
set_column_comment(table_name, column_name, nil)
|
29
|
+
end
|
30
|
+
|
31
|
+
def retrieve_table_comment(table_name)
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def retrieve_column_comments(table_name, *column_names)
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
|
39
|
+
def retrieve_column_comment(table_name, column_name)
|
40
|
+
retrieve_column_comments(table_name, column_name)[column_name]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module ActiveRecord::Comments::ConnectionAdapters
|
2
|
+
module AbstractSQLiteAdapter
|
3
|
+
def comments_supported?
|
4
|
+
true
|
5
|
+
end
|
6
|
+
|
7
|
+
def inline_comments?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_table_comment(table_name, comment_text)
|
12
|
+
alter_table(table_name, :comment => comment_text)
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_column_comment(table_name, column_name, comment_text)
|
16
|
+
sql_type = primary_key(table_name) == column_name.to_s ?
|
17
|
+
:primary_key :
|
18
|
+
column_for(table_name, column_name).sql_type
|
19
|
+
change_column table_name, column_name, sql_type, :comment => comment_text
|
20
|
+
end
|
21
|
+
|
22
|
+
def retrieve_table_comment(table_name)
|
23
|
+
result = select_rows(lookup_comment_sql(table_name))
|
24
|
+
if result[0][0] =~ /CREATE (?:TEMPORARY )?TABLE #{quote_table_name table_name} [^\(]*\/\*(.*)\*\/ \(/
|
25
|
+
$1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def retrieve_column_comments(table_name, *column_names)
|
30
|
+
if column_names.empty?
|
31
|
+
return columns(table_name).inject({}) { |m, v| m[v.name.to_sym] = v.comment if v.comment.present?; m }
|
32
|
+
end
|
33
|
+
result = select_rows(lookup_comment_sql(table_name))
|
34
|
+
result[0][0] =~ /^CREATE (?:TEMPORARY )?TABLE "\w*" [^\(]*(?:\/\*.*\*\/ )?\((.*)\)[^\)]*$/
|
35
|
+
col_defs = $1
|
36
|
+
comment_matches = col_defs.scan(/"([^",]+)"[^,]*\/\*(.+?)\*\//)
|
37
|
+
comment_matches.inject({}){|m, row| m[row.first.to_sym] = row.last; m}
|
38
|
+
end
|
39
|
+
|
40
|
+
def column_for(table_name, column_name)
|
41
|
+
columns(table_name).detect{|col| col.name == column_name.to_s}
|
42
|
+
end
|
43
|
+
|
44
|
+
def comment_sql(comment_definition)
|
45
|
+
if comment_definition.nil? || comment_definition.comment_text.blank?
|
46
|
+
""
|
47
|
+
else
|
48
|
+
" /*#{escaped_comment(comment_definition.comment_text)}*/"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_column_options!(sql, options)
|
54
|
+
super(sql, options)
|
55
|
+
if options.keys.include?(:comment)
|
56
|
+
sql << CommentDefinition.new(self, nil, nil, options[:comment]).to_sql
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def escaped_comment(comment)
|
63
|
+
comment.gsub(/\*\//, "*-/")
|
64
|
+
end
|
65
|
+
|
66
|
+
def lookup_comment_sql(table_name)
|
67
|
+
"select sql from (select * from sqlite_master where type='table' union select * from sqlite_temp_master where type='table') where tbl_name = '#{table_name}'"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActiveRecord::Comments::ConnectionAdapters
|
2
|
+
class CommentDefinition < Struct.new(:adapter, :table, :column_name, :comment_text)
|
3
|
+
def to_dump
|
4
|
+
if table_comment?
|
5
|
+
"set_table_comment :#{table_name}, %{#{comment_text}}"
|
6
|
+
else
|
7
|
+
"set_column_comment :#{table_name}, :#{column_name}, %{#{comment_text}}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_sql
|
12
|
+
adapter.comment_sql(self)
|
13
|
+
end
|
14
|
+
alias to_s :to_sql
|
15
|
+
|
16
|
+
def table_comment?
|
17
|
+
column_name.blank?
|
18
|
+
end
|
19
|
+
|
20
|
+
def table_name
|
21
|
+
table.respond_to?(:name) ? table.name : table
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module ActiveRecord::Comments::ConnectionAdapters
|
2
|
+
module MysqlAdapter
|
3
|
+
def comments_supported?
|
4
|
+
true
|
5
|
+
end
|
6
|
+
|
7
|
+
def set_table_comment(table_name, comment_text)
|
8
|
+
execute "ALTER TABLE #{quote_table_name table_name} COMMENT #{escaped_comment(comment_text)}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_column_comment(table_name, column_name, comment_text)
|
12
|
+
pk_info = pk_and_sequence_for(table_name)
|
13
|
+
if pk_info && pk_info.first == column_name.to_s # change_column broken for :primary_key
|
14
|
+
primary_col_def = native_database_types[:primary_key].sub(/ PRIMARY KEY/i, '')
|
15
|
+
execute "ALTER TABLE #{quote_table_name table_name} CHANGE #{quote_column_name column_name} #{quote_column_name column_name} #{primary_col_def} COMMENT #{escaped_comment(comment_text)};"
|
16
|
+
else
|
17
|
+
column = column_for(table_name, column_name)
|
18
|
+
change_column table_name, column_name, column.sql_type, :comment => comment_text
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def retrieve_table_comment(table_name)
|
23
|
+
result = select_rows(table_comment_sql(table_name))
|
24
|
+
result[0].nil? || result[0][0].blank? ? nil : result[0][0]
|
25
|
+
end
|
26
|
+
|
27
|
+
def retrieve_column_comments(table_name, *column_names)
|
28
|
+
result = select_rows(column_comment_sql(table_name, *column_names))
|
29
|
+
return {} if result.nil?
|
30
|
+
result.inject({}){|m, row| m[row[0].to_sym] = (row[1].blank? ? nil : row[1]); m}
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_column_options!(sql, options)
|
34
|
+
super(sql, options)
|
35
|
+
if options.keys.include?(:comment)
|
36
|
+
sql << CommentDefinition.new(self, nil, nil, options[:comment]).to_sql
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def comment_sql(comment_definition)
|
41
|
+
" COMMENT #{escaped_comment(comment_definition.comment_text)}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def execute_comment(comment_definition)
|
45
|
+
if comment_definition.table_comment?
|
46
|
+
set_table_comment comment_definition.table_name, comment_definition.comment_text
|
47
|
+
else
|
48
|
+
set_column_comment comment_definition.table_name, comment_definition.column_name, comment_definition.comment_text
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def escaped_comment(comment)
|
54
|
+
comment.nil? ? "''" : "'#{comment.gsub("'", "''").gsub("\\", "\\\\\\\\")}'"
|
55
|
+
end
|
56
|
+
|
57
|
+
def table_comment_sql(table_name)
|
58
|
+
<<SQL
|
59
|
+
SELECT table_comment FROM INFORMATION_SCHEMA.TABLES
|
60
|
+
WHERE table_schema = '#{database_name}'
|
61
|
+
AND table_name = '#{table_name}'
|
62
|
+
SQL
|
63
|
+
end
|
64
|
+
|
65
|
+
def column_comment_sql(table_name, *column_names)
|
66
|
+
col_matcher_sql = column_names.empty? ? "" : " AND column_name IN (#{column_names.map{|c_name| "'#{c_name}'"}.join(',')})"
|
67
|
+
<<SQL
|
68
|
+
SELECT column_name, column_comment FROM INFORMATION_SCHEMA.COLUMNS
|
69
|
+
WHERE table_schema = '#{database_name}'
|
70
|
+
AND table_name = '#{table_name}' #{col_matcher_sql}
|
71
|
+
SQL
|
72
|
+
end
|
73
|
+
|
74
|
+
def database_name
|
75
|
+
@database_name ||= select_rows("SELECT DATABASE()")[0][0]
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module ActiveRecord::Comments::ConnectionAdapters
|
2
|
+
module PostgreSQLAdapter
|
3
|
+
def comments_supported?
|
4
|
+
true
|
5
|
+
end
|
6
|
+
|
7
|
+
def independent_comments?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_table_comment(table_name, comment_text)
|
12
|
+
execute CommentDefinition.new(self, table_name, nil, comment_text).to_sql
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_column_comment(table_name, column_name, comment_text)
|
16
|
+
execute CommentDefinition.new(self, table_name, column_name, comment_text).to_sql
|
17
|
+
end
|
18
|
+
|
19
|
+
def retrieve_table_comment(table_name)
|
20
|
+
result = select_rows(table_comment_sql(table_name))
|
21
|
+
result[0].nil? ? nil : result[0][0]
|
22
|
+
end
|
23
|
+
|
24
|
+
def retrieve_column_comments(table_name, *column_names)
|
25
|
+
result = select_rows(column_comment_sql(table_name, *column_names))
|
26
|
+
return {} if result.nil?
|
27
|
+
return result.inject({}){|m, row| m[row[0].to_sym] = row[1]; m}
|
28
|
+
end
|
29
|
+
|
30
|
+
def comment_sql(comment_definition)
|
31
|
+
"COMMENT ON #{comment_target(comment_definition)} IS #{escaped_comment(comment_definition.comment_text)}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def comment_target(comment_definition)
|
37
|
+
comment_definition.table_comment? ?
|
38
|
+
"TABLE #{quote_table_name(comment_definition.table_name)}" :
|
39
|
+
"COLUMN #{quote_table_name(comment_definition.table_name)}.#{quote_column_name(comment_definition.column_name)}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def escaped_comment(comment)
|
43
|
+
comment.nil? ? 'NULL' : "'#{comment.gsub("'", "''")}'"
|
44
|
+
end
|
45
|
+
|
46
|
+
def table_comment_sql(table_name)
|
47
|
+
<<SQL
|
48
|
+
SELECT d.description FROM (
|
49
|
+
#{table_oids(table_name)}) tt
|
50
|
+
JOIN pg_catalog.pg_description d
|
51
|
+
ON tt.oid = d.objoid AND tt.tableoid = d.classoid AND d.objsubid = 0;
|
52
|
+
SQL
|
53
|
+
end
|
54
|
+
|
55
|
+
def column_comment_sql(table_name, *column_names)
|
56
|
+
col_matcher_sql = column_names.empty? ? "" : " a.attname IN (#{column_names.map{|c_name| "'#{c_name}'"}.join(',')}) AND "
|
57
|
+
<<SQL
|
58
|
+
SELECT a.attname, pg_catalog.col_description(a.attrelid, a.attnum)
|
59
|
+
FROM pg_catalog.pg_attribute a
|
60
|
+
JOIN (
|
61
|
+
#{table_oids(table_name)}) tt
|
62
|
+
ON tt.oid = a.attrelid
|
63
|
+
WHERE #{col_matcher_sql} a.attnum > 0 AND NOT a.attisdropped;
|
64
|
+
SQL
|
65
|
+
end
|
66
|
+
|
67
|
+
def table_oids(table_name)
|
68
|
+
<<SQL
|
69
|
+
SELECT c.oid, c.tableoid
|
70
|
+
FROM pg_catalog.pg_class c
|
71
|
+
WHERE c.relname = '#{table_name}'
|
72
|
+
AND c.relkind = 'r'
|
73
|
+
AND pg_catalog.pg_table_is_visible(c.oid)
|
74
|
+
SQL
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord::Comments::ConnectionAdapters
|
2
|
+
module SQLite3Adapter
|
3
|
+
include ActiveRecord::Comments::ConnectionAdapters::AbstractSQLiteAdapter
|
4
|
+
|
5
|
+
def create_table(table_name, options = {})
|
6
|
+
td = create_table_definition table_name, options[:temporary], options[:options]
|
7
|
+
td.base = self
|
8
|
+
|
9
|
+
unless options[:id] == false
|
10
|
+
pk = options.fetch(:primary_key) {
|
11
|
+
ActiveRecord::Base.get_primary_key table_name.to_s.singularize
|
12
|
+
}
|
13
|
+
|
14
|
+
td.primary_key pk, options.fetch(:id, :primary_key), options
|
15
|
+
end
|
16
|
+
td.comment options[:comment] if options.has_key?(:comment)
|
17
|
+
|
18
|
+
yield td if block_given?
|
19
|
+
|
20
|
+
if options[:force] && table_exists?(table_name)
|
21
|
+
drop_table(table_name, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
execute schema_creation.accept td
|
25
|
+
td.indexes.each_pair { |c,o| add_index table_name, c, o }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActiveRecord::Comments::ConnectionAdapters
|
2
|
+
module SQLiteAdapter
|
3
|
+
include ActiveRecord::Comments::ConnectionAdapters::AbstractSQLiteAdapter
|
4
|
+
|
5
|
+
def create_table(table_name, options = {})
|
6
|
+
td = ::ActiveRecord::ConnectionAdapters::TableDefinition.new(self)
|
7
|
+
td.primary_key(options[:primary_key] || ActiveRecord::Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
|
8
|
+
td.comment options[:comment] if options.has_key?(:comment)
|
9
|
+
td.base = self
|
10
|
+
|
11
|
+
yield td if block_given?
|
12
|
+
|
13
|
+
if options[:force] && table_exists?(table_name)
|
14
|
+
drop_table(table_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
|
18
|
+
create_sql << "#{quote_table_name(table_name)}#{td.table_comment} ("
|
19
|
+
create_sql << td.columns.map do |column|
|
20
|
+
column.to_sql + column.comment.to_s
|
21
|
+
end * ", "
|
22
|
+
create_sql << ") #{options[:options]}"
|
23
|
+
execute create_sql
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
TEST_COMMENTS =
|
4
|
+
[
|
5
|
+
%q{some "comment" with 'quotes'},
|
6
|
+
%q{other ; special \\ characters \' \"},
|
7
|
+
]
|
8
|
+
|
9
|
+
describe ActiveRecord::Comments do
|
10
|
+
subject { ActiveRecord::Base.connection }
|
11
|
+
let(:table) { :test }
|
12
|
+
describe '#setup' do
|
13
|
+
context 'should define' do
|
14
|
+
it('set_table_comment') { expect(subject).to respond_to(:set_table_comment) }
|
15
|
+
it('retrieve_table_comment') { expect(subject).to respond_to(:retrieve_table_comment) }
|
16
|
+
it('remove_table_comment') { expect(subject).to respond_to(:remove_table_comment) }
|
17
|
+
|
18
|
+
it('set_column_comment') { expect(subject).to respond_to(:set_column_comment) }
|
19
|
+
it('retrieve_column_comment') { expect(subject).to respond_to(:retrieve_column_comment) }
|
20
|
+
it('retrieve_column_comments') { expect(subject).to respond_to(:retrieve_column_comments) }
|
21
|
+
it('remove_column_comment') { expect(subject).to respond_to(:remove_column_comment) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'table comments' do
|
26
|
+
let(:comment) { 'test comment' }
|
27
|
+
let(:comment2) { 'other test comment' }
|
28
|
+
|
29
|
+
before { subject.set_table_comment table, comment }
|
30
|
+
|
31
|
+
it 'should read same comment' do
|
32
|
+
expect(subject.retrieve_table_comment table).to eq(comment)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should read last version' do
|
36
|
+
expect(subject.retrieve_table_comment table).to eq(comment)
|
37
|
+
subject.set_table_comment table, comment2
|
38
|
+
expect(subject.retrieve_table_comment table).to eq(comment2)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should read nil after remove' do
|
42
|
+
subject.remove_table_comment table
|
43
|
+
expect(subject.retrieve_table_comment table).to be_nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'test_comments' do
|
48
|
+
TEST_COMMENTS.each do |comment|
|
49
|
+
it do
|
50
|
+
subject.set_table_comment table, comment
|
51
|
+
expect(subject.retrieve_table_comment table).to eq(comment)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'column comments' do
|
57
|
+
let(:comment) { 'some comment' }
|
58
|
+
let(:comment2) { 'some other comment' }
|
59
|
+
let(:comment3) { 'another comment' }
|
60
|
+
|
61
|
+
let(:col) { :field1 }
|
62
|
+
let(:col2) { :field2 }
|
63
|
+
|
64
|
+
context 'should read' do
|
65
|
+
it do
|
66
|
+
subject.set_column_comment table, col, comment
|
67
|
+
expect(subject.retrieve_column_comment table, col).to eq(comment)
|
68
|
+
end
|
69
|
+
|
70
|
+
it do
|
71
|
+
subject.set_column_comment table, col2, comment2
|
72
|
+
expect(subject.retrieve_column_comment table, col2).to eq(comment2)
|
73
|
+
end
|
74
|
+
|
75
|
+
it do
|
76
|
+
subject.set_column_comment table, col, comment
|
77
|
+
subject.set_column_comment table, col2, comment2
|
78
|
+
expect(subject.retrieve_column_comments table, col, col2).to eq(col => comment, col2 => comment2)
|
79
|
+
end
|
80
|
+
|
81
|
+
it do
|
82
|
+
subject.set_column_comment table, col, comment
|
83
|
+
expect(subject.retrieve_column_comment table, col).to eq(comment)
|
84
|
+
subject.remove_column_comment table, col
|
85
|
+
expect(subject.retrieve_column_comment table, col).to be_nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'test_comments' do
|
90
|
+
TEST_COMMENTS.each do |comment|
|
91
|
+
it do
|
92
|
+
subject.set_column_comment table, col, comment
|
93
|
+
expect(subject.retrieve_column_comment table, col).to eq(comment)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
postgres:
|
2
|
+
adapter: postgresql
|
3
|
+
database: activerecord_comments_test
|
4
|
+
host: 127.0.0.1
|
5
|
+
username: postgres
|
6
|
+
password: postgres
|
7
|
+
|
8
|
+
mysql:
|
9
|
+
adapter: mysql2
|
10
|
+
database: activerecord_comments_test
|
11
|
+
user: root
|
12
|
+
password: password
|
13
|
+
|
14
|
+
sqlite:
|
15
|
+
adapter: sqlite3
|
16
|
+
database: test/db/activerecord_comments_test
|
17
|
+
pool: 5
|
18
|
+
timeout: 5000
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$: << File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'pry'
|
5
|
+
|
6
|
+
require 'active_record/comments'
|
7
|
+
|
8
|
+
DBCONF = YAML::load(IO.read(File.expand_path('../config/database.yml', __FILE__)))
|
9
|
+
ENV['DB'] ||= 'postgres'
|
10
|
+
ActiveRecord::Base.establish_connection DBCONF[ENV['DB']]
|
11
|
+
|
12
|
+
RSpec.configure do |c|
|
13
|
+
c.around(:each) do |ex|
|
14
|
+
ActiveRecord::Migration.suppress_messages do
|
15
|
+
ActiveRecord::Schema.define do
|
16
|
+
create_table :test do |t|
|
17
|
+
t.string :field1
|
18
|
+
t.integer :field2
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
ex.run
|
23
|
+
ActiveRecord::Migration.suppress_messages { ActiveRecord::Schema.define { drop_table :test } }
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord-comments
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marton Somogyi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pg
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.17'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.17'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mysql2
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sqlite3
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activerecord
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3'
|
111
|
+
description: Manage comments for SQL tables and columns
|
112
|
+
email:
|
113
|
+
- msomogyi@whitepages.com
|
114
|
+
executables:
|
115
|
+
- activerecord-comments
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- ".gitignore"
|
120
|
+
- Gemfile
|
121
|
+
- LICENSE.txt
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- activerecord-comments.gemspec
|
125
|
+
- bin/activerecord-comments
|
126
|
+
- lib/active_record/comments.rb
|
127
|
+
- lib/active_record/comments/connection_adapters/abstract_adapter.rb
|
128
|
+
- lib/active_record/comments/connection_adapters/abstract_sqlite_adapter.rb
|
129
|
+
- lib/active_record/comments/connection_adapters/comment_definition.rb
|
130
|
+
- lib/active_record/comments/connection_adapters/mysql2_adapter.rb
|
131
|
+
- lib/active_record/comments/connection_adapters/mysql_adapter.rb
|
132
|
+
- lib/active_record/comments/connection_adapters/postgresql_adapter.rb
|
133
|
+
- lib/active_record/comments/connection_adapters/sqlite3_adapter.rb
|
134
|
+
- lib/active_record/comments/connection_adapters/sqlite_adapter.rb
|
135
|
+
- lib/active_record/comments/version.rb
|
136
|
+
- spec/activerecord_comments_spec.rb
|
137
|
+
- spec/config/database.yml
|
138
|
+
- spec/spec_helper.rb
|
139
|
+
homepage: https://github.com/mcbuddha/activerecord-comments
|
140
|
+
licenses:
|
141
|
+
- MIT
|
142
|
+
metadata: {}
|
143
|
+
post_install_message:
|
144
|
+
rdoc_options: []
|
145
|
+
require_paths:
|
146
|
+
- lib
|
147
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
requirements: []
|
158
|
+
rubyforge_project:
|
159
|
+
rubygems_version: 2.2.2
|
160
|
+
signing_key:
|
161
|
+
specification_version: 4
|
162
|
+
summary: Comments for SQL schemas
|
163
|
+
test_files:
|
164
|
+
- spec/activerecord_comments_spec.rb
|
165
|
+
- spec/config/database.yml
|
166
|
+
- spec/spec_helper.rb
|