webink 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,425 @@
1
+ module Ink
2
+
3
+ # = Database class
4
+ #
5
+ # == Config
6
+ #
7
+ # Currently there are two types of databases supported, MySQL and
8
+ # SQLite3. Either way, you need to specify them in a config-file
9
+ # that is located inside the web project folder.
10
+ #
11
+ # Sample config for MySQL:
12
+ # config = {
13
+ # "escape_post_data" => false,
14
+ # "production" => true,
15
+ # "db_type" => "mysql",
16
+ # "db_user" => "yourusername",
17
+ # "db_pass" => "yourpassword",
18
+ # "db_database" => "yourdatabase",
19
+ # "db_server" => "localhost",
20
+ # }
21
+ #
22
+ # Sample config for SQLite3:
23
+ # config = {
24
+ # "escape_post_data" => false,
25
+ # "production" => true,
26
+ # "db_type" => "sqlite3",
27
+ # "db_server" => "/full/path/to/database.sqlite",
28
+ # }
29
+ #
30
+ # == Usage
31
+ #
32
+ # Create an Ink::Database instance with the self.create class method.
33
+ # Now it can be accessed via the public class variable 'database'.
34
+ # Once that is done, can use it to execute various SQL statements
35
+ # to gather data.
36
+ #
37
+ # Ink::Database.database.query "SELECT * FROM x;"
38
+ #
39
+ # This is the most basic query, it returns an Array of results,
40
+ # and each element contains a Hash of column_name => column_entry.
41
+ #
42
+ # Ink::Database.database.find "apples", "WHERE id < 10 GROUP BY color"
43
+ # => self.query("SELECT * FROM apples WHERE id < 10 GROUP BY color;")
44
+ #
45
+ # This is different from the query method, because it returns an Array
46
+ # of Objects, created by the information stored in the database. So this
47
+ # find() will return you a set of Apple-instances.
48
+ #
49
+ # Ink::Database.database.find_union "apple", 5, "tree", ""
50
+ #
51
+ # find_union allows you to retrieve data through a many_many reference.
52
+ # When you define a many_many relationship, a helper-table is created
53
+ # inside the database, which is apple_tree, in this case. This statement
54
+ # will fetch an Array of all Trees, that connect to the Apple with primary
55
+ # key 5. Notice that the relationship database apple_tree is put together
56
+ # by the alphabetically first, and then second classname. The last quotes
57
+ # allow additional query informations to be passed along (like group by)
58
+ #
59
+ # Ink::Database.database.find_reference "tree", 1, "apple", ""
60
+ #
61
+ # find_reference is similar to find_union, only that it handles all
62
+ # other relationships. This statement above requires one Tree to have many
63
+ # Apples, so it will return an Array of Apples, all those that belong to
64
+ # the Tree with primary key 1
65
+ #
66
+ # Please close the dbinstance once you are done. This is automatically
67
+ # inserted in the init.rb of a project.
68
+ #
69
+ #
70
+ #
71
+ class Database
72
+ private_class_method :new
73
+ @@database = nil
74
+
75
+ # Private Constructor
76
+ #
77
+ # Uses the config parameter to create a database
78
+ # connection, and will throw an error, if that is not
79
+ # possible.
80
+ # [param config:] Hash of config parameters
81
+ def initialize(config)
82
+ @type = config["db_type"]
83
+ if @type == "mysql"
84
+ @db = Mysql.real_connect(config["db_server"],config["db_user"],config["db_pass"],config["db_database"])
85
+ @db.reconnect = true
86
+ elsif @type == "sqlite3"
87
+ @db = SQLite3::Database.new(config["db_server"])
88
+ else
89
+ raise ArgumentError.new("Database undefined.")
90
+ end
91
+ end
92
+
93
+ # Class method
94
+ #
95
+ # Instanciates a new Database if none is found
96
+ # [param config:] Hash of config parameters
97
+ def self.create(config)
98
+ @@database = new(config) if not @@database
99
+ end
100
+
101
+ # Class method
102
+ #
103
+ # Removes an instanciated Database
104
+ def self.drop
105
+ @@database = nil if @@database
106
+ end
107
+
108
+ # Class method
109
+ #
110
+ # Returns the Database instance or raises a Runtime Error
111
+ # [returns:] Database instance
112
+ def self.database
113
+ (@@database) ? @@database : (raise RuntimeError.new("No Database found. Create one first"))
114
+ end
115
+
116
+ # Instance method
117
+ #
118
+ # This will retrieve all tables nested into
119
+ # the connected database.
120
+ # [returns:] Array of tables
121
+ def tables
122
+ result = Array.new
123
+ if @type == "mysql"
124
+ re = @db.query "show tables;"
125
+ re.each do |row|
126
+ row.each do |t|
127
+ result.push t
128
+ end
129
+ end
130
+ elsif @type == "sqlite3"
131
+ re = @db.query "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"
132
+ re.each do |row|
133
+ row.each do |t|
134
+ result.push t
135
+ end
136
+ end
137
+ re.close
138
+ end
139
+ result
140
+ end
141
+
142
+ # Instance method
143
+ #
144
+ # Send an SQL query string to the database
145
+ # and retrieve a result set
146
+ # [param query:] SQL query string
147
+ # [returns:] Array of Hashes of column_name => column_entry
148
+ def query(query)
149
+ result = Array.new
150
+ if @type == "mysql"
151
+ re = @db.method("query").call query
152
+ if re
153
+ re.each_hash do |row|
154
+ result.push Hash.new
155
+ row.each do |k,v|
156
+ v = $&.to_i if v =~ /^[0-9]+$/
157
+ result[result.length-1][k] = v
158
+ end
159
+ end
160
+ end
161
+ elsif @type == "sqlite3"
162
+ re = @db.method("query").call query
163
+ re.each do |row|
164
+ result.push Hash.new
165
+ for i in 0...re.columns.length
166
+ row[i] = $&.to_i if row[i] =~ /^[0-9]+$/
167
+ result[result.length-1][re.columns[i]] = row[i]
168
+ end
169
+ end
170
+ re.close if not re.closed?
171
+ end
172
+ result
173
+ end
174
+
175
+ # Instance method
176
+ #
177
+ # Closes the database connection, there is no way
178
+ # to reopen without creating a new Ink::Database instance
179
+ def close
180
+ if @type == "sqlite3" and not @db.closed?
181
+ begin
182
+ @db.close
183
+ rescue SQLite3::BusyException
184
+ end
185
+ elsif @type == "mysql"
186
+ @db.close
187
+ end
188
+ self.class.drop
189
+ end
190
+
191
+ # Instance method
192
+ #
193
+ # Attempts to fetch the last inserted primary key
194
+ # [returns:] primary key or nil
195
+ def last_inserted_pk
196
+ string = ""
197
+ if @type == "mysql"
198
+ string = "LAST_INSERT_ID()"
199
+ elsif @type == "sqlite3"
200
+ string = "last_insert_rowid()"
201
+ end
202
+ response = self.query "SELECT #{string} as id"
203
+ return (response.length > 0) ? response[0]["id"] : nil
204
+ end
205
+
206
+ # Instance method
207
+ #
208
+ # Creates the SQL syntax for the chosen database type
209
+ # to define a primary key, autoincrementing field
210
+ # [returns:] SQL syntax for a primary key field
211
+ def primary_key_autoincrement(pk="id")
212
+ result = Array.new
213
+ if @type == "mysql"
214
+ result = ["`#{pk}`", "INTEGER", "PRIMARY KEY", "AUTO_INCREMENT"]
215
+ elsif @type == "sqlite3"
216
+ result = ["`#{pk}`", "INTEGER", "PRIMARY KEY", "ASC"]
217
+ end
218
+ result
219
+ end
220
+
221
+ # Instance method
222
+ #
223
+ # Delete something from the database.
224
+ # [param class_name:] Defines the table name
225
+ # [param params:] Additional SQL syntax like WHERE conditions (optional)
226
+ def remove(class_name, params="")
227
+ table_name = Ink::Model.str_to_tablename(class_name)
228
+ return if not table_name
229
+ self.query("DELETE FROM #{table_name} #{params};")
230
+ end
231
+
232
+ # Instance method
233
+ #
234
+ # Retrieve class instances, that are loaded with the database result set.
235
+ # [param class_name:] Defines the table name and resulting Instance classnames
236
+ # [param params:] Additional SQL syntax like WHERE conditions (optional)
237
+ # [returns:] Array of class_name instances from the SQL result set
238
+ def find(class_name, params="")
239
+ result = Array.new
240
+ table_name = Ink::Model.str_to_tablename(class_name)
241
+ return result if not table_name
242
+
243
+ re = self.query("SELECT * FROM #{table_name} #{params};")
244
+ re.each do |entry|
245
+ instance = Ink::Model.classname(class_name).new entry
246
+ result.push instance
247
+ end
248
+ result
249
+ end
250
+
251
+ # Instance method
252
+ #
253
+ # Retrieve class2 instances, that are related to the class1 instance with
254
+ # primary key class1_id. This is done via an additional relationship table.
255
+ # Only relevant for many_many relationships.
256
+ # [param class1:] Reference classname
257
+ # [param class1_id:] Primary key value of the reference classname
258
+ # [param class2:] Match classname
259
+ # [param params:] Additional SQL syntax like GROUP BY (optional)
260
+ # [returns:] Array of class2 instances from the SQL result set
261
+ def find_union(class1, class1_id, class2, params="")
262
+ result = Array.new
263
+ relationship = nil
264
+ Ink::Model.classname(class1).foreign.each do |k,v|
265
+ relationship = v if k.downcase == class2.downcase
266
+ end
267
+ return result if relationship != "many_many"
268
+ fk1 = Ink::Model.classname(class1).foreign_key[0]
269
+ pk2 = Ink::Model.classname(class2).primary_key[0]
270
+ fk2 = Ink::Model.classname(class2).foreign_key[0]
271
+ tablename1 = Ink::Model.str_to_tablename(class1)
272
+ tablename2 = Ink::Model.str_to_tablename(class2)
273
+ union_class = ((class1.downcase <=> class2.downcase) < 0) ? "#{tablename1}_#{tablename2}" : "#{tablename2}_#{tablename1}"
274
+ re = self.query("SELECT #{tablename2}.* FROM #{union_class}, #{tablename2} WHERE #{union_class}.#{fk1} = #{class1_id} AND #{union_class}.#{fk2} = #{tablename2}.#{pk2} #{params};")
275
+ re.each do |entry|
276
+ instance = Ink::Model.classname(tablename2).new entry
277
+ result.push instance
278
+ end
279
+ result
280
+ end
281
+
282
+ # Instance method
283
+ #
284
+ # Retrieve class2 instances, that are related to the class1 instance with
285
+ # primary key class1_id. Not relevant for many_many relationships
286
+ # [param class1:] Reference classname
287
+ # [param class1_id:] Primary key value of the reference classname
288
+ # [param class2:] Match classname
289
+ # [param params:] Additional SQL syntax like GROUP BY (optional)
290
+ # [returns:] Array of class2 instances from the SQL result set
291
+ def find_references(class1, class1_id, class2, params="")
292
+ result = Array.new
293
+ relationship = nil
294
+ Ink::Model.classname(class1).foreign.each do |k,v|
295
+ relationship = v if k.downcase == class2.downcase
296
+ end
297
+ return result if relationship == "many_many"
298
+ re = Array.new
299
+ fk1 = Ink::Model.classname(class1).foreign_key[0]
300
+ if ((class1.downcase <=> class2.downcase) < 0 and relationship == "one_one") or relationship == "one_many"
301
+ re = self.query "SELECT * FROM #{Ink::Model.str_to_tablename(class2)} WHERE #{Ink::Model.classname(class2).primary_key[0]}=(SELECT #{Ink::Model.classname(class2).foreign_key[0]} FROM #{Ink::Model.str_to_tablename(class1)} WHERE #{Ink::Model.classname(class1).primary_key[0]}=#{class1_id});"
302
+ else
303
+ re = self.query "SELECT * FROM #{Ink::Model.str_to_tablename(class2)} WHERE #{fk1} = #{class1_id} #{params};"
304
+ end
305
+
306
+ re.each do |entry|
307
+ instance = Ink::Model.classname(class2).new entry
308
+ result.push instance
309
+ end
310
+ result
311
+ end
312
+
313
+ # Instance method
314
+ #
315
+ # This method attempts to remove all existing relationship data
316
+ # of instance with link of type: type. For one_one relationships
317
+ # this works only one way, requiring a second call later on before
318
+ # setting a new value.
319
+ # [param instance:] Instance of a class that refers to an existing database entry
320
+ # [param link:] the related class (not a String, but class reference)
321
+ # [param type:] relationship type
322
+ def delete_all_links(instance, link, type)
323
+ if type == "one_one"
324
+ firstclass = ((instance.class.name.downcase <=> link.name.downcase) < 0) ? instance.class : link
325
+ secondclass = ((instance.class.name.downcase <=> link.name.downcase) < 0) ? link : instance.class
326
+ key = ((instance.class.name.downcase <=> link.name.downcase) < 0) ? instance.class.primary_key[0] : instance.class.foreign_key[0]
327
+ value = instance.method(instance.class.primary_key[0]).call
328
+ @db.query "UPDATE #{Ink::Model.str_to_tablename(firstclass.name)} SET #{secondclass.foreign_key[0]}=NULL WHERE #{key}=#{value};"
329
+ elsif type == "one_many" or type == "many_one"
330
+ firstclass = (type == "one_many") ? instance.class : link
331
+ secondclass = (type == "one_many") ? link : instance.class
332
+ key = (type == "one_many") ? instance.class.primary_key[0] : instance.class.foreign_key[0]
333
+ value = instance.method(instance.class.primary_key[0]).call
334
+ @db.query "UPDATE #{Ink::Model.str_to_tablename(firstclass.name)} SET #{secondclass.foreign_key[0]}=NULL WHERE #{key}=#{value};"
335
+ elsif type == "many_many"
336
+ tablename1 = Ink::Model.str_to_tablename(instance.class.name)
337
+ tablename2 = Ink::Model.str_to_tablename(link.name)
338
+ union_class = ((instance.class.name.downcase <=> link.name.downcase) < 0) ? "#{tablename1}_#{tablename2}" : "#{tablename2}_#{tablename1}"
339
+ value = instance.method(instance.class.primary_key[0]).call
340
+ @db.query "DELETE FROM #{union_class} WHERE #{instance.class.foreign_key[0]}=#{value};"
341
+ end
342
+ end
343
+
344
+ # Instance method
345
+ #
346
+ # Attempt to create links of instance to the data inside value.
347
+ # link is the class of the related data, and type refers to the
348
+ # relationship type of the two. When one tries to insert an array
349
+ # for a x_one relationship, the last entry will be set.
350
+ # [param instance:] Instance of a class that refers to an existing database entry
351
+ # [param link:] the related class (not a String, but class reference)
352
+ # [param type:] relationship type
353
+ # [param value:] relationship data that was set, either a primary key value, or an instance, or an array of both
354
+ def create_all_links(instance, link, type, value)
355
+ to_add = Array.new
356
+ if value.is_a? Array
357
+ value.each do |v|
358
+ if v.instance_of? link
359
+ to_add.push(v.method(link.primary_key[0]).call)
360
+ else
361
+ to_add.push v
362
+ end
363
+ end
364
+ elsif value.instance_of? link
365
+ to_add.push(value.method(link.primary_key[0]).call)
366
+ else
367
+ to_add.push value
368
+ end
369
+ to_add.each do |fk|
370
+ self.create_link instance, link, type, fk
371
+ end
372
+ end
373
+
374
+ # Instance method
375
+ #
376
+ # Creates a link between instance and a link with primary fk.
377
+ # The relationship between the two is defined by type. one_one
378
+ # relationships are placing an additional call to delete_all_links
379
+ # that will remove conflicts.
380
+ # [param instance:] Instance of a class that refers to an existing database entry
381
+ # [param link:] the related class (not a String, but class reference)
382
+ # [param type:] relationship type
383
+ # [param value:] primary key of the relationship, that is to be created
384
+ def create_link(instance, link, type, fk)
385
+ if type == "one_one"
386
+ if (instance.class.name.downcase <=> link.name.downcase) < 0
387
+ re = self.find(link.name, "WHERE #{link.primary_key[0]}=#{fk};")[0]
388
+ self.delete_all_links re, instance.class, type
389
+ end
390
+ firstclass = ((instance.class.name.downcase <=> link.name.downcase) < 0) ? instance.class : link
391
+ secondclass = ((instance.class.name.downcase <=> link.name.downcase) < 0) ? link : instance.class
392
+ key = ((instance.class.name.downcase <=> link.name.downcase) < 0) ? instance.class.primary_key[0] : link.primary_key[0]
393
+ value = instance.method(instance.class.primary_key[0]).call
394
+ fk_set = ((instance.class.name.downcase <=> link.name.downcase) < 0) ? fk : value
395
+ value_set = ((instance.class.name.downcase <=> link.name.downcase) < 0) ? value : fk
396
+ @db.query "UPDATE #{Ink::Model.str_to_tablename(firstclass.name)} SET #{secondclass.foreign_key[0]}=#{fk} WHERE #{key}=#{value};"
397
+ elsif type == "one_many" or type == "many_one"
398
+ firstclass = (type == "one_many") ? instance.class : link
399
+ secondclass = (type == "one_many") ? link : instance.class
400
+ key = (type == "one_many") ? instance.class.primary_key[0] : link.primary_key[0]
401
+ value = instance.method(instance.class.primary_key[0]).call
402
+ fk_set = (type == "one_many") ? fk : value
403
+ value_set = (type == "one_many") ? value : fk
404
+ @db.query "UPDATE #{Ink::Model.str_to_tablename(firstclass.name)} SET #{secondclass.foreign_key[0]}=#{fk_set} WHERE #{key}=#{value_set};"
405
+ elsif type == "many_many"
406
+ tablename1 = Ink::Model.str_to_tablename(instance.class.name)
407
+ tablename2 = Ink::Model.str_to_tablename(link.name)
408
+ union_class = ((instance.class.name.downcase <=> link.name.downcase) < 0) ? "#{tablename1}_#{tablename2}" : "#{tablename2}_#{tablename1}"
409
+ value = instance.method(instance.class.primary_key[0]).call
410
+ @db.query "INSERT INTO #{union_class} (#{instance.class.foreign_key[0]}, #{link.foreign_key[0]}) VALUES (#{value}, #{fk});"
411
+ end
412
+ end
413
+
414
+ # Class method
415
+ #
416
+ # Formats a Time object according to the SQL TimeDate standard
417
+ # [param date:] Time object
418
+ # [returns:] Formatted string
419
+ def self.format_date(date)
420
+ (date.instance_of? Time) ? date.strftime("%Y-%m-%d %H:%M:%S") : ""
421
+ end
422
+
423
+ end
424
+
425
+ end
@@ -0,0 +1,311 @@
1
+ module Ink
2
+
3
+ # = Model class
4
+ #
5
+ # == Usage
6
+ #
7
+ # Models are usually derived from. So let's assume there is a
8
+ # class called Apple < Ink::Model
9
+ #
10
+ # apple = Apple.new {:color => "red", :diameter => 4}
11
+ #
12
+ # The constructor checks, if there are class methods 'fields'
13
+ # and 'foreign' defined. If that check is positive, it will
14
+ # match the parameter Hash to the fields, that are set for
15
+ # the database, and thow an exception if fields is lacking
16
+ # an entry (excluded the primary key). The other case just
17
+ # creates an Apple with the Hash as instance variables.
18
+ #
19
+ # puts apple.color
20
+ #
21
+ # This prints "red" to the stdout, since getter and setter
22
+ # methods are automatically added for either the Hash, or
23
+ # the fields and foreign keys.
24
+ #
25
+ # apple.tree = nil
26
+ # apple.save
27
+ #
28
+ # You can save your apple by using the save method. New instances
29
+ # will create a new row in the database, and update its primary
30
+ # key. Old instances just update the fields. Relationships, like
31
+ # in the sample below a tree, is set to nil by default, and therefore
32
+ # the save method will not touch relationships.
33
+ #
34
+ # treeinstance.apple = [1,2,myapple]
35
+ # treeinstance.save
36
+ #
37
+ # To insert relationship data, you can provide them by array, value
38
+ # or reference, so setting treeinstance.apple to 1 is allowed, also
39
+ # to myapple, or an array or a combination. An empty array [] will
40
+ # remove all references. This works both ways, just consider the
41
+ # relationship type, as an apple cannot have more than one tree.
42
+ #
43
+ # treeinstance.delete
44
+ #
45
+ # The model provides a convenience method for deletion. It removes all
46
+ # references from relationships, but does not remove the relationships
47
+ # themselves, so you must fetch all related data, and delete them by
48
+ # 'hand' if you will.
49
+ #
50
+ #
51
+ # = Fields and foreign sample config
52
+ #
53
+ # class Apple < Ink::Model
54
+ # def self.fields
55
+ # fields = {
56
+ # :id => "PRIMARY KEY"
57
+ # :color => [ "VARCHAR", "NOT NULL" ],
58
+ # :diameter => [ "NUMERIC", "NOT NULL" ]
59
+ # }
60
+ # fields
61
+ # end
62
+ # def self.foreign
63
+ # foreign = {
64
+ # "Tree" => "one_many"
65
+ # }
66
+ # foreign
67
+ # end
68
+ # end
69
+ #
70
+ # Let's look at this construct.
71
+ # The constructor is inherited from Ink::Model, so are its
72
+ # methods. 'fields' defines a Hash of Arrays, that will
73
+ # create the Database table for us.
74
+ # 'foreign' handles the contraints to other classes, here
75
+ # it reads: one "Tree" has many Apples, other constructs
76
+ # could be: [one "Tree" has one Apple, many "Tree"s have
77
+ # many Apples, many "Tree"s have one Apple] => [one_one,
78
+ # many_many, many_one]
79
+ # Obviously the Tree class requires a foreign with "Apple"
80
+ # mapped to "many_one" to match this schema.
81
+ #
82
+ #
83
+ #
84
+ class Model
85
+
86
+ # Constructor
87
+ #
88
+ # Keys from the data parameter will be converted into
89
+ # instance variables with getters and setters in place.
90
+ # The primary key has no setter, but adds a getter called
91
+ # pk for convenience.
92
+ # [param data:] Hash of String => Objects
93
+ def initialize(data)
94
+ if self.class.respond_to? :fields
95
+ self.class.fields.each do |k,v|
96
+ raise NameError.new("Model cannot use #{k} as field, it already exists") if self.class.respond_to? k or k.to_s.downcase == "pk"
97
+ raise LoadError.new("Model cannot be loaded, argument missing: #{k}") if not data[k.to_s] and self.class.primary_key[0] != k
98
+ entry = nil
99
+ if data[k.to_s].is_a? String
100
+ entry = data[k.to_s].gsub(/'/, '&#39;')
101
+ elsif data[k.to_s].is_a? Numeric
102
+ entry = data[k.to_s]
103
+ else
104
+ entry = "\'#{data[k.to_s]}\'"
105
+ end
106
+ instance_variable_set("@#{k}", entry)
107
+
108
+ self.class.send(:define_method, k) do
109
+ instance_variable_get "@#{k}"
110
+ end
111
+ if self.class.primary_key[0] != k
112
+ self.class.send(:define_method, "#{k}=") do |val|
113
+ if val.is_a? String
114
+ val = val.gsub(/'/, '&#39;')
115
+ elsif val.is_a? Numeric
116
+ val = val
117
+ else
118
+ val = "\'#{val}\'"
119
+ end
120
+ instance_variable_set "@#{k}", val
121
+ end
122
+ else
123
+ self.class.send(:define_method, "pk") do
124
+ instance_variable_get "@#{k.to_s.downcase}"
125
+ end
126
+ end
127
+ end
128
+ if self.class.respond_to? :foreign
129
+ self.class.foreign.each do |k,v|
130
+ raise NameError.new("Model cannot use #{k} as foreign, it already exists") if self.class.respond_to? k.to_sym or k.downcase == "pk"
131
+ instance_variable_set "@#{self.class.str_to_tablename(k)}", nil
132
+ self.class.send(:define_method, k.downcase) do
133
+ instance_variable_get "@#{k.downcase}"
134
+ end
135
+ self.class.send(:define_method, "#{k.downcase}=") do |val|
136
+ instance_variable_set "@#{k.downcase}", val
137
+ end
138
+ end
139
+ end
140
+ else
141
+ data.each do |k,v|
142
+ instance_variable_set "@#{k}", (v.is_a?(Numeric)) ? v : "\'#{v}\'"
143
+ end
144
+ end
145
+ end
146
+
147
+ # Instance method
148
+ #
149
+ # Save the instance to the database. Set all foreign sets to
150
+ # nil if you do not want to change them. Old references are
151
+ # automatically removed.
152
+ def save
153
+ raise NotImplementedError.new("Cannot save to Database without field definitions") if not self.class.respond_to? :fields
154
+ string = Array.new
155
+ keystring = Array.new
156
+ valuestring = Array.new
157
+ fields = self.class.fields
158
+ pkvalue = nil
159
+ for i in 0...fields.keys.length
160
+ k = fields.keys[i]
161
+ value = instance_variable_get "@#{k}"
162
+ value = "NULL" if not value
163
+ if k != self.class.primary_key[0]
164
+ string.push "`#{k}`=#{(value.is_a?(Numeric)) ? value : "\'#{value}\'"}"
165
+ keystring.push "`#{k}`"
166
+ valuestring.push "#{(value.is_a?(Numeric)) ? value : "\'#{value}\'"}"
167
+ else
168
+ pkvalue = "WHERE `#{self.class.primary_key[0]}`=#{(value.is_a?(Numeric)) ? value : "\'#{value}\'"}"
169
+ end
170
+ end
171
+ if pkvalue
172
+ response = Ink::Database.database.find self.class.name, pkvalue
173
+ if response.length == 1
174
+ Ink::Database.database.query "UPDATE #{Ink::Model.str_to_tablename(self.class.name)} SET #{string * ","} #{pkvalue}"
175
+ elsif response.length == 0
176
+ Ink::Database.database.query "INSERT INTO #{Ink::Model.str_to_tablename(self.class.name)} (#{keystring * ","}) VALUES (#{valuestring * ","});"
177
+ pk = Ink::Database.database.last_inserted_pk
178
+ instance_variable_set "@#{self.class.primary_key[0]}", pk.is_a?(Numeric) ? pk : "\'#{pk}\'" if pk
179
+ end
180
+ end
181
+
182
+ if self.class.respond_to? :foreign
183
+ self.class.foreign.each do |k,v|
184
+ value = instance_variable_get "@#{self.class.str_to_tablename(k)}"
185
+ if value
186
+ Ink::Database.database.delete_all_links self, Ink::Model.classname(k), v
187
+ Ink::Database.database.create_all_links self, Ink::Model.classname(k), v, value
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ # Instance method
194
+ #
195
+ # Deletes the data from the database, essentially making the instance
196
+ # obsolete. Disregard from using the instance anymore.
197
+ # All links between models will be removed also.
198
+ def delete
199
+ raise NotImplementedError.new("Cannot delete from Database without field definitions") if not self.class.respond_to? :fields
200
+ if self.class.respond_to? :foreign
201
+ self.class.foreign.each do |k,v|
202
+ Ink::Database.database.delete_all_links self, Ink::Model.classname(k), v
203
+ end
204
+ end
205
+
206
+ pkvalue = instance_variable_get "@#{self.class.primary_key[0]}"
207
+ Ink::Database.database.remove self.class.name, "WHERE `#{self.class.primary_key[0]}`=#{(pkvalue.is_a?(Numeric)) ? pkvalue : "\'#{pkvalue}\'"};"
208
+ end
209
+
210
+ # Class method
211
+ #
212
+ # This will create SQL statements for creating the
213
+ # database tables. 'fields' method is mandatory for
214
+ # this, and 'foreign' is optional.
215
+ # [returns:] Array of SQL statements
216
+ def self.create
217
+ result = Array.new
218
+ raise NotImplementedError.new("Cannot create a Database without field definitions") if not self.respond_to? :fields
219
+
220
+ string = "CREATE TABLE #{Model::str_to_tablename(self.name)} ("
221
+ mfk = self.foreign_key
222
+ fields = self.fields
223
+ for i in 0...fields.keys.length
224
+ k = fields.keys[i]
225
+ string += "`#{k}` #{fields[k]*" "}" if k != self.primary_key[0]
226
+ string += "#{Ink::Database.database.primary_key_autoincrement(k)*" "}" if k == self.primary_key[0]
227
+ string += "," if i < fields.keys.length - 1
228
+ end
229
+
230
+ if self.respond_to? :foreign
231
+ foreign = self.foreign
232
+ for i in 0...foreign.keys.length
233
+ k = foreign.keys[i]
234
+ v = foreign[k]
235
+ fk = Model::classname(k).foreign_key
236
+ string += ",`#{fk[0]}` #{fk[1]}" if fk.length > 0 and (v == "one_many" or (v == "one_one" and (self.name <=> k) < 0))
237
+
238
+ if mfk.length > 0 and fk.length > 1 and v == "many_many" and (self.name <=> k) < 0
239
+ result.push "CREATE TABLE #{Model::str_to_tablename(self.name)}_#{Model::str_to_tablename(k)} (#{Ink::Database.database.primary_key_autoincrement*" "}, `#{mfk[0]}` #{mfk[1]}, `#{fk[0]}` #{fk[1]});"
240
+ end
241
+ end
242
+ end
243
+ string += ");"
244
+ result.push string
245
+ result
246
+ end
247
+
248
+ # Class method
249
+ #
250
+ # This will check the parent module for existing classnames
251
+ # that match the input of the str parameter.
252
+ # [param str:] some string
253
+ # [returns:] valid classname or nil
254
+ def self.str_to_classname(str)
255
+ ((Module.const_get str.capitalize).is_a? Class) ? str.capitalize : nil
256
+ end
257
+
258
+ # Class method
259
+ #
260
+ # This will check the parent module for existing classnames
261
+ # that match the input of the str parameter. Once found, it
262
+ # converts the string into the matching tablename.
263
+ # [param str:] some string
264
+ # [returns:] valid tablename or nil
265
+ def self.str_to_tablename(str)
266
+ ((Module.const_get str.capitalize).is_a? Class) ? str.downcase : nil
267
+ end
268
+
269
+ # Class method
270
+ #
271
+ # This will check the parent module for existing classnames
272
+ # that match the input of the str parameter. Once found, it
273
+ # returns the class, not the string of the class.
274
+ # [param str:] some string
275
+ # [returns:] valid class or nil
276
+ def self.classname(str)
277
+ ((Module.const_get str.capitalize).is_a? Class) ? (Module.const_get str.capitalize) : nil
278
+ end
279
+
280
+ # Class method
281
+ #
282
+ # This will find the primary key, as defined in the fields class
283
+ # method.
284
+ # [returns:] Array of the form: key name, key type or empty
285
+ def self.primary_key
286
+ if self.respond_to? :fields
287
+ pk = nil
288
+ pktype = nil
289
+ self.fields.each do |k,v|
290
+ if v.is_a?(String) and v == "PRIMARY KEY"
291
+ pk = k
292
+ pktype = Ink::Database.database.primary_key_autoincrement(k)[1]
293
+ end
294
+ end
295
+ return [pk, pktype]
296
+ end
297
+ return []
298
+ end
299
+
300
+ # Class method
301
+ #
302
+ # This will create the foreign key from the defined primary key
303
+ # [returns:] Array of the form: key name, key type or empty
304
+ def self.foreign_key
305
+ pk = self.primary_key
306
+ return (pk) ? ["#{self.str_to_tablename(self.name)}_#{pk[0]}", pk[1]] : []
307
+ end
308
+
309
+ end
310
+
311
+ end