kansas 0.9.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/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +97 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/kansas.gemspec +35 -0
- data/lib/kansas.rb +42 -0
- data/lib/kansas/BelongsTo.rb +23 -0
- data/lib/kansas/Database.rb +659 -0
- data/lib/kansas/Expression.rb +361 -0
- data/lib/kansas/Table.rb +102 -0
- data/lib/kansas/TableClass.rb +179 -0
- data/lib/kansas/ToMany.rb +13 -0
- data/lib/kansas/ToOne.rb +29 -0
- data/lib/kansas/adaptors/AdaptorRule.rb +17 -0
- data/lib/kansas/adaptors/Adaptors.rb +24 -0
- data/lib/kansas/adaptors/StandardSQLMixin.rb +106 -0
- data/lib/kansas/adaptors/dbi_rule.rb +8 -0
- data/lib/kansas/patch_dbi.rb +54 -0
- data/lib/kansas/version.rb +3 -0
- data/notes +85 -0
- metadata +138 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
class KSTable
|
2
|
+
|
3
|
+
def KSTable.is_a_kstable?
|
4
|
+
true
|
5
|
+
end
|
6
|
+
|
7
|
+
def KSTable.field(localName, remoteName=nil, conversion=nil)
|
8
|
+
remoteName = localName unless remoteName
|
9
|
+
|
10
|
+
conversionCall = conversion ? ".#{conversion}" : ''
|
11
|
+
addField(localName, remoteName)
|
12
|
+
|
13
|
+
class_eval <<-EOS
|
14
|
+
def #{localName}
|
15
|
+
@row['#{remoteName}']#{conversionCall}
|
16
|
+
end
|
17
|
+
|
18
|
+
def #{localName}=(val)
|
19
|
+
unless pending_deletion? or read_only?
|
20
|
+
@rollback_buffer.push ['#{remoteName}', @row['#{remoteName}']]
|
21
|
+
@rollback_hash['#{remoteName}'] ||= []
|
22
|
+
@rollback_hash['#{remoteName}'].push @row['#{remoteName}']
|
23
|
+
@row['#{remoteName}'] = val
|
24
|
+
changed
|
25
|
+
end
|
26
|
+
end
|
27
|
+
EOS
|
28
|
+
end
|
29
|
+
|
30
|
+
# A table can have more than one primary field?
|
31
|
+
|
32
|
+
def KSTable.primary(field)
|
33
|
+
if defined?(@primaries) && @primaries
|
34
|
+
@primaries << field unless @primaries.include?(field)
|
35
|
+
else
|
36
|
+
@primaries = [field]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def KSTable.to_one(name, local_field, foreign_table, foreign_field = nil)
|
41
|
+
foreign_table = foreign_table.to_s if Symbol === foreign_table
|
42
|
+
if foreign_table.class.to_s == 'String'
|
43
|
+
if KSDatabase.partial_to_complete_map.has_key?(foreign_table)
|
44
|
+
foreign_table = KSDatabase.const_get KSDatabase.partial_to_complete_map[foreign_table]
|
45
|
+
else
|
46
|
+
foreign_table = nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Need to throw an exception if foregin_table is bad.
|
51
|
+
|
52
|
+
addRelation(name.to_s, KSToOne.new(self, local_field.to_s, foreign_table, foreign_field.to_s))
|
53
|
+
class_eval <<-EOS
|
54
|
+
def #{name}
|
55
|
+
self.class.relations['#{name}'].get(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def #{name}=(val)
|
59
|
+
self.class.relations['#{name}'].set(self, val)
|
60
|
+
end
|
61
|
+
EOS
|
62
|
+
end
|
63
|
+
|
64
|
+
def KSTable.belongs_to(name, foreign_table, local_field, foreign_field = nil)
|
65
|
+
foreign_table = foreign_table.to_s if Symbol === foreign_table
|
66
|
+
if foreign_table.class.to_s == 'String'
|
67
|
+
if KSDatabase.partial_to_complete_map.has_key?(foreign_table)
|
68
|
+
foreign_table = KSDatabase.const_get KSDatabase.partial_to_complete_map[foreign_table]
|
69
|
+
else
|
70
|
+
foreign_table = nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Need to throw an exception if foregin_table is bad.
|
75
|
+
|
76
|
+
addRelation(name.to_s, KSBelongsTo.new(self, foreign_table, local_field.to_s, foreign_field.to_s))
|
77
|
+
class_eval <<-EOS
|
78
|
+
def #{name}
|
79
|
+
self.class.relations['#{name}'].get(self)
|
80
|
+
end
|
81
|
+
|
82
|
+
def #{name}=(val)
|
83
|
+
self.class.relations['#{name}'].set(self, val)
|
84
|
+
end
|
85
|
+
EOS
|
86
|
+
end
|
87
|
+
|
88
|
+
def KSTable.to_many(name, foreign_table, foreign_field, local_field = nil)
|
89
|
+
foreign_table = foreign_table.to_s if Symbol === foreign_table
|
90
|
+
if foreign_table.class.to_s == 'String'
|
91
|
+
if KSDatabase.partial_to_complete_map.has_key?(foreign_table)
|
92
|
+
foreign_table = KSDatabase.const_get KSDatabase.partial_to_complete_map[foreign_table]
|
93
|
+
else
|
94
|
+
foreign_table = nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Need to throw an exception if foregin_table is bad.
|
99
|
+
|
100
|
+
addRelation(name.to_s, KSToMany.new(self, foreign_table, foreign_field.to_s, local_field.to_s))
|
101
|
+
class_eval <<-EOS
|
102
|
+
def #{name}
|
103
|
+
self.class.relations['#{name}'].get(self)
|
104
|
+
end
|
105
|
+
|
106
|
+
def #{name}_count
|
107
|
+
self.class.relations['#{name}'].count(self)
|
108
|
+
end
|
109
|
+
EOS
|
110
|
+
end
|
111
|
+
|
112
|
+
def KSTable.all_fields
|
113
|
+
database.query do |dbh|
|
114
|
+
dbh.columns(table_name).each do |descr|
|
115
|
+
fieldName = descr['name']
|
116
|
+
field(fieldName, fieldName, conversion(descr['type_name']))
|
117
|
+
if descr['primary']
|
118
|
+
primary fieldName
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def KSTable.primaries
|
125
|
+
@primaries
|
126
|
+
end
|
127
|
+
|
128
|
+
def KSTable.table_name
|
129
|
+
class_eval 'Name'
|
130
|
+
end
|
131
|
+
|
132
|
+
def KSTable.database
|
133
|
+
class_eval 'Database'
|
134
|
+
end
|
135
|
+
|
136
|
+
def KSTable.fields
|
137
|
+
@fields
|
138
|
+
end
|
139
|
+
|
140
|
+
def KSTable.relations
|
141
|
+
@relations
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def KSTable.addField(name, field)
|
147
|
+
if defined?(@fields) && @fields
|
148
|
+
@fields[name] = field
|
149
|
+
else
|
150
|
+
@fields = {name => field}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def KSTable.addRelation(name, relation)
|
155
|
+
if defined?(@relations)
|
156
|
+
@relations[name] = relation
|
157
|
+
else
|
158
|
+
@relations = {name => relation}
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def KSTable.conversion(type)
|
163
|
+
case type
|
164
|
+
when /int/
|
165
|
+
:to_i
|
166
|
+
when /float/, /double/
|
167
|
+
:to_f
|
168
|
+
else
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def KSTable.canonical(fieldName)
|
174
|
+
newName = fieldName.mixcase
|
175
|
+
newName[0,1] = newName[0,1].downcase
|
176
|
+
newName
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class KSToMany
|
2
|
+
|
3
|
+
def initialize(*args)
|
4
|
+
@localTable, @foreignTable, @foreignField, @localField = args
|
5
|
+
@localField = @localTable.primaries.first unless @localField != ''
|
6
|
+
end
|
7
|
+
|
8
|
+
def get(parent)
|
9
|
+
sql = "SELECT * FROM #{@foreignTable.table_name} " +
|
10
|
+
"WHERE #{@foreignField} = '#{parent.row[@localField]}'"
|
11
|
+
parent.context.select(@foreignTable, sql)
|
12
|
+
end
|
13
|
+
end
|
data/lib/kansas/ToOne.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class KSToOne
|
2
|
+
|
3
|
+
attr_reader :foreignTable, :foreignField, :localTable, :localField
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@localTable, @localField, @foreignTable, @foreignField = args
|
7
|
+
@foreignField = @foreignTable.primaries.first unless @foreignField != ''
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(parent)
|
11
|
+
if @foreignField != @foreignTable.primaries.first
|
12
|
+
sql = "SELECT * FROM #{@foreignTable.table_name} " +
|
13
|
+
"WHERE #{@foreignField} = '#{parent.row[@localField]}'"
|
14
|
+
parent.context.select(@foreignTable, sql).first
|
15
|
+
else
|
16
|
+
parent.context.get_object(@foreignTable, [parent.row[@localField]])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def set(parent, child)
|
21
|
+
parent.row[@localField] = child.row[@foreignField]
|
22
|
+
parent.changed
|
23
|
+
end
|
24
|
+
|
25
|
+
def join
|
26
|
+
"#{@localTable.table_name}.#{@localField} = #{@foreignTable.table_name}.#{@foreignField}"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class KSAdaptorRule
|
2
|
+
def initialize(args, &block)
|
3
|
+
@name = args[:name] # name of adaptor
|
4
|
+
@description = args[:description] # short description of what adaptor provides
|
5
|
+
@file = args[:file] # adaptor file to require
|
6
|
+
@priority = args[:priority] # rules with a lower priority will be checked first
|
7
|
+
@klass = args[:klass] # adaptor class
|
8
|
+
@block = block # code to determine if this adaptor should be used
|
9
|
+
end
|
10
|
+
|
11
|
+
def name; @name; end
|
12
|
+
def description; @description; end
|
13
|
+
def file; @file; end
|
14
|
+
def priority; @priority; end
|
15
|
+
def klass; @klass; end
|
16
|
+
def check(*args); @block.call(args); end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class KSAdaptors
|
2
|
+
|
3
|
+
@@list = Hash.new {|h,k| h[k] = []}
|
4
|
+
|
5
|
+
def KSAdaptors.<<(val)
|
6
|
+
@@list[val.priority] << val
|
7
|
+
end
|
8
|
+
|
9
|
+
def KSAdaptors[]=(key,val)
|
10
|
+
@@list[key] << val
|
11
|
+
end
|
12
|
+
|
13
|
+
def KSAdaptors[](key)
|
14
|
+
@@list[key]
|
15
|
+
end
|
16
|
+
|
17
|
+
def KSAdaptors.list
|
18
|
+
@@list
|
19
|
+
end
|
20
|
+
|
21
|
+
def KSAdaptors.to_s
|
22
|
+
@@list.inspect
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class Object
|
2
|
+
def expr_body
|
3
|
+
to_s
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class Array
|
8
|
+
def expr_body
|
9
|
+
collect {|e| e.expr_body}.join(',')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class String
|
14
|
+
def expr_body
|
15
|
+
KSDatabase.sql_escape(self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class KSStandardSQLMixin
|
20
|
+
def select_sql(context)
|
21
|
+
distinct_fields = []
|
22
|
+
fields = []
|
23
|
+
context.select_table.fields.each_value do |f|
|
24
|
+
if context.distinct[f]
|
25
|
+
distinct_fields.push "distinct(#{@context.select}.#{f})"
|
26
|
+
else
|
27
|
+
fields.push "#{@context.select}.#{f}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
fields = distinct_fields.concat(fields)
|
32
|
+
|
33
|
+
selectedTables = context.tables.compact.flatten.uniq.join(',')
|
34
|
+
joinConstraints = context.joins.compact.flatten.uniq.join(' AND ')
|
35
|
+
#selected_rows = context.select.compact.flatten.uniq.collect {|t| "#{t}.*"}.join(',')
|
36
|
+
|
37
|
+
if joinConstraints != ""
|
38
|
+
joinConstraints << " AND "
|
39
|
+
end
|
40
|
+
|
41
|
+
#statement = "SELECT #{context.select}.* FROM #{selectedTables} WHERE #{joinConstraints} #{expr_body}"
|
42
|
+
statement = "SELECT #{fields.join(',')} FROM #{selectedTables} WHERE #{joinConstraints} #{expr_body}"
|
43
|
+
statement << ' ORDER BY ' << context.sort_fields.collect {|f| "#{f[0].respond_to?(:expr_body) ? f[0].expr_body : f[0].to_s} #{f[1]}"}.join(',') if context.sort_fields.length > 0
|
44
|
+
statement << ' LIMIT ' << context.limits.join(',') if context.limits.length > 0
|
45
|
+
|
46
|
+
statement
|
47
|
+
end
|
48
|
+
|
49
|
+
def count_sql(context)
|
50
|
+
selectedTables = context.tables.compact.flatten.uniq.join(',')
|
51
|
+
joinConstraints = context.joins.compact.flatten.uniq.join(' AND ')
|
52
|
+
#selected_rows = context.select.compact.flatten.uniq.collect {|t| "#{t}.*"}.join(',')
|
53
|
+
|
54
|
+
if joinConstraints != ""
|
55
|
+
joinConstraints << " AND "
|
56
|
+
end
|
57
|
+
|
58
|
+
statement = "SELECT count(*) FROM #{selectedTables} WHERE #{joinConstraints} #{expr_body}"
|
59
|
+
statement << ' ORDER BY ' << context.sort_fields.collect {|f| "#{f[0].respond_to?(:expr_body) ? f[0].expr_body : f[0].to_s} #{f[1]}"}.join(',') if context.sort_fields.length > 0
|
60
|
+
statement << ' LIMIT ' << context.limits.join(',') if @context.limits.length > 0
|
61
|
+
|
62
|
+
statement
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete_sql(context)
|
66
|
+
selectedTables = context.tables.compact.flatten.uniq.join(",")
|
67
|
+
joinConstraints = context.joins.compact.flatten.uniq.join(" AND ")
|
68
|
+
if joinConstraints != ""
|
69
|
+
joinConstraints << " AND "
|
70
|
+
end
|
71
|
+
|
72
|
+
statement = "DELETE FROM #{selectedTables} WHERE #{joinConstraints} #{expr_body}"
|
73
|
+
|
74
|
+
statement
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_where(object)
|
78
|
+
build_where_from_key(object, object.key)
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_where_from_key(object, key, tableflag = false)
|
82
|
+
table = tableflag ? object : object.class
|
83
|
+
where = " WHERE "
|
84
|
+
where_ary = []
|
85
|
+
fields = table.primaries
|
86
|
+
|
87
|
+
fields.each_index do |i|
|
88
|
+
if !tableflag && object.rollback_hash[fields[i]]
|
89
|
+
val = object.rollback_hash[fields[i]].last
|
90
|
+
else
|
91
|
+
val = key[i]
|
92
|
+
end
|
93
|
+
|
94
|
+
where_ary.push "#{fields[i]} = #{KSDatabase.sql_escape(val)}"
|
95
|
+
end
|
96
|
+
where + where_ary.join(' AND ')
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_update(object)
|
100
|
+
update = "UPDATE #{object.table_name} SET "
|
101
|
+
object.row.each do |field, val|
|
102
|
+
update << " #{field} = #{KSDatabase.sql_escape(val)},"
|
103
|
+
end
|
104
|
+
update.chop!
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
KSAdaptors << KSAdaptorRule.new(
|
2
|
+
:name => 'General DBI Adaptor',
|
3
|
+
:description => 'Provides a generalized access to DBI; this is equivalent to the behavior of the legacy Kansas.',
|
4
|
+
:file => '',
|
5
|
+
:priority => 1000
|
6
|
+
) {|args|
|
7
|
+
# This code block determines if this adaptor might be able to handle the request.
|
8
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module DBI
|
2
|
+
|
3
|
+
class Row
|
4
|
+
if RUBY_VERSION =~ /^1\.9/ || RUBY_VERSION =~ /^2/
|
5
|
+
def __getobj__
|
6
|
+
@arr
|
7
|
+
end
|
8
|
+
|
9
|
+
def __setobj__(obj)
|
10
|
+
@delegate_dc_obj = @arr = obj
|
11
|
+
end
|
12
|
+
else
|
13
|
+
def clone
|
14
|
+
Marshal.load(Marshal.dump(self))
|
15
|
+
end
|
16
|
+
|
17
|
+
def dup
|
18
|
+
row = self.class.allocate
|
19
|
+
row.instance_variable_set :@column_types, @column_types
|
20
|
+
row.instance_variable_set :@convert_types, @convert_types
|
21
|
+
row.instance_variable_set :@column_map, @column_map
|
22
|
+
row.instance_variable_set :@column_names, @column_names
|
23
|
+
# this is the only one we actually dup...
|
24
|
+
row.instance_variable_set :@arr, arr = @arr.dup
|
25
|
+
row.instance_variable_set :@_dc_obj, arr
|
26
|
+
row
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class ColumnInfo
|
33
|
+
def initialize(hash=nil)
|
34
|
+
@hash = hash.dup rescue nil
|
35
|
+
@hash ||= Hash.new
|
36
|
+
|
37
|
+
# coerce all strings to symbols
|
38
|
+
@hash.keys.each do |x|
|
39
|
+
if x.kind_of? String
|
40
|
+
sym = x.to_sym
|
41
|
+
if @hash.has_key? sym
|
42
|
+
raise ::TypeError,
|
43
|
+
"#{self.class.name} may construct from a hash keyed with strings or symbols, but not both"
|
44
|
+
end
|
45
|
+
@hash[sym] = @hash[x]
|
46
|
+
@hash.delete(x)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
super(@hash)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|