dbd4 1.0.2

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.
@@ -0,0 +1,182 @@
1
+ module DBD4
2
+ class OrderedHash < Hash
3
+
4
+ attr_accessor :order
5
+
6
+ class << self
7
+
8
+ def [] *args
9
+ hsh = OrderedHash.new
10
+ if Hash === args[0]
11
+ hsh.replace args[0]
12
+ elsif (args.size % 2) != 0
13
+ raise ArgumentError, "odd number of elements for Hash"
14
+ else
15
+ hsh[args.shift] = args.shift while args.size > 0
16
+ end
17
+ hsh
18
+ end
19
+
20
+ end
21
+
22
+ def initialize(*a, &b)
23
+ super
24
+ @order = []
25
+ end
26
+
27
+ def store_only a,b
28
+ store a,b
29
+ end
30
+
31
+ alias orig_store store
32
+
33
+ def store a,b
34
+ @order.push a unless has_key? a
35
+ super a,b
36
+ end
37
+
38
+ alias []= store
39
+
40
+ def == hsh2
41
+ return hsh2==self if !hsh2.is_a?(OrderedHash)
42
+ return false if @order != hsh2.order
43
+ super hsh2
44
+ end
45
+
46
+ def clear
47
+ @order = []
48
+ super
49
+ end
50
+
51
+ def delete key
52
+ @order.delete key
53
+ super
54
+ end
55
+
56
+ def each_key
57
+ @order.each { |k| yield k }
58
+ self
59
+ end
60
+
61
+ def each_value
62
+ @order.each { |k| yield self[k] }
63
+ self
64
+ end
65
+
66
+ def each
67
+ @order.each { |k| yield k,self[k] }
68
+ self
69
+ end
70
+
71
+ alias each_pair each
72
+
73
+ def delete_if
74
+ @order.clone.each { |k|
75
+ delete k if yield
76
+ }
77
+ self
78
+ end
79
+
80
+ def values
81
+ ary = []
82
+ @order.each { |k| ary.push self[k] }
83
+ ary
84
+ end
85
+
86
+ def keys
87
+ @order
88
+ end
89
+
90
+ def invert
91
+ hsh2 = Hash.new
92
+ @order.each { |k| hsh2[self[k]] = k }
93
+ hsh2
94
+ end
95
+
96
+ def reject &block
97
+ self.dup.delete_if( &block )
98
+ end
99
+
100
+ def reject! &block
101
+ hsh2 = reject( &block )
102
+ self == hsh2 ? nil : hsh2
103
+ end
104
+
105
+ def replace hsh2
106
+ @order = hsh2.keys
107
+ super hsh2
108
+ end
109
+
110
+ def shift
111
+ key = @order.first
112
+ key ? [key,delete(key)] : super
113
+ end
114
+
115
+ def unshift k,v
116
+ unless self.include? k
117
+ @order.unshift k
118
+ orig_store(k,v)
119
+ true
120
+ else
121
+ false
122
+ end
123
+ end
124
+
125
+ def push k,v
126
+ unless self.include? k
127
+ @order.push k
128
+ orig_store(k,v)
129
+ true
130
+ else
131
+ false
132
+ end
133
+ end
134
+
135
+ def pop
136
+ key = @order.last
137
+ key ? [key,delete(key)] : nil
138
+ end
139
+
140
+ def first
141
+ self[@order.first]
142
+ end
143
+
144
+ def last
145
+ self[@order.last]
146
+ end
147
+
148
+ def to_a
149
+ ary = []
150
+ each { |k,v| ary << [k,v] }
151
+ ary
152
+ end
153
+
154
+ def to_s
155
+ self.to_a.to_s
156
+ end
157
+
158
+ def inspect
159
+ ary = []
160
+ each {|k,v| ary << k.inspect + "=>" + v.inspect}
161
+ '{' + ary.join(", ") + '}'
162
+ end
163
+
164
+ def update hsh2
165
+ hsh2.each { |k,v| self[k] = v }
166
+ self
167
+ end
168
+
169
+ alias :merge! update
170
+
171
+ def merge hsh2
172
+ self.dup update(hsh2)
173
+ end
174
+
175
+ def select
176
+ ary = []
177
+ each { |k,v| ary << [k,v] if yield k,v }
178
+ ary
179
+ end
180
+
181
+ end
182
+ end
@@ -0,0 +1,122 @@
1
+ #--
2
+ # Copyright (c) 2006 Daniel Shane
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.
22
+ #--
23
+
24
+ require 'slurp'
25
+
26
+ module DBD4
27
+ class RailsMigrationFile
28
+ attr_writer :tablename
29
+ attr_reader :warnings, :tablename
30
+
31
+ def initialize(options = {})
32
+ @warnings = Array.new
33
+ @infos = Array.new
34
+ @tablename = options[:tablename]
35
+
36
+ if options[:tablename]
37
+ if Inflector.singularize(@tablename) == @tablename
38
+ raise RailsModelFileError, "MigrationModelFile : tablename #{@tablename} is not valid, it must be plural, not singular"
39
+ end
40
+ partial_name = "_create_" + Inflector.underscore(Inflector.camelize(@tablename)) + ".rb"
41
+ fileList = Dir.new(File.join('db', 'migrate')).entries.delete_if { |e| e !~ /#{partial_name}/ }
42
+ if fileList.size == 0
43
+ raise "Could not find migration file starting with #{partial_name} in dir db/migrate"
44
+ end
45
+ if fileList.size > 1
46
+ raise "Found more than 1 migration file starting with #{partial_name} in dir db/migrate"
47
+ end
48
+ @migrationfile = File.join('db', 'migrate', fileList[0])
49
+ end
50
+ end
51
+
52
+ def columnsToString(table)
53
+ lines = Array.new
54
+ table.columns.each_value do |c|
55
+ next if c.primary_key?
56
+ d = c.datatype
57
+ case d.type
58
+ when 'BOOL'
59
+ lines << " t.column :#{c.name}, :boolean"
60
+ when 'TEXT'
61
+ lines << " t.column :#{c.name}, :text"
62
+ when 'VARCHAR'
63
+ if d.params['length']
64
+ lines << " t.column :#{c.name}, :string, :length => #{d.params['length']}"
65
+ else
66
+ lines << " t.column :#{c.name}, :string"
67
+ end
68
+ when 'FLOAT'
69
+ lines << " t.column :#{c.name}, :float"
70
+ when 'DATETIME'
71
+ lines << " t.column :#{c.name}, :datetime"
72
+ when 'DATE'
73
+ lines << " t.column :#{c.name}, :date"
74
+ when 'INTEGER'
75
+ if d.params['length']
76
+ lines << " t.column :#{c.name}, :integer, :length => #{d.params['length']}"
77
+ else
78
+ lines << " t.column :#{c.name}, :integer"
79
+ end
80
+ when 'DECIMAL'
81
+ lines << " t.column :#{c.name}, :decimal, :precision => #{d.params['length']}, :scale => #{d.params['decimals']}"
82
+ when 'BINARY'
83
+ lines << " t.column :#{c.name}, :binary"
84
+ when 'BLOB'
85
+ lines << " t.column :#{c.name}, :binary"
86
+ else
87
+ raise "Warning : table #{column.table.name} column #{column.name}, #{d.type} is not a valid type"
88
+ end
89
+ end
90
+ if lines.size == 0
91
+ " #t.column :name, :type"
92
+ else
93
+ lines.join("\n")
94
+ end
95
+ end
96
+
97
+ def update(table)
98
+ raise "MigrationFile : migration file name not given" if ! @migrationfile
99
+
100
+ puts "Updating migration file for table #{@tablename} (file : #{@migrationfile})..."
101
+ newFile = File.open(@migrationfile, "w")
102
+ c = Inflector.camelize(@tablename)
103
+ newFile.puts <<"MIGRATIONFILE"
104
+ class Create#{c} < ActiveRecord::Migration
105
+ def self.up
106
+ create_table :#{table.name} do |t|
107
+ #{columnsToString(table)}
108
+ end
109
+ end
110
+
111
+ def self.down
112
+ drop_table :#{@tablename}
113
+ end
114
+ end
115
+ MIGRATIONFILE
116
+ end
117
+
118
+ def to_str
119
+ @lines.join("")
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,279 @@
1
+ #--
2
+ # Copyright (c) 2006 Daniel Shane
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.
22
+ #--
23
+
24
+ require 'slurp'
25
+
26
+ module DBD4
27
+ class RailsModelFile
28
+ attr_writer :modelfile, :modelname
29
+ attr_reader :warnings
30
+
31
+ def init_variables
32
+ @content = nil
33
+ @lines = Array.new
34
+ @warnings = Array.new
35
+ @infos = Array.new
36
+ @elements = {
37
+ 'has_one' => {},
38
+ 'has_many' => {},
39
+ 'belongs_to' => {},
40
+ 'habtm' => {},
41
+ 'has_and_belongs_to_many' => {},
42
+ 'class' => {},
43
+ 'primary_key' => {},
44
+ 'table_name' => {},
45
+ 'as' => {}
46
+ }
47
+ end
48
+
49
+ def setContent(string)
50
+ init_variables
51
+ @content = string
52
+ @lines = string.readlines
53
+ load
54
+ end
55
+
56
+ def initialize(options = {})
57
+ init_variables
58
+
59
+ @modelname = options[:modelname]
60
+
61
+ if options[:modelname]
62
+ if Inflector.singularize(@modelname) != @modelname
63
+ raise RailsModelFileError, "ModelFile : modelname #{@modelname} is not valid, it must not be plural"
64
+ end
65
+ @modelfile = File.join('app', 'models', Inflector.underscore(Inflector.camelize(@modelname)) + ".rb")
66
+ if ! FileTest.exist?(@modelfile)
67
+ puts "Generating model #{@modelname}..."
68
+ generate unless $dryrun
69
+ end
70
+ load
71
+ end
72
+ end
73
+
74
+ def load
75
+ raise "ModelFile : modelname not given" if ! @modelname
76
+ raise "ModelFile : model file name not given" if ! @modelfile
77
+
78
+ @content = IO.read(@modelfile) if ! @content
79
+ @lines = IO.readlines(@modelfile) if @lines.empty?
80
+
81
+ raise "ModelFile : Cannot load, no content was given" if ! @content
82
+
83
+ for i in (0..@lines.size-1)
84
+ l = @lines[i]
85
+ comment = false
86
+
87
+ if l.match(/^\s*#/)
88
+ #do nothing
89
+ elsif l.match(/^\s*class\s+([A-Za-z_0-9]+)/) and @elements['class'].empty?
90
+ @elements['class'] = { :start => i, :name => $~[1] }
91
+ if @elements['class'][:name] != Inflector.camelize(@modelname)
92
+ @warnings << "Warning in file #{@modelfile} at line #{i + 1}:"
93
+ @warnings << " Class \"#{@elements['class'][:name]}\" does not match expected name : \"" + Inflector.camelize(@modelname) + "\""
94
+ @warnings << " Model name is \"#{@modelname}\" class name should be \"" + Inflector.camelize(@modelname) + "\""
95
+ end
96
+ elsif l.match(/^\s*set_primary_key(?:[ \t\("'])+([a-zA-Z_0-9]+)/)
97
+ if ! @elements['primary_key'].empty?
98
+ @warnings << "Warning in file #{@modelfile} at line #{i + 1}:"
99
+ @warnings << " Duplicate set_primary_key declaration, it is already defined at line #{@elements['primary_key'][:start] + 1}"
100
+ @lines[i] = "#" + @lines[i]
101
+ else
102
+ @elements['primary_key'] = { :start => i, :name => $~[1] }
103
+ end
104
+ elsif l.match(/^\s*set_table_name(?:[ \t\("'])+([a-zA-Z_0-9]+)/)
105
+ if ! @elements['table_name'].empty?
106
+ @warnings << "Warning in file #{@modelfile} at line #{i + 1}:"
107
+ @warnings << " Duplicate set_table_name declaration, it is already defined at line #{@elements['table_name'][:start] + 1}"
108
+ @lines[i] = "#" + @lines[i]
109
+ else
110
+ @elements['table_name'] = { :start => i, :name => $~[1] }
111
+ end
112
+ elsif l.match(/^\s*(has_one|has_many|has_and_belongs_to_many|habtm|belongs_to)\s+:([a-zA-Z_0-9]+)/)
113
+ r_type = $~[1]
114
+ t_name = $~[2]
115
+
116
+ if r_type == 'has_and_belongs_to_many'
117
+ r_type = 'habtm'
118
+ end
119
+
120
+ if @elements[r_type][t_name]
121
+ @warnings << "Warning in file #{@modelfile} at line #{i + 1}:"
122
+ @warnings << " Duplicate declaration #{r_type}, it is already defined at line #{@elements[r_type][t_name][:start] + 1}"
123
+ @lines[i] = "#" + @lines[i]
124
+ i += 1
125
+ while i < @lines.size
126
+ break if @lines[i].match(/^\s*(?:has_one|has_many|has_and_belongs_to_many|habtm|belongs_to|def|end)/)
127
+ @lines[i] = "#" + @lines[i] if @lines[i] !~ /^\s*#/
128
+ i += 1
129
+ end
130
+ else
131
+ r = @elements[r_type][t_name] = { :start => i, :end => i }
132
+ r[:foreign_key] = $~ if l.match(/:foreign_key\s*=>\s*["']([a-zA-Z_0-9]+)/)
133
+ r[:polymorphic] = $~ if l.match(/:polymorphic\s*=>\s*true/)
134
+ r[:join_table] = $~ if l.match(/:join_table\s*=>\s*["']([a-zA-Z_0-9]+)/)
135
+ r[:as] = $~ if l.match(/:as\s*=>\s*([a-zA-Z_0-9]+)/)
136
+ r[:association_foreign_key] = $~ if l.match(/:association_foreign_key\s*=>\s*["']([a-zA-Z_0-9]+)/)
137
+
138
+ i += 1
139
+ while i < @lines.size
140
+ l2 = @lines[i]
141
+ break if @lines[i].match(/^\s*(?:has_one|has_many|has_and_belongs_to_many|habtm|belongs_to|def|end)/)
142
+ r[:foreign_key] = $~ if l2.match(/:foreign_key\s*=>\s*["']([a-zA-Z_0-9]+)/)
143
+ r[:polymorphic] = $~ if l2.match(/:polymorphic\s*=>\s*true/)
144
+ r[:as] = $~ if l2.match(/:as\s*=>\s*([a-zA-Z_0-9]+)/)
145
+ r[:join_table] = $~ if l2.match(/:join_table\s*=>\s*["']([a-zA-Z_0-9]+)/)
146
+ r[:association_foreign_key] = $~ if l2.match(/:association_foreign_key\s*=>\s*["']([a-zA-Z_0-9]+)/)
147
+ r[:end] = i
148
+
149
+ i += 1
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ if @elements['class'].empty?
156
+ @warnings << "Error : Could not find the class declaration in model file #{@modelfile}"
157
+ raise "Error : Could not find the class declaration in model file #{@modelfile}"
158
+ end
159
+ end
160
+
161
+ def generate
162
+ if ! FileTest.exist?(@modelfile)
163
+ if @comments =~ /SCAFFOLD/
164
+ cmdline = ['scaffold', @modelname]
165
+ else
166
+ cmdline = ['model', @modelname]
167
+ end
168
+
169
+ @infos << "Calling generator: #{cmdline.join(' ')}"
170
+ Rails::Generator::Scripts::Generate.new.run(['model', @modelname]) if not $dryrun
171
+ end
172
+ end
173
+
174
+ def update(table)
175
+ raise "ModelFile : modelname not given" if ! @modelname
176
+ raise "ModelFile : model file name not given" if ! @modelfile
177
+ raise "ModelFile : no content given" if @content == nil
178
+
179
+ table.relation_starts.each_value do |r|
180
+ dname = ""
181
+ if r.type == :one2one
182
+ dname = r.destination_table.modelname
183
+ else
184
+ dname = r.destination_table.name
185
+ end
186
+
187
+ etype = 'has_one' if r.type == :one2one
188
+ etype = 'has_many' if r.type == :one2many
189
+ etype = 'habtm' if r.type == :many2many
190
+ element = @elements[etype][dname]
191
+
192
+ #If the line exists
193
+ if element
194
+ if element[:as]
195
+ if r.destination_column.polymorphic
196
+ for i in (element[:start] .. element[:end])
197
+ @lines[i].sub!(/(:as\s*=>\s*["']?)[a-zA-Z_0-9]+/, "\\1#{r.destination_column.polymorphic}")
198
+ end
199
+ else
200
+ for i in (element[:start] .. element[:end])
201
+ @lines[i].sub!(/,\s*:as\s*=>\s*["']?[a-zA-Z_0-9]+["']?/, "")
202
+ end
203
+ end
204
+ else
205
+ @lines[element[:start]].sub!(/(#{etype}[^,]+)/, "\\1, :as => '#{r.destination_column.polymorphic}'")
206
+ end
207
+ @elements[etype].delete(dname)
208
+ else
209
+ @lines[@elements['class'][:start]] += " #{etype} :#{dname}"
210
+ if r.destination_column.polymorphic
211
+ @lines[@elements['class'][:start]] += ", :as => #{r.destination_column.polymorphic}"
212
+ end
213
+ @lines[@elements['class'][:start]] += "\n"
214
+ end
215
+ end
216
+
217
+ table.relation_ends.each_value do |r|
218
+ dname = ""
219
+ if r.type == :many2many
220
+ dname = r.source_table.name
221
+ else
222
+ if r.destination_column.polymorphic
223
+ dname = r.destination_column.polymorphic
224
+ else
225
+ dname = r.source_table.modelname
226
+ end
227
+ end
228
+
229
+ etype = 'belongs_to' if r.type == :one2one or r.type == :one2many
230
+ etype = 'habtm' if r.type == :many2many
231
+ element = @elements[etype][dname]
232
+
233
+ #If the line exists
234
+ if element
235
+ if element[:polymorphic]
236
+ if ! r.destination_column.polymorphic
237
+ for i in (element[:start] .. element[:end])
238
+ @lines[i].sub!(/,\s*:polymorphic\s*=>\s*["']?[a-zA-Z_0-9]+["']?/, "")
239
+ end
240
+ end
241
+ else
242
+ if r.destination_column.polymorphic
243
+ @lines[element[:start]].sub!(/(#{etype}[^,\n]+)/, "\\1, :polymorphic => true")
244
+ else
245
+ # do nothing
246
+ end
247
+ end
248
+ @elements[etype].delete(dname)
249
+ else
250
+ @lines[@elements['class'][:start]] += " #{etype} :#{dname}"
251
+ if r.destination_column.polymorphic
252
+ @lines[@elements['class'][:start]] += ", :polymorphic => true"
253
+ end
254
+ @lines[@elements['class'][:start]] += "\n"
255
+ end
256
+ end
257
+
258
+ @elements.each do |k1, v1|
259
+ next if k1 == 'class' or v1.empty?
260
+ v1.each do |k2, v2|
261
+ @warnings << "Warning in file #{@modelfile} at line #{v2[:start]}"
262
+ @warnings << " #{k1} :#{k2} declaration is useless, will comment it"
263
+
264
+ for i in (v2[:start] .. v2[:end])
265
+ @lines[i] = "#" + @lines[i]
266
+ end
267
+ end
268
+ end
269
+
270
+ puts "Updating model #{@modelname} (file : #{@modelfile})..."
271
+ newFile = File.open(@modelfile, "w")
272
+ newFile.puts @lines
273
+ end
274
+
275
+ def to_str
276
+ @lines.join("")
277
+ end
278
+ end
279
+ end