dbsketch 0.0.1
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/LICENSE +674 -0
- data/README.md +1 -0
- data/lib/dbsketch.rb +64 -0
- data/lib/dbsketch/automation/automation_error.rb +15 -0
- data/lib/dbsketch/automation/database_connection_details.rb +32 -0
- data/lib/dbsketch/automation/database_importer.rb +184 -0
- data/lib/dbsketch/automation/database_proxy.rb +78 -0
- data/lib/dbsketch/automation/table_importer.rb +114 -0
- data/lib/dbsketch/comparison/check_constraint_comparator.rb +54 -0
- data/lib/dbsketch/comparison/column_comparator.rb +65 -0
- data/lib/dbsketch/comparison/comparison_error.rb +15 -0
- data/lib/dbsketch/comparison/computed_column_comparator.rb +63 -0
- data/lib/dbsketch/comparison/database_comparator.rb +115 -0
- data/lib/dbsketch/comparison/diff.rb +37 -0
- data/lib/dbsketch/comparison/foreign_key_comparator.rb +56 -0
- data/lib/dbsketch/comparison/function_comparator.rb +56 -0
- data/lib/dbsketch/comparison/index_comparator.rb +65 -0
- data/lib/dbsketch/comparison/primary_key_comparator.rb +61 -0
- data/lib/dbsketch/comparison/procedure_comparator.rb +54 -0
- data/lib/dbsketch/comparison/table_comparator.rb +158 -0
- data/lib/dbsketch/comparison/trigger_comparator.rb +53 -0
- data/lib/dbsketch/comparison/type_comparator.rb +51 -0
- data/lib/dbsketch/comparison/unique_constraint_comparator.rb +58 -0
- data/lib/dbsketch/comparison/view_comparator.rb +54 -0
- data/lib/dbsketch/model/abstract_column.rb +25 -0
- data/lib/dbsketch/model/check_constraint.rb +23 -0
- data/lib/dbsketch/model/column.rb +35 -0
- data/lib/dbsketch/model/computed_column.rb +27 -0
- data/lib/dbsketch/model/custom_code.rb +21 -0
- data/lib/dbsketch/model/database.rb +87 -0
- data/lib/dbsketch/model/database_object.rb +71 -0
- data/lib/dbsketch/model/foreign_key.rb +29 -0
- data/lib/dbsketch/model/function.rb +23 -0
- data/lib/dbsketch/model/index.rb +40 -0
- data/lib/dbsketch/model/model_error.rb +15 -0
- data/lib/dbsketch/model/operation.rb +28 -0
- data/lib/dbsketch/model/primary_key.rb +33 -0
- data/lib/dbsketch/model/procedure.rb +18 -0
- data/lib/dbsketch/model/table.rb +171 -0
- data/lib/dbsketch/model/trigger.rb +32 -0
- data/lib/dbsketch/model/type.rb +92 -0
- data/lib/dbsketch/model/unique_constraint.rb +33 -0
- data/lib/dbsketch/model/view.rb +23 -0
- data/lib/dbsketch/rendering/meta/column_renderer.rb +76 -0
- data/lib/dbsketch/rendering/meta/database_renderer.rb +112 -0
- data/lib/dbsketch/rendering/meta/foreign_key_renderer.rb +31 -0
- data/lib/dbsketch/rendering/meta/index_renderer.rb +30 -0
- data/lib/dbsketch/rendering/meta/operation_renderer.rb +66 -0
- data/lib/dbsketch/rendering/meta/table_renderer.rb +177 -0
- data/lib/dbsketch/rendering/meta/trigger_renderer.rb +31 -0
- data/lib/dbsketch/rendering/meta/type_renderer.rb +37 -0
- data/lib/dbsketch/rendering/meta/view_renderer.rb +32 -0
- data/lib/dbsketch/rendering/sql/column_renderer.rb +75 -0
- data/lib/dbsketch/rendering/sql/database_diff_renderer.rb +94 -0
- data/lib/dbsketch/rendering/sql/database_renderer.rb +139 -0
- data/lib/dbsketch/rendering/sql/foreign_key_renderer.rb +24 -0
- data/lib/dbsketch/rendering/sql/index_renderer.rb +31 -0
- data/lib/dbsketch/rendering/sql/operation_renderer.rb +64 -0
- data/lib/dbsketch/rendering/sql/table_renderer.rb +148 -0
- data/lib/dbsketch/rendering/sql/trigger_renderer.rb +31 -0
- data/lib/dbsketch/rendering/sql/type_renderer.rb +28 -0
- data/lib/dbsketch/rendering/sql/view_renderer.rb +31 -0
- data/lib/dbsketch/version.rb +3 -0
- metadata +134 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Database differences between two existing unique constraint
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative '../model/unique_constraint'
|
6
|
+
require_relative 'diff'
|
7
|
+
|
8
|
+
module Dbsketch
|
9
|
+
module Comparison
|
10
|
+
|
11
|
+
class UniqueConstraintDiff < Diff
|
12
|
+
def initialize old_constraint, new_constraint, columns
|
13
|
+
super old_constraint, new_constraint
|
14
|
+
### Preconditions
|
15
|
+
columns.each_with_index { |column, index| raise ArgumentError, "columns[#{index}] is not a Dbsketch::Comparison::Diff" unless column.is_a? Dbsketch::Comparison::Diff }
|
16
|
+
###
|
17
|
+
@columns = columns
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :columns
|
21
|
+
alias :old_constraint :old_value
|
22
|
+
alias :new_constraint :new_value
|
23
|
+
end
|
24
|
+
|
25
|
+
class UniqueConstraintComparator
|
26
|
+
|
27
|
+
def are_equivalent? old_constraint, new_constraint
|
28
|
+
### Preconditions
|
29
|
+
raise ArgumentError, "old_constraint is not a Dbsketch::Model::UniqueConstraint" unless nil == old_constraint or old_constraint.is_a? Dbsketch::Model::UniqueConstraint
|
30
|
+
raise ArgumentError, "new_constraint is not a Dbsketch::Model::UniqueConstraint" unless nil == new_constraint or new_constraint.is_a? Dbsketch::Model::UniqueConstraint
|
31
|
+
###
|
32
|
+
(nil != old_constraint and nil != new_constraint) and columns(old_constraint, new_constraint).empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a UniqueConstraintDiff if tables are different, nil otherwise
|
36
|
+
def compare old_constraint, new_constraint
|
37
|
+
### Preconditions
|
38
|
+
raise ArgumentError, "old_constraint is not a Dbsketch::Model::UniqueConstraint" unless nil == old_constraint or old_constraint.is_a? Dbsketch::Model::UniqueConstraint
|
39
|
+
raise ArgumentError, "new_constraint is not a Dbsketch::Model::UniqueConstraint" unless nil == new_constraint or new_constraint.is_a? Dbsketch::Model::UniqueConstraint
|
40
|
+
###
|
41
|
+
UniqueConstraintDiff.new(old_constraint, new_constraint, columns(old_constraint, new_constraint)) if not are_equivalent? old_constraint, new_constraint
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def columns old_constraint, new_constraint
|
47
|
+
diffs = []
|
48
|
+
if nil != old_constraint and nil != new_constraint
|
49
|
+
diffs << old_constraint.columns.select { |old_c| not new_constraint.has_column? old_c.name }.map { |old_c| Diff.new old_c.name, nil }
|
50
|
+
diffs << new_constraint.columns.select { |new_c| not old_constraint.has_column? new_c.name }.map { |new_c| Diff.new nil, new_c.name }
|
51
|
+
end
|
52
|
+
diffs.flatten.compact
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Compute differences between two existing check constraint
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative '../model/view'
|
6
|
+
require_relative 'comparison_error'
|
7
|
+
require_relative 'diff'
|
8
|
+
|
9
|
+
module Dbsketch
|
10
|
+
module Comparison
|
11
|
+
|
12
|
+
class ViewDiff < Diff
|
13
|
+
def initialize old_view, new_view
|
14
|
+
super old_view, new_view
|
15
|
+
end
|
16
|
+
alias :old_view :old_value
|
17
|
+
alias :new_view :new_value
|
18
|
+
|
19
|
+
def query
|
20
|
+
### Preconditions
|
21
|
+
raise ComparisonError, "no change found" if not change?
|
22
|
+
###
|
23
|
+
Diff.new old_view.query, new_view.query
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Dbsketch
|
31
|
+
module Comparison
|
32
|
+
|
33
|
+
class ViewComparator
|
34
|
+
|
35
|
+
def are_equivalent? old_view, new_view
|
36
|
+
### Preconditions
|
37
|
+
raise ArgumentError, "old_view is not a Dbsketch::Model::View" unless nil == old_view or old_view.is_a? Dbsketch::Model::View
|
38
|
+
raise ArgumentError, "new_view is not a Dbsketch::Model::View" unless nil == new_view or new_view.is_a? Dbsketch::Model::View
|
39
|
+
###
|
40
|
+
(nil != old_view and nil != new_view) and (old_view.query == new_view.query)
|
41
|
+
end
|
42
|
+
|
43
|
+
def compare old_view, new_view
|
44
|
+
### Preconditions
|
45
|
+
raise ArgumentError, "old_view is not a Dbsketch::Model::View" unless nil == old_view or old_view.is_a? Dbsketch::Model::View
|
46
|
+
raise ArgumentError, "new_view is not a Dbsketch::Model::View" unless nil == new_view or new_view.is_a? Dbsketch::Model::View
|
47
|
+
###
|
48
|
+
ViewDiff.new(old_view, new_view) if not are_equivalent? old_view, new_view
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Common structure for database columns
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative 'database_object'
|
6
|
+
|
7
|
+
module Dbsketch
|
8
|
+
module Model
|
9
|
+
|
10
|
+
class AbstractColumn < Database_Object
|
11
|
+
def initialize name, meaning: nil, comment: nil, nullable: true, order: nil
|
12
|
+
super name, :meaning => meaning, :comment => comment
|
13
|
+
### Preconditions
|
14
|
+
raise ArgumentError, "nullable is not a boolean" unless nullable.is_a? TrueClass or nullable.is_a? FalseClass
|
15
|
+
raise ArgumentError, "order is not an Integer" unless nil == order or order.is_a? Integer
|
16
|
+
###
|
17
|
+
@nullable = nullable
|
18
|
+
@order = order
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :nullable, :order
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Check constraint
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative 'database_object'
|
6
|
+
|
7
|
+
module Dbsketch
|
8
|
+
module Model
|
9
|
+
|
10
|
+
class CheckConstraint < Database_Object
|
11
|
+
def initialize name, condition, meaning: nil, comment: nil
|
12
|
+
super name, :meaning => meaning, :comment => comment
|
13
|
+
### Preconditions
|
14
|
+
raise ArgumentError, "condition is not a String" unless condition.is_a? String
|
15
|
+
###
|
16
|
+
@condition = condition
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :condition
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Defines database column
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative 'abstract_column'
|
6
|
+
require_relative 'type'
|
7
|
+
|
8
|
+
module Dbsketch
|
9
|
+
module Model
|
10
|
+
|
11
|
+
class Column < AbstractColumn
|
12
|
+
def initialize name, type, meaning: nil, comment: nil, nullable: true, order: nil, identity: false, default: nil
|
13
|
+
super(name, :meaning => meaning, :comment => comment, :nullable => nullable, :order => order)
|
14
|
+
### Preconditions
|
15
|
+
raise ArgumentError, "type is not a Dbsketch::Model::Type" unless type.is_a? Dbsketch::Model::Type
|
16
|
+
raise ArgumentError, "identity is not a boolean" unless identity.is_a? TrueClass or identity.is_a? FalseClass
|
17
|
+
###
|
18
|
+
@type = type
|
19
|
+
@identity = identity
|
20
|
+
@default = default
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :type, :identity, :default
|
24
|
+
attr_writer :default
|
25
|
+
|
26
|
+
def compatible_with? other_column
|
27
|
+
### Preconditions
|
28
|
+
raise ArgumentError, "other_column is not a Dbsketch::Model::Column" unless other_column.is_a? Column
|
29
|
+
###
|
30
|
+
(@type.compatible_with? other_column.type) and (not @nullable or other_column.nullable)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Defines database computed column
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative 'abstract_column'
|
6
|
+
require_relative 'model_error'
|
7
|
+
|
8
|
+
module Dbsketch
|
9
|
+
module Model
|
10
|
+
|
11
|
+
class ComputedColumn < AbstractColumn
|
12
|
+
def initialize name, query, meaning: nil, comment: nil, nullable: true, order: nil, persisted: false
|
13
|
+
super(name, :meaning => meaning, :comment => comment, :nullable => nullable, :order => order)
|
14
|
+
### Preconditions
|
15
|
+
raise ArgumentError, "query is not a String" unless query.is_a? String
|
16
|
+
raise ArgumentError, "persisted is not a boolean" unless persisted.is_a? TrueClass or persisted.is_a? FalseClass
|
17
|
+
raise ModelError, "column is not persisted althought not nullable" if not nullable and not persisted
|
18
|
+
###
|
19
|
+
@query = query
|
20
|
+
@persisted = persisted
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :query, :persisted
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Defines database custom code, so developers can write specific code
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative 'database_object'
|
6
|
+
|
7
|
+
module Dbsketch
|
8
|
+
module Model
|
9
|
+
|
10
|
+
class CustomCode < Database_Object
|
11
|
+
def initialize name, do_code, undo_code: nil, meaning: nil, comment: nil, dependencies: []
|
12
|
+
super name, :meaning => meaning, :comment => comment, :dependencies => dependencies
|
13
|
+
@do_code = do_code
|
14
|
+
@undo_code = undo_code
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :do_code, :undo_code
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Database representation
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative 'custom_code'
|
6
|
+
require_relative 'index'
|
7
|
+
require_relative 'model_error'
|
8
|
+
require_relative 'operation'
|
9
|
+
require_relative 'table'
|
10
|
+
require_relative 'trigger'
|
11
|
+
require_relative 'view'
|
12
|
+
|
13
|
+
module Dbsketch
|
14
|
+
module Model
|
15
|
+
|
16
|
+
class Database
|
17
|
+
def initialize
|
18
|
+
@items = []
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :items
|
22
|
+
|
23
|
+
def add object
|
24
|
+
objects = object.is_a?(Array) ? object : [object]
|
25
|
+
### Preconditions
|
26
|
+
objects.each_with_index do |o, index|
|
27
|
+
raise ArgumentError, "object n°#{index + 1} is not a Dbsketch::Model::CustomCode, Index, Operation, Table nor View" unless o.is_a? CustomCode or o.is_a? Index or o.is_a? Operation or o.is_a? Table or o.is_a? Trigger or o.is_a? View
|
28
|
+
raise ModelError, "object #{o.name} already exists" if nil != (@items.find { |i| i.name.downcase == o.name.downcase })
|
29
|
+
end
|
30
|
+
###
|
31
|
+
@items = @items + objects
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_by_class given_class
|
35
|
+
@items.select { |i| i.is_a? given_class }
|
36
|
+
end
|
37
|
+
|
38
|
+
def custom_code
|
39
|
+
get_by_class CustomCode
|
40
|
+
end
|
41
|
+
|
42
|
+
def indexes
|
43
|
+
get_by_class Index
|
44
|
+
end
|
45
|
+
|
46
|
+
def operations
|
47
|
+
get_by_class Operation
|
48
|
+
end
|
49
|
+
|
50
|
+
def tables
|
51
|
+
get_by_class Table
|
52
|
+
end
|
53
|
+
|
54
|
+
def triggers
|
55
|
+
get_by_class Trigger
|
56
|
+
end
|
57
|
+
|
58
|
+
def views
|
59
|
+
get_by_class View
|
60
|
+
end
|
61
|
+
|
62
|
+
def has_item? item_name
|
63
|
+
### Preconditions
|
64
|
+
raise ArgumentError, "item_name is not a String" unless item_name.is_a? String
|
65
|
+
###
|
66
|
+
nil != @items.find { |t| t.name.downcase == item_name.downcase }
|
67
|
+
end
|
68
|
+
|
69
|
+
def [] item_name
|
70
|
+
### Preconditions
|
71
|
+
raise ArgumentError, "item_name is not a String" unless item_name.is_a? String
|
72
|
+
###
|
73
|
+
item = @items.find { |t| t.name.downcase == item_name.downcase }
|
74
|
+
raise ModelError, "item #{item_name} not found in database" if nil == item
|
75
|
+
item
|
76
|
+
end
|
77
|
+
|
78
|
+
def order_items!
|
79
|
+
@items.map! { |i| i.reset_order!; i }
|
80
|
+
@items.map! { |i| i.compute_order!; i }
|
81
|
+
@items.sort! { |a,b| (a.order != b.order) ? (a.order <=> b.order) : (a.name <=> b.name) }
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Defines database common object
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative 'model_error'
|
6
|
+
|
7
|
+
module Dbsketch
|
8
|
+
module Model
|
9
|
+
|
10
|
+
class Database_Object
|
11
|
+
def initialize name, meaning: nil, comment: nil, dependencies: []
|
12
|
+
dependencies = dependencies.is_a?(Array) ? dependencies : [dependencies]
|
13
|
+
### Preconditions
|
14
|
+
raise ArgumentError, "name is not a String" unless name.is_a? String
|
15
|
+
raise ModelError, "name (#{name}) shall not contain special chars (apart from underscore)" if name.match(/\W/)
|
16
|
+
raise ArgumentError, "meaning is not a String" unless nil == meaning or meaning.is_a? String
|
17
|
+
raise ArgumentError, "comment is not a String" unless nil == comment or comment.is_a? String
|
18
|
+
dependencies.each_with_index do |d, index|
|
19
|
+
raise ArgumentError, "dependencies[#{index}] is not a Dbsketch::Model::Database_Object" unless d.is_a? Database_Object
|
20
|
+
end
|
21
|
+
###
|
22
|
+
@name = name
|
23
|
+
@meaning = meaning
|
24
|
+
@comment = comment
|
25
|
+
@dependencies = dependencies
|
26
|
+
@order = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :name, :meaning, :comment, :dependencies, :order
|
30
|
+
|
31
|
+
def add_dependencies dependencies
|
32
|
+
dependencies = dependencies.is_a?(Array) ? dependencies : [dependencies]
|
33
|
+
### Preconditions
|
34
|
+
dependencies.each_with_index do |d, index|
|
35
|
+
raise ArgumentError, "dependencies[#{index}] is not a Dbsketch::Model::Database_Object" unless d.is_a? Database_Object
|
36
|
+
end
|
37
|
+
###
|
38
|
+
@dependencies = (@dependencies.concat(dependencies)).uniq
|
39
|
+
end
|
40
|
+
|
41
|
+
def class_name
|
42
|
+
self.class.name.gsub(/^Dbsketch::Model::/, '')
|
43
|
+
end
|
44
|
+
|
45
|
+
def inspect
|
46
|
+
"#{self.class_name}:#{object_id} #{@name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def reset_order!
|
50
|
+
@order = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def compute_order! trace = []
|
54
|
+
if nil == @order
|
55
|
+
current_trace = trace.clone
|
56
|
+
current_trace << self
|
57
|
+
if nil != trace.find { |t| t.object_id == self.object_id }
|
58
|
+
raise ModelError, "circular dependencies found. Trace: #{current_trace.map { |t| "#{t.class_name} #{t.name}" }.join(" < ")}"
|
59
|
+
end
|
60
|
+
if @dependencies.empty?
|
61
|
+
@order = 1
|
62
|
+
else
|
63
|
+
@order = @dependencies.map {|d| d.compute_order! current_trace }.max + 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
@order
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
########################################################################################################################
|
2
|
+
# Primary Key
|
3
|
+
########################################################################################################################
|
4
|
+
|
5
|
+
require_relative 'column'
|
6
|
+
require_relative 'database_object'
|
7
|
+
require_relative 'table'
|
8
|
+
|
9
|
+
module Dbsketch
|
10
|
+
module Model
|
11
|
+
|
12
|
+
class ForeignKey < Database_Object
|
13
|
+
def initialize name, constricted_column, referenced_table, referenced_column, meaning: nil, comment: nil
|
14
|
+
super name, :meaning => meaning, :comment => comment
|
15
|
+
### Preconditions
|
16
|
+
raise ArgumentError, "constricted column is not a Dbsketch::Model::Column" unless constricted_column.is_a? Column
|
17
|
+
raise ArgumentError, "name is not a Dbsketch::Model::Table" unless referenced_table.is_a? Table
|
18
|
+
raise ArgumentError, "referenced column is not a Dbsketch::Model::Column" unless referenced_column.is_a? Column
|
19
|
+
###
|
20
|
+
@constricted_column = constricted_column
|
21
|
+
@referenced_table = referenced_table
|
22
|
+
@referenced_column = referenced_column
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :constricted_column, :referenced_table, :referenced_column
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|