sequel-annotate 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|