sequel-annotate 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/CHANGELOG +3 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +73 -0
- data/Rakefile +46 -0
- data/lib/sequel/annotate.rb +211 -0
- data/spec/annotated/category.rb +12 -0
- data/spec/annotated/item.rb +23 -0
- data/spec/annotated/manufacturer.rb +12 -0
- data/spec/annotated/scategory.rb +7 -0
- data/spec/annotated/sitem.rb +18 -0
- data/spec/annotated/smanufacturer.rb +8 -0
- data/spec/sequel-annotate_spec.rb +181 -0
- data/spec/unannotated/category.rb +2 -0
- data/spec/unannotated/item.rb +3 -0
- data/spec/unannotated/manufacturer.rb +2 -0
- data/spec/unannotated/scategory.rb +2 -0
- data/spec/unannotated/sitem.rb +3 -0
- data/spec/unannotated/smanufacturer.rb +2 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eb5fcadf130d2d23412b4597e7e0b1fa4c2ef7c0
|
4
|
+
data.tar.gz: efa509bb8630ad280e036c47105d4f3fd84a8a54
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9ea01f3487452deeb82a3b34b5c2469c47d7e10e843dc66d60b68e4a762e841d21cbe73611ff3f400006dc5acbbc57db1a47598082200f5a132b1c2abd103e42
|
7
|
+
data.tar.gz: 945371efe3340ddd3a41f887572d6214b6940659a16cb9602cce9cec9cfd0f1ec104ef077a87b4658dff725bbd709cc131d09fda282e655cd441b794b948dffc
|
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2015 Jeremy Evans
|
2
|
+
Copyright (c) 2013-2015 Kenny Meyer
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
= sequel-annotate
|
2
|
+
|
3
|
+
sequel-annotate annotates Sequel models with schema information. By
|
4
|
+
default, it includes information on columns, indexes, and foreign key
|
5
|
+
constraints for the current table.
|
6
|
+
|
7
|
+
On PostgreSQL, this includes more advanced information, including
|
8
|
+
check constraints, triggers, and foreign keys constraints for other
|
9
|
+
tables that reference the current table.
|
10
|
+
|
11
|
+
== Example
|
12
|
+
|
13
|
+
The schema comments are kept at the end of the file, using a format similar to:
|
14
|
+
|
15
|
+
class Item < Sequel::Model
|
16
|
+
end
|
17
|
+
|
18
|
+
# Table: items
|
19
|
+
# Columns:
|
20
|
+
# id | integer | PRIMARY KEY DEFAULT nextval('items_id_seq'::regclass)
|
21
|
+
# category_id | integer | NOT NULL
|
22
|
+
# manufacturer_name | character varying(50) |
|
23
|
+
# manufacturer_location | text |
|
24
|
+
# in_stock | boolean | DEFAULT false
|
25
|
+
# name | text | DEFAULT 'John'::text
|
26
|
+
# price | double precision | DEFAULT 0
|
27
|
+
# Indexes:
|
28
|
+
# items_pkey | PRIMARY KEY btree (id)
|
29
|
+
# name | UNIQUE btree (manufacturer_name, manufacturer_location)
|
30
|
+
# manufacturer_name | btree (manufacturer_name)
|
31
|
+
# Check constraints:
|
32
|
+
# pos_id | (id > 0)
|
33
|
+
# Foreign key constraints:
|
34
|
+
# items_category_id_fkey | (category_id) REFERENCES categories(id)
|
35
|
+
# items_manufacturer_name_fkey | (manufacturer_name, manufacturer_location) REFERENCES manufacturers(name, location)
|
36
|
+
# Triggers:
|
37
|
+
# valid_price | BEFORE INSERT OR UPDATE ON items FOR EACH ROW EXECUTE PROCEDURE valid_price()
|
38
|
+
|
39
|
+
== Install
|
40
|
+
|
41
|
+
gem install sequel-annotate
|
42
|
+
|
43
|
+
== Usage
|
44
|
+
|
45
|
+
After loading the models:
|
46
|
+
|
47
|
+
require 'sequel/annotate'
|
48
|
+
Sequel::Annotate.annotate(Dir['models/*.rb'])
|
49
|
+
|
50
|
+
That will append or replace the schema comment in each of the files. It's best
|
51
|
+
to run this when the repository is clean, and then use your source control
|
52
|
+
tools (e.g. git diff) to see the changes it makes.
|
53
|
+
|
54
|
+
In some cases, sequel-annotate may not be able to correctly guess the
|
55
|
+
model for the file. In that case, you may need to create an instance manually:
|
56
|
+
|
57
|
+
sa = Sequel::Annotate.new(Item)
|
58
|
+
sa.annotate('models/item.rb')
|
59
|
+
|
60
|
+
If you want to get the schema comment for a model without appending it to
|
61
|
+
a file:
|
62
|
+
|
63
|
+
sa.schema_comment
|
64
|
+
|
65
|
+
== License
|
66
|
+
|
67
|
+
MIT
|
68
|
+
|
69
|
+
== Author
|
70
|
+
|
71
|
+
Jeremy Evans <code@jeremyevans.net>
|
72
|
+
|
73
|
+
Based on [annotate-sequel]{https://github.com/kennym/annotate-sequel} by Kenny Meyer.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "rake/clean"
|
3
|
+
|
4
|
+
CLEAN.include ["sequel-annotate-*.gem", "rdoc"]
|
5
|
+
|
6
|
+
desc "Build sequel-annotate gem"
|
7
|
+
task :package=>[:clean] do |p|
|
8
|
+
sh %{#{FileUtils::RUBY} -S gem build sequel-annotate.gemspec}
|
9
|
+
end
|
10
|
+
|
11
|
+
### Specs
|
12
|
+
|
13
|
+
desc "Run specs"
|
14
|
+
task :spec do
|
15
|
+
sh "#{FileUtils::RUBY} -rubygems -I lib spec/sequel-annotate_spec.rb"
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => :spec
|
19
|
+
|
20
|
+
### RDoc
|
21
|
+
|
22
|
+
RDOC_DEFAULT_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', 'sequel-annotate: Annotate Sequel models with schema information']
|
23
|
+
|
24
|
+
begin
|
25
|
+
gem 'rdoc'
|
26
|
+
gem 'hanna-nouveau'
|
27
|
+
RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
|
28
|
+
rescue Gem::LoadError
|
29
|
+
end
|
30
|
+
|
31
|
+
rdoc_task_class = begin
|
32
|
+
require "rdoc/task"
|
33
|
+
RDoc::Task
|
34
|
+
rescue LoadError
|
35
|
+
require "rake/rdoctask"
|
36
|
+
Rake::RDocTask
|
37
|
+
end
|
38
|
+
|
39
|
+
RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
|
40
|
+
|
41
|
+
rdoc_task_class.new do |rdoc|
|
42
|
+
rdoc.rdoc_dir = "rdoc"
|
43
|
+
rdoc.options += RDOC_OPTS
|
44
|
+
rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb"
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
class Annotate
|
5
|
+
# Append/replace the schema comment for all of the given files.
|
6
|
+
# Attempts to guess the model for each file using a regexp match of
|
7
|
+
# the file's content, if this doesn't work, you'll need to create
|
8
|
+
# an instance manually and pass in the model and path. Example:
|
9
|
+
#
|
10
|
+
# Sequel::Annotate.annotate(Dir['models/*.rb'])
|
11
|
+
def self.annotate(paths)
|
12
|
+
Sequel.extension :inflector
|
13
|
+
paths.each do |path|
|
14
|
+
if match = File.read(path).match(/class (\S+)\s*<\s*Sequel::Model/)
|
15
|
+
new(match[1].constantize).annotate(path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# The model to annotate
|
21
|
+
attr_reader :model
|
22
|
+
|
23
|
+
# Store the model to annotate
|
24
|
+
def initialize(model)
|
25
|
+
@model = model
|
26
|
+
end
|
27
|
+
|
28
|
+
# Append the schema comment (or replace it if one already exists) to
|
29
|
+
# the file at the given path.
|
30
|
+
def annotate(path)
|
31
|
+
orig = current = File.read(path).rstrip
|
32
|
+
|
33
|
+
if m = current.reverse.match(/#{"#{$/}# Table: ".reverse}/m)
|
34
|
+
offset = current.length - m.end(0) + 1
|
35
|
+
unless current[offset..-1].match(/^[^#]/)
|
36
|
+
# If Table: comment exists, and there are no
|
37
|
+
# uncommented lines between it and the end of the file
|
38
|
+
# then replace current comment instead of appending it
|
39
|
+
current = current[0...offset].rstrip
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
current += "#{$/}#{$/}#{schema_comment}"
|
44
|
+
|
45
|
+
if orig != current
|
46
|
+
File.open(path, "wb") do |f|
|
47
|
+
f.puts current
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# The schema comment to use for this model.
|
53
|
+
# For all databases, includes columns, indexes, and foreign
|
54
|
+
# key constraints in this table referencing other tables.
|
55
|
+
# On PostgreSQL, also includes check constraints, triggers,
|
56
|
+
# and foreign key constraints in other tables referencing this table.
|
57
|
+
def schema_comment
|
58
|
+
output = []
|
59
|
+
output << "# Table: #{model.table_name}"
|
60
|
+
|
61
|
+
meth = :"_schema_comment_#{model.db.database_type}"
|
62
|
+
if respond_to?(meth, true)
|
63
|
+
send(meth, output)
|
64
|
+
else
|
65
|
+
schema_comment_columns(output)
|
66
|
+
schema_comment_indexes(output)
|
67
|
+
schema_comment_foreign_keys(output)
|
68
|
+
end
|
69
|
+
|
70
|
+
output.join($/)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# Returns an array of strings for each array of array, such that
|
76
|
+
# each string is aligned and commented appropriately. Example:
|
77
|
+
#
|
78
|
+
# align([['abcdef', '1'], ['g', '123456']])
|
79
|
+
# # => ["# abcdef 1", "# g 123456"]
|
80
|
+
def align(rows)
|
81
|
+
cols = rows.first.length
|
82
|
+
lengths = [0] * cols
|
83
|
+
|
84
|
+
cols.times do |i|
|
85
|
+
rows.each do |r|
|
86
|
+
lengths[i] = r[i].length if r[i].length > lengths[i]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
rows.map do |r|
|
91
|
+
"# #{r.zip(lengths).map{|c, l| c.ljust(l)}.join(' | ')}".strip
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Use the standard columns schema output, but use PostgreSQL specific
|
96
|
+
# code for additional schema information.
|
97
|
+
def _schema_comment_postgres(output)
|
98
|
+
schema_comment_columns(output)
|
99
|
+
oid = model.db.send(:regclass_oid, model.table_name)
|
100
|
+
|
101
|
+
# These queries below are all based on the queries that psql
|
102
|
+
# uses, captured using the -E option to psql.
|
103
|
+
|
104
|
+
rows = model.db.fetch(<<SQL, :oid=>oid).all
|
105
|
+
SELECT c2.relname, i.indisprimary, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)
|
106
|
+
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
|
107
|
+
LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x'))
|
108
|
+
WHERE c.oid = :oid AND c.oid = i.indrelid AND i.indexrelid = c2.oid AND indisvalid
|
109
|
+
ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname;
|
110
|
+
SQL
|
111
|
+
unless rows.empty?
|
112
|
+
output << "# Indexes:"
|
113
|
+
rows = rows.map do |r|
|
114
|
+
[r[:relname], "#{"PRIMARY KEY " if r[:indisprimary]}#{"UNIQUE " if r[:indisunique] && !r[:indisprimary]}#{r[:pg_get_indexdef].match(/USING (.+)\z/)[1]}"]
|
115
|
+
end
|
116
|
+
output.concat(align(rows))
|
117
|
+
end
|
118
|
+
|
119
|
+
rows = model.db.fetch(<<SQL, :oid=>oid).all
|
120
|
+
SELECT r.conname, pg_catalog.pg_get_constraintdef(r.oid, true)
|
121
|
+
FROM pg_catalog.pg_constraint r
|
122
|
+
WHERE r.conrelid = :oid AND r.contype = 'c'
|
123
|
+
ORDER BY 1;
|
124
|
+
SQL
|
125
|
+
unless rows.empty?
|
126
|
+
output << "# Check constraints:"
|
127
|
+
rows = rows.map do |r|
|
128
|
+
[r[:conname], r[:pg_get_constraintdef].match(/CHECK (.+)\z/)[1]]
|
129
|
+
end
|
130
|
+
output.concat(align(rows))
|
131
|
+
end
|
132
|
+
|
133
|
+
rows = model.db.fetch(<<SQL, :oid=>oid).all
|
134
|
+
SELECT conname,
|
135
|
+
pg_catalog.pg_get_constraintdef(r.oid, true) as condef
|
136
|
+
FROM pg_catalog.pg_constraint r
|
137
|
+
WHERE r.conrelid = :oid AND r.contype = 'f' ORDER BY 1;
|
138
|
+
SQL
|
139
|
+
unless rows.empty?
|
140
|
+
output << "# Foreign key constraints:"
|
141
|
+
rows = rows.map do |r|
|
142
|
+
[r[:conname], r[:condef].match(/FOREIGN KEY (.+)\z/)[1]]
|
143
|
+
end
|
144
|
+
output.concat(align(rows))
|
145
|
+
end
|
146
|
+
|
147
|
+
rows = model.db.fetch(<<SQL, :oid=>oid).all
|
148
|
+
SELECT conname, conrelid::pg_catalog.regclass,
|
149
|
+
pg_catalog.pg_get_constraintdef(c.oid, true) as condef
|
150
|
+
FROM pg_catalog.pg_constraint c
|
151
|
+
WHERE c.confrelid = :oid AND c.contype = 'f' ORDER BY 2, 1;
|
152
|
+
SQL
|
153
|
+
unless rows.empty?
|
154
|
+
output << "# Referenced By:"
|
155
|
+
rows = rows.map do |r|
|
156
|
+
[r[:conrelid], r[:conname], r[:condef].match(/FOREIGN KEY (.+)\z/)[1]]
|
157
|
+
end
|
158
|
+
output.concat(align(rows))
|
159
|
+
end
|
160
|
+
|
161
|
+
rows = model.db.fetch(<<SQL, :oid=>oid).all
|
162
|
+
SELECT t.tgname, pg_catalog.pg_get_triggerdef(t.oid, true), t.tgenabled, t.tgisinternal
|
163
|
+
FROM pg_catalog.pg_trigger t
|
164
|
+
WHERE t.tgrelid = :oid AND (NOT t.tgisinternal OR (t.tgisinternal AND t.tgenabled = 'D'))
|
165
|
+
ORDER BY 1;
|
166
|
+
SQL
|
167
|
+
unless rows.empty?
|
168
|
+
output << "# Triggers:"
|
169
|
+
rows = rows.map do |r|
|
170
|
+
[r[:tgname], r[:pg_get_triggerdef].match(/((?:BEFORE|AFTER) .+)\z/)[1]]
|
171
|
+
end
|
172
|
+
output.concat(align(rows))
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# The standard column schema information to output.
|
177
|
+
def schema_comment_columns(output)
|
178
|
+
if cpk = model.primary_key.is_a?(Array)
|
179
|
+
output << "# Primary Key: (#{model.primary_key.join(', ')})"
|
180
|
+
end
|
181
|
+
output << "# Columns:"
|
182
|
+
rows = model.columns.map do |col|
|
183
|
+
sch = model.db_schema[col]
|
184
|
+
[col.to_s, sch[:db_type], "#{"PRIMARY KEY #{"AUTOINCREMENT " if sch[:auto_increment] && model.db.database_type != :postgres}" if sch[:primary_key] && !cpk}#{"NOT NULL " if sch[:allow_null] == false && !sch[:primary_key]}#{"DEFAULT #{sch[:default]}" if sch[:default]}"]
|
185
|
+
end
|
186
|
+
output.concat(align(rows))
|
187
|
+
end
|
188
|
+
|
189
|
+
# The standard index information to output.
|
190
|
+
def schema_comment_indexes(output)
|
191
|
+
unless (indexes = model.db.indexes(model.table_name)).empty?
|
192
|
+
output << "# Indexes:"
|
193
|
+
rows = indexes.map do |name, metadata|
|
194
|
+
[name.to_s, "#{'UNIQUE ' if metadata[:unique]}(#{metadata[:columns].join(', ')})"]
|
195
|
+
end
|
196
|
+
output.concat(align(rows).sort)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# The standard foreign key information to output.
|
201
|
+
def schema_comment_foreign_keys(output)
|
202
|
+
unless (fks = model.db.foreign_key_list(model.table_name)).empty?
|
203
|
+
output << "# Foreign key constraints:"
|
204
|
+
rows = fks.map do |fk|
|
205
|
+
["(#{fk[:columns].join(', ')}) REFERENCES #{fk[:table]}#{"(#{fk[:key].join(', ')})" if fk[:key]}"]
|
206
|
+
end
|
207
|
+
output.concat(align(rows).sort)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Category < Sequel::Model
|
2
|
+
end
|
3
|
+
|
4
|
+
# Table: categories
|
5
|
+
# Columns:
|
6
|
+
# id | integer | PRIMARY KEY DEFAULT nextval('categories_id_seq'::regclass)
|
7
|
+
# name | text | NOT NULL
|
8
|
+
# Indexes:
|
9
|
+
# categories_pkey | PRIMARY KEY btree (id)
|
10
|
+
# categories_name_key | UNIQUE btree (name)
|
11
|
+
# Referenced By:
|
12
|
+
# items | items_category_id_fkey | (category_id) REFERENCES categories(id)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Item < Sequel::Model
|
2
|
+
end
|
3
|
+
|
4
|
+
# Table: items
|
5
|
+
# Columns:
|
6
|
+
# id | integer | PRIMARY KEY DEFAULT nextval('items_id_seq'::regclass)
|
7
|
+
# category_id | integer | NOT NULL
|
8
|
+
# manufacturer_name | character varying(50) |
|
9
|
+
# manufacturer_location | text |
|
10
|
+
# in_stock | boolean | DEFAULT false
|
11
|
+
# name | text | DEFAULT 'John'::text
|
12
|
+
# price | double precision | DEFAULT 0
|
13
|
+
# Indexes:
|
14
|
+
# items_pkey | PRIMARY KEY btree (id)
|
15
|
+
# name | UNIQUE btree (manufacturer_name, manufacturer_location)
|
16
|
+
# manufacturer_name | btree (manufacturer_name)
|
17
|
+
# Check constraints:
|
18
|
+
# pos_id | (id > 0)
|
19
|
+
# Foreign key constraints:
|
20
|
+
# items_category_id_fkey | (category_id) REFERENCES categories(id)
|
21
|
+
# items_manufacturer_name_fkey | (manufacturer_name, manufacturer_location) REFERENCES manufacturers(name, location)
|
22
|
+
# Triggers:
|
23
|
+
# valid_price | BEFORE INSERT OR UPDATE ON items FOR EACH ROW EXECUTE PROCEDURE valid_price()
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Manufacturer < Sequel::Model
|
2
|
+
end
|
3
|
+
|
4
|
+
# Table: manufacturers
|
5
|
+
# Primary Key: (name, location)
|
6
|
+
# Columns:
|
7
|
+
# name | text |
|
8
|
+
# location | text |
|
9
|
+
# Indexes:
|
10
|
+
# manufacturers_pkey | PRIMARY KEY btree (name, location)
|
11
|
+
# Referenced By:
|
12
|
+
# items | items_manufacturer_name_fkey | (manufacturer_name, manufacturer_location) REFERENCES manufacturers(name, location)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class SItem < Sequel::Model(SDB[:items])
|
2
|
+
end
|
3
|
+
|
4
|
+
# Table: items
|
5
|
+
# Columns:
|
6
|
+
# id | integer | PRIMARY KEY AUTOINCREMENT
|
7
|
+
# category_id | integer | NOT NULL
|
8
|
+
# manufacturer_name | varchar(50) |
|
9
|
+
# manufacturer_location | varchar(255) |
|
10
|
+
# in_stock | boolean | DEFAULT 0
|
11
|
+
# name | varchar(255) | DEFAULT 'John'
|
12
|
+
# price | double precision | DEFAULT 0
|
13
|
+
# Indexes:
|
14
|
+
# manufacturer_name | (manufacturer_name)
|
15
|
+
# name | UNIQUE (manufacturer_name, manufacturer_location)
|
16
|
+
# Foreign key constraints:
|
17
|
+
# (category_id) REFERENCES categories
|
18
|
+
# (manufacturer_name, manufacturer_location) REFERENCES manufacturers
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fileutils'
|
3
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), '../lib/sequel/annotate')
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
pg_user = ENV['PGUSER'] || 'postgres'
|
7
|
+
db_name = ENV['SEQUEL_ANNOTATE_DB'] || 'sequel-annotate_spec'
|
8
|
+
system("dropdb", "-U", pg_user, db_name)
|
9
|
+
system("createdb", "-U", pg_user, db_name)
|
10
|
+
DB = Sequel.postgres(db_name, :user=>pg_user)
|
11
|
+
SDB = Sequel.sqlite
|
12
|
+
|
13
|
+
[DB, SDB].each do |db|
|
14
|
+
db.create_table :categories do
|
15
|
+
primary_key :id
|
16
|
+
String :name, :unique=>true, :null=>false
|
17
|
+
end
|
18
|
+
|
19
|
+
db.create_table :manufacturers do
|
20
|
+
String :name
|
21
|
+
String :location
|
22
|
+
primary_key [:name, :location]
|
23
|
+
end
|
24
|
+
|
25
|
+
db.create_table :items do
|
26
|
+
primary_key :id
|
27
|
+
foreign_key :category_id, :categories, :null => false
|
28
|
+
foreign_key [:manufacturer_name, :manufacturer_location], :manufacturers
|
29
|
+
|
30
|
+
String :manufacturer_name, :size => 50
|
31
|
+
String :manufacturer_location
|
32
|
+
TrueClass :in_stock, :default => false
|
33
|
+
String :name, :default => "John"
|
34
|
+
Float :price, :default => 0
|
35
|
+
|
36
|
+
constraint :pos_id, Sequel.expr(:id) > 0
|
37
|
+
|
38
|
+
index [:manufacturer_name, :manufacturer_location], :name=>:name, :unique=>true
|
39
|
+
index [:manufacturer_name], :name=>:manufacturer_name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
DB.run <<SQL
|
44
|
+
CREATE FUNCTION valid_price() RETURNS trigger AS $emp_stamp$
|
45
|
+
BEGIN
|
46
|
+
-- Check that empname and salary are given
|
47
|
+
IF NEW.price > 1000000 THEN
|
48
|
+
RAISE EXCEPTION 'price is too high';
|
49
|
+
END IF;
|
50
|
+
RETURN NEW;
|
51
|
+
END;
|
52
|
+
$emp_stamp$ LANGUAGE plpgsql;
|
53
|
+
SQL
|
54
|
+
|
55
|
+
DB.run <<SQL
|
56
|
+
CREATE TRIGGER valid_price BEFORE INSERT OR UPDATE ON items
|
57
|
+
FOR EACH ROW EXECUTE PROCEDURE valid_price();
|
58
|
+
SQL
|
59
|
+
|
60
|
+
class ::Item < Sequel::Model; end
|
61
|
+
class ::Category < Sequel::Model; end
|
62
|
+
class ::Manufacturer < Sequel::Model; end
|
63
|
+
class ::SItem < Sequel::Model(SDB[:items]); end
|
64
|
+
class ::SCategory < Sequel::Model(SDB[:categories]); end
|
65
|
+
class ::SManufacturer < Sequel::Model(SDB[:manufacturers]); end
|
66
|
+
|
67
|
+
|
68
|
+
describe Sequel::Annotate do
|
69
|
+
before do
|
70
|
+
File.mkdir('spec/tmp') unless File.directory?('spec/tmp')
|
71
|
+
end
|
72
|
+
after do
|
73
|
+
Dir['spec/tmp/*.rb'].each{|f| File.delete(f)}
|
74
|
+
end
|
75
|
+
|
76
|
+
it "#schema_info should return the model schema comment" do
|
77
|
+
Sequel::Annotate.new(Item).schema_comment.must_equal((<<OUTPUT).chomp)
|
78
|
+
# Table: items
|
79
|
+
# Columns:
|
80
|
+
# id | integer | PRIMARY KEY DEFAULT nextval('items_id_seq'::regclass)
|
81
|
+
# category_id | integer | NOT NULL
|
82
|
+
# manufacturer_name | character varying(50) |
|
83
|
+
# manufacturer_location | text |
|
84
|
+
# in_stock | boolean | DEFAULT false
|
85
|
+
# name | text | DEFAULT 'John'::text
|
86
|
+
# price | double precision | DEFAULT 0
|
87
|
+
# Indexes:
|
88
|
+
# items_pkey | PRIMARY KEY btree (id)
|
89
|
+
# name | UNIQUE btree (manufacturer_name, manufacturer_location)
|
90
|
+
# manufacturer_name | btree (manufacturer_name)
|
91
|
+
# Check constraints:
|
92
|
+
# pos_id | (id > 0)
|
93
|
+
# Foreign key constraints:
|
94
|
+
# items_category_id_fkey | (category_id) REFERENCES categories(id)
|
95
|
+
# items_manufacturer_name_fkey | (manufacturer_name, manufacturer_location) REFERENCES manufacturers(name, location)
|
96
|
+
# Triggers:
|
97
|
+
# valid_price | BEFORE INSERT OR UPDATE ON items FOR EACH ROW EXECUTE PROCEDURE valid_price()
|
98
|
+
OUTPUT
|
99
|
+
|
100
|
+
Sequel::Annotate.new(Category).schema_comment.must_equal((<<OUTPUT).chomp)
|
101
|
+
# Table: categories
|
102
|
+
# Columns:
|
103
|
+
# id | integer | PRIMARY KEY DEFAULT nextval('categories_id_seq'::regclass)
|
104
|
+
# name | text | NOT NULL
|
105
|
+
# Indexes:
|
106
|
+
# categories_pkey | PRIMARY KEY btree (id)
|
107
|
+
# categories_name_key | UNIQUE btree (name)
|
108
|
+
# Referenced By:
|
109
|
+
# items | items_category_id_fkey | (category_id) REFERENCES categories(id)
|
110
|
+
OUTPUT
|
111
|
+
|
112
|
+
Sequel::Annotate.new(Manufacturer).schema_comment.must_equal((<<OUTPUT).chomp)
|
113
|
+
# Table: manufacturers
|
114
|
+
# Primary Key: (name, location)
|
115
|
+
# Columns:
|
116
|
+
# name | text |
|
117
|
+
# location | text |
|
118
|
+
# Indexes:
|
119
|
+
# manufacturers_pkey | PRIMARY KEY btree (name, location)
|
120
|
+
# Referenced By:
|
121
|
+
# items | items_manufacturer_name_fkey | (manufacturer_name, manufacturer_location) REFERENCES manufacturers(name, location)
|
122
|
+
OUTPUT
|
123
|
+
|
124
|
+
Sequel::Annotate.new(SItem).schema_comment.must_equal((<<OUTPUT).chomp)
|
125
|
+
# Table: items
|
126
|
+
# Columns:
|
127
|
+
# id | integer | PRIMARY KEY AUTOINCREMENT
|
128
|
+
# category_id | integer | NOT NULL
|
129
|
+
# manufacturer_name | varchar(50) |
|
130
|
+
# manufacturer_location | varchar(255) |
|
131
|
+
# in_stock | boolean | DEFAULT 0
|
132
|
+
# name | varchar(255) | DEFAULT 'John'
|
133
|
+
# price | double precision | DEFAULT 0
|
134
|
+
# Indexes:
|
135
|
+
# manufacturer_name | (manufacturer_name)
|
136
|
+
# name | UNIQUE (manufacturer_name, manufacturer_location)
|
137
|
+
# Foreign key constraints:
|
138
|
+
# (category_id) REFERENCES categories
|
139
|
+
# (manufacturer_name, manufacturer_location) REFERENCES manufacturers
|
140
|
+
OUTPUT
|
141
|
+
|
142
|
+
Sequel::Annotate.new(SCategory).schema_comment.must_equal((<<OUTPUT).chomp)
|
143
|
+
# Table: categories
|
144
|
+
# Columns:
|
145
|
+
# id | integer | PRIMARY KEY AUTOINCREMENT
|
146
|
+
# name | varchar(255) | NOT NULL
|
147
|
+
OUTPUT
|
148
|
+
|
149
|
+
Sequel::Annotate.new(SManufacturer).schema_comment.must_equal((<<OUTPUT).chomp)
|
150
|
+
# Table: manufacturers
|
151
|
+
# Primary Key: (name, location)
|
152
|
+
# Columns:
|
153
|
+
# name | varchar(255) |
|
154
|
+
# location | varchar(255) |
|
155
|
+
OUTPUT
|
156
|
+
end
|
157
|
+
|
158
|
+
it "#annotate should annotate the file comment" do
|
159
|
+
FileUtils.cp(Dir['spec/unannotated/*.rb'], 'spec/tmp')
|
160
|
+
|
161
|
+
[Item, Category, Manufacturer, SItem, SCategory, SManufacturer].each do |model|
|
162
|
+
filename = model.name.downcase
|
163
|
+
2.times do
|
164
|
+
Sequel::Annotate.new(model).annotate("spec/tmp/#{filename}.rb")
|
165
|
+
File.read("spec/tmp/#{filename}.rb").must_equal File.read("spec/annotated/#{filename}.rb")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
it ".annotate should annotate all files given" do
|
171
|
+
FileUtils.cp(Dir['spec/unannotated/*.rb'], 'spec/tmp')
|
172
|
+
|
173
|
+
2.times do
|
174
|
+
Sequel::Annotate.annotate(Dir["spec/tmp/*.rb"])
|
175
|
+
[Item, Category, Manufacturer, SItem, SCategory, SManufacturer].each do |model|
|
176
|
+
filename = model.name.downcase
|
177
|
+
File.read("spec/tmp/#{filename}.rb").must_equal File.read("spec/annotated/#{filename}.rb")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sequel-annotate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Evans
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sequel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pg
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: |
|
70
|
+
sequel-annotate annotates Sequel models with schema information. By
|
71
|
+
default, it includes information on columns, indexes, and foreign key
|
72
|
+
constraints for the current table.
|
73
|
+
|
74
|
+
On PostgreSQL, this includes more advanced information, including
|
75
|
+
check constraints, triggers, and foreign keys constraints for other
|
76
|
+
tables that reference the current table.
|
77
|
+
email: code@jeremyevans.net
|
78
|
+
executables: []
|
79
|
+
extensions: []
|
80
|
+
extra_rdoc_files:
|
81
|
+
- README.rdoc
|
82
|
+
- CHANGELOG
|
83
|
+
- MIT-LICENSE
|
84
|
+
files:
|
85
|
+
- CHANGELOG
|
86
|
+
- MIT-LICENSE
|
87
|
+
- README.rdoc
|
88
|
+
- Rakefile
|
89
|
+
- lib/sequel/annotate.rb
|
90
|
+
- spec/annotated/category.rb
|
91
|
+
- spec/annotated/item.rb
|
92
|
+
- spec/annotated/manufacturer.rb
|
93
|
+
- spec/annotated/scategory.rb
|
94
|
+
- spec/annotated/sitem.rb
|
95
|
+
- spec/annotated/smanufacturer.rb
|
96
|
+
- spec/sequel-annotate_spec.rb
|
97
|
+
- spec/unannotated/category.rb
|
98
|
+
- spec/unannotated/item.rb
|
99
|
+
- spec/unannotated/manufacturer.rb
|
100
|
+
- spec/unannotated/scategory.rb
|
101
|
+
- spec/unannotated/sitem.rb
|
102
|
+
- spec/unannotated/smanufacturer.rb
|
103
|
+
homepage: http://github.com/jeremyevans/sequel-annotate
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options:
|
109
|
+
- "--quiet"
|
110
|
+
- "--line-numbers"
|
111
|
+
- "--inline-source"
|
112
|
+
- "--title"
|
113
|
+
- 'sequel-annotate: Annotate Sequel models with schema information'
|
114
|
+
- "--main"
|
115
|
+
- README.rdoc
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 1.8.7
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.4.5.1
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: Annotate Sequel models with schema information
|
134
|
+
test_files: []
|