webink 3.0.2 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ module Ink
2
+
3
+ class MysqlAdapter
4
+
5
+ def initialize(config)
6
+ @type = config[:db_type]
7
+ @db = Mysql.real_connect(config[:db_server],config[:db_user],
8
+ config[:db_pass],config[:db_database])
9
+ @db.reconnect = true
10
+ end
11
+
12
+ def tables
13
+ result = Array.new
14
+ re = @db.query "show tables;"
15
+ re.each do |row|
16
+ row.each do |t|
17
+ result.push t
18
+ end
19
+ end
20
+ return result
21
+ end
22
+
23
+ def query(query, type=Hash)
24
+ type = Hash if not block_given?
25
+ result = Array.new
26
+ re = @db.method("query").call query
27
+ if re
28
+ keys = re.fetch_fields.map(&:name)
29
+ re.each do |row|
30
+ result.push type.new
31
+ row.each_index do |i|
32
+ k = keys[i]
33
+ v = self.class.transform_from_sql(row[i])
34
+ if block_given?
35
+ yield(result[result.length-1], k, v)
36
+ else
37
+ result[result.length-1][k] = v
38
+ end
39
+ end
40
+ end
41
+ end
42
+ return result
43
+ end
44
+
45
+ def close
46
+ @db.close
47
+ @db = nil
48
+ end
49
+
50
+ def primary_key_autoincrement(pk="id")
51
+ ["`#{pk}`", "INTEGER", "PRIMARY KEY", "AUTO_INCREMENT"]
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,58 @@
1
+ module Ink
2
+
3
+ class Sqlite3Adapter < SqlAdapter
4
+
5
+ def initialize(config)
6
+ @type = config[:db_type]
7
+ @db = SQLite3::Database.new(config[:db_server])
8
+ end
9
+
10
+ def tables
11
+ result = Array.new
12
+ re = @db.query <<QUERY
13
+ SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;
14
+ QUERY
15
+ re.each do |row|
16
+ row.each do |t|
17
+ result.push t
18
+ end
19
+ end
20
+ re.close
21
+ return result
22
+ end
23
+
24
+ def query(query, type=Hash)
25
+ type = Hash if not block_given?
26
+ result = Array.new
27
+ re = @db.method("query").call query
28
+ re.each do |row|
29
+ result.push type.new
30
+ re.columns.each_index do |i|
31
+ row[i] = self.class.transform_from_sql(row[i])
32
+ if block_given?
33
+ yield(result[result.length-1], re.columns[i], row[i])
34
+ else
35
+ result[result.length-1][re.columns[i]] = row[i]
36
+ end
37
+ end
38
+ end
39
+ re.close if not re.closed?
40
+ return result
41
+ end
42
+
43
+ def close
44
+ return if @db.closed?
45
+ begin
46
+ @db.close
47
+ rescue SQLite3::BusyException
48
+ end
49
+ @db = nil
50
+ end
51
+
52
+ def primary_key_autoincrement(pk="id")
53
+ ["`#{pk}`", "INTEGER", "PRIMARY KEY", "ASC"]
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -2,4 +2,5 @@ require 'webink/beauty'
2
2
  require 'webink/model'
3
3
  require 'webink/controller'
4
4
  require 'webink/database'
5
+ require 'webink/sql_adapter'
5
6
 
@@ -65,6 +65,7 @@ module Ink
65
65
  # [returns:] Controller class
66
66
  def load_env
67
67
  require "#{@params[:config][:db_type]}"
68
+ require "#{@params[:config][:db_type]}_adapter"
68
69
  Dir.new("./models").each{ |m| load "./models/#{m}" if m =~ /\.rb$/ }
69
70
  load "./controllers/#{@params[:controller]}.rb"
70
71
  Ink::Controller.verify(@params[:controller]).new(@params)
@@ -25,6 +25,20 @@ module Ink
25
25
  # :db_server => "/full/path/to/database.sqlite",
26
26
  # }
27
27
  #
28
+ # == Adapters
29
+ #
30
+ # Generally before using the database, a fitting adapter needs to be
31
+ # loaded which will host the necessary methods to query the database.
32
+ #
33
+ # The SqlAdapter is an abstract class shipped with webink that is
34
+ # inherited in Sqlite3Adapter and MysqlAdapter. Any custom adapters
35
+ # can be easily built in a similar manner. More information can be
36
+ # found in the doc of SqlAdapter
37
+ #
38
+ # This means, all database does essentially, is being an interface
39
+ # to all methods offered by the adapters. Method documentation is copied
40
+ # over to the SqlAdapter for convenience.
41
+ #
28
42
  # == Usage
29
43
  #
30
44
  # Create an Ink::Database instance with the self.create class method.
@@ -107,13 +121,10 @@ module Ink
107
121
  # possible.
108
122
  # [param config:] Hash of config parameters
109
123
  def initialize(config)
110
- @type = config[:db_type]
111
- if @type == "mysql"
112
- @db = Mysql.real_connect(config[:db_server],config[:db_user],
113
- config[:db_pass],config[:db_database])
114
- @db.reconnect = true
115
- elsif @type == "sqlite3"
116
- @db = SQLite3::Database.new(config[:db_server])
124
+ klass = Ink.const_get("#{config[:db_type].capitalize}Adapter")
125
+ if klass.is_a?(Class)
126
+ @db_class = klass
127
+ @db = klass.new(config)
117
128
  else
118
129
  raise ArgumentError.new("Database undefined.")
119
130
  end
@@ -152,26 +163,7 @@ module Ink
152
163
  # the connected database.
153
164
  # [returns:] Array of tables
154
165
  def tables
155
- result = Array.new
156
- if @type == "mysql"
157
- re = @db.query "show tables;"
158
- re.each do |row|
159
- row.each do |t|
160
- result.push t
161
- end
162
- end
163
- elsif @type == "sqlite3"
164
- re = @db.query <<QUERY
165
- SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;
166
- QUERY
167
- re.each do |row|
168
- row.each do |t|
169
- result.push t
170
- end
171
- end
172
- re.close
173
- end
174
- result
166
+ @db.tables
175
167
  end
176
168
 
177
169
  # Class method
@@ -182,15 +174,7 @@ QUERY
182
174
  # [param value:] Object
183
175
  # [returns:] transformed String
184
176
  def self.transform_to_sql(value)
185
- if value.nil?
186
- "NULL"
187
- elsif value.is_a? String
188
- "\'#{value.gsub(/'/, '&#39;')}\'"
189
- elsif value.is_a? Numeric
190
- value
191
- else
192
- "\'#{value}\'"
193
- end
177
+ @db_class.transform_to_sql(value)
194
178
  end
195
179
 
196
180
  # Class method
@@ -201,15 +185,7 @@ QUERY
201
185
  # [param value:] String
202
186
  # [returns:] Object
203
187
  def self.transform_from_sql(value)
204
- if value =~ /^NULL$/
205
- nil
206
- elsif value =~ /^\d+$/
207
- value.to_i
208
- elsif value =~ /^\d+\.\d+$/
209
- value.to_f
210
- else
211
- value
212
- end
188
+ @db_class.transform_from_sql(value)
213
189
  end
214
190
 
215
191
  # Instance method
@@ -219,41 +195,7 @@ QUERY
219
195
  # [param query:] SQL query string
220
196
  # [returns:] Array of Hashes of column_name => column_entry
221
197
  def query(query, type=Hash)
222
- type = Hash if not block_given?
223
- result = Array.new
224
- if @type == "mysql"
225
- re = @db.method("query").call query
226
- if re
227
- keys = re.fetch_fields.map(&:name)
228
- re.each do |row|
229
- result.push type.new
230
- row.each_index do |i|
231
- k = keys[i]
232
- v = self.class.transform_from_sql(row[i])
233
- if block_given?
234
- yield(result[result.length-1], k, v)
235
- else
236
- result[result.length-1][k] = v
237
- end
238
- end
239
- end
240
- end
241
- elsif @type == "sqlite3"
242
- re = @db.method("query").call query
243
- re.each do |row|
244
- result.push type.new
245
- re.columns.each_index do |i|
246
- row[i] = self.class.transform_from_sql(row[i])
247
- if block_given?
248
- yield(result[result.length-1], re.columns[i], row[i])
249
- else
250
- result[result.length-1][re.columns[i]] = row[i]
251
- end
252
- end
253
- end
254
- re.close if not re.closed?
255
- end
256
- result
198
+ @db.query(query, type)
257
199
  end
258
200
 
259
201
  # Instance method
@@ -261,14 +203,7 @@ QUERY
261
203
  # Closes the database connection, there is no way
262
204
  # to reopen without creating a new Ink::Database instance
263
205
  def close
264
- if @type == "sqlite3" and not @db.closed?
265
- begin
266
- @db.close
267
- rescue SQLite3::BusyException
268
- end
269
- elsif @type == "mysql"
270
- @db.close
271
- end
206
+ @db.close
272
207
  self.class.drop
273
208
  end
274
209
 
@@ -278,14 +213,7 @@ QUERY
278
213
  # [param class_name:] Defines the __table__ name or class
279
214
  # [returns:] primary key or nil
280
215
  def last_inserted_pk(class_name)
281
- unless class_name.is_a?(Class)
282
- class_name = Ink::Model.classname(class_name)
283
- end
284
- table_name = class_name.table_name
285
- pk_name = class_name.primary_key
286
- return if table_name.nil? or pk_name.nil?
287
- response = self.query("SELECT MAX(#{pk_name}) as id FROM #{table_name};")
288
- return (response.empty?) ? nil : response.first["id"]
216
+ @db.last_inserted_pk(class_name)
289
217
  end
290
218
 
291
219
  # Instance method
@@ -294,13 +222,7 @@ QUERY
294
222
  # to define a primary key, autoincrementing field
295
223
  # [returns:] SQL syntax for a primary key field
296
224
  def primary_key_autoincrement(pk="id")
297
- result = Array.new
298
- if @type == "mysql"
299
- result = ["`#{pk}`", "INTEGER", "PRIMARY KEY", "AUTO_INCREMENT"]
300
- elsif @type == "sqlite3"
301
- result = ["`#{pk}`", "INTEGER", "PRIMARY KEY", "ASC"]
302
- end
303
- result
225
+ @db.primary_key_autoincrement(pk)
304
226
  end
305
227
 
306
228
  # Instance method
@@ -309,10 +231,7 @@ QUERY
309
231
  # [param class_name:] Defines the class name or class
310
232
  # [param params:] Additional SQL syntax like WHERE conditions (optional)
311
233
  def remove(class_name, params="")
312
- table_name = (class_name.is_a? Class) ? class_name.table_name :
313
- Ink::Model.str_to_tablename(class_name)
314
- return if table_name.nil?
315
- self.query("DELETE FROM #{table_name} #{params};")
234
+ @db.remove(class_name, params)
316
235
  end
317
236
 
318
237
  # Instance method
@@ -323,19 +242,7 @@ QUERY
323
242
  # [param params:] Additional SQL syntax like WHERE conditions (optional)
324
243
  # [returns:] Array of class_name instances from the SQL result set
325
244
  def find(class_name, params="")
326
- unless class_name.is_a?(Class)
327
- class_name = Ink::Model.classname(class_name)
328
- end
329
- result = Array.new
330
- table_name = class_name.table_name
331
- return result if table_name.nil?
332
-
333
- re = self.query("SELECT * FROM #{table_name} #{params};")
334
- re.each do |entry|
335
- instance = class_name.new entry
336
- result.push instance
337
- end
338
- result
245
+ @db.find(class_name, params)
339
246
  end
340
247
 
341
248
  # Instance method
@@ -349,32 +256,7 @@ QUERY
349
256
  # [param params:] Additional SQL syntax like GROUP BY (optional)
350
257
  # [returns:] Array of class2 instances from the SQL result set
351
258
  def find_union(class1, class1_id, class2, params="")
352
- class1 = Ink::Model.classname(class1) unless class1.is_a? Class
353
- class2 = Ink::Model.classname(class2) unless class2.is_a? Class
354
- result = Array.new
355
- relationship = nil
356
- class1.foreign.each do |k,v|
357
- relationship = v if k == class2.class_name
358
- end
359
- return result if relationship != "many_many"
360
- fk1 = class1.foreign_key
361
- pk2 = class2.primary_key
362
- fk2 = class2.foreign_key
363
- tablename1 = class1.table_name
364
- tablename2 = class2.table_name
365
- union_class = ((class1.class_name <=> class2.class_name) < 0) ?
366
- "#{tablename1}_#{tablename2}" :
367
- "#{tablename2}_#{tablename1}"
368
- re = self.query <<QUERY
369
- SELECT #{tablename2}.* FROM #{union_class}, #{tablename2}
370
- WHERE #{union_class}.#{fk1} = #{class1_id}
371
- AND #{union_class}.#{fk2} = #{tablename2}.#{pk2} #{params};
372
- QUERY
373
- re.each do |entry|
374
- instance = class2.new entry
375
- result.push instance
376
- end
377
- result
259
+ @db.find_union(class1, class1_id, class2, params)
378
260
  end
379
261
 
380
262
  # Instance method
@@ -387,38 +269,7 @@ QUERY
387
269
  # [param params:] Additional SQL syntax like GROUP BY (optional)
388
270
  # [returns:] Array of class2 instances from the SQL result set
389
271
  def find_references(class1, class1_id, class2, params="")
390
- class1 = Ink::Model.classname(class1) unless class1.is_a? Class
391
- class2 = Ink::Model.classname(class2) unless class2.is_a? Class
392
- result = Array.new
393
- relationship = nil
394
- class1.foreign.each do |k,v|
395
- relationship = v if k == class2.class_name
396
- end
397
- return result if relationship == "many_many"
398
- re = Array.new
399
- fk1 = class1.foreign_key
400
- tablename1 = class1.table_name
401
- tablename2 = class2.table_name
402
- if ((class1.class_name <=> class2.class_name) < 0 and
403
- relationship == "one_one") or relationship == "one_many"
404
- re = self.query <<QUERY
405
- SELECT * FROM #{tablename2}
406
- WHERE #{class2.primary_key}=(
407
- SELECT #{class2.foreign_key} FROM #{tablename1}
408
- WHERE #{class1.primary_key}=#{class1_id}
409
- );
410
- QUERY
411
- else
412
- re = self.query <<QUERY
413
- SELECT * FROM #{tablename2} WHERE #{fk1} = #{class1_id} #{params};
414
- QUERY
415
- end
416
-
417
- re.each do |entry|
418
- instance = class2.new entry
419
- result.push instance
420
- end
421
- result
272
+ @db.find_references(class1, class1_id, class2, params)
422
273
  end
423
274
 
424
275
  # Instance method
@@ -432,12 +283,7 @@ QUERY
432
283
  # [param params:] Additional SQL syntax like GROUP BY (optional)
433
284
  # [returns:] single class2 instance from the SQL result set or nil
434
285
  def find_reference(class1, class1_id, class2, params="")
435
- result_array = self.find_references class1, class1_id, class2, params
436
- if result_array.length == 1
437
- result_array.first
438
- else
439
- nil
440
- end
286
+ @db.find_reference(class1, class1_id, class2, params)
441
287
  end
442
288
 
443
289
  # Instance method
@@ -450,44 +296,7 @@ QUERY
450
296
  # [param link:] the related class (not a String, but class reference)
451
297
  # [param type:] relationship type
452
298
  def delete_all_links(instance, link, type)
453
- if type == "one_one"
454
- firstclass =
455
- ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
456
- instance.class : link
457
- secondclass =
458
- ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
459
- link : instance.class
460
- key =
461
- ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
462
- instance.class.primary_key : instance.class.foreign_key
463
- value = instance.method(instance.class.primary_key).call
464
- @db.query <<QUERY
465
- UPDATE #{firstclass.table_name}
466
- SET #{secondclass.foreign_key}=NULL
467
- WHERE #{key}=#{value};
468
- QUERY
469
- elsif type == "one_many" or type == "many_one"
470
- firstclass = (type == "one_many") ? instance.class : link
471
- secondclass = (type == "one_many") ? link : instance.class
472
- key = (type == "one_many") ? instance.class.primary_key :
473
- instance.class.foreign_key
474
- value = instance.method(instance.class.primary_key).call
475
- @db.query <<QUERY
476
- UPDATE #{firstclass.table_name}
477
- SET #{secondclass.foreign_key}=NULL
478
- WHERE #{key}=#{value};
479
- QUERY
480
- elsif type == "many_many"
481
- tablename1 = instance.class.table_name
482
- tablename2 = link.table_name
483
- union_class =
484
- ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
485
- "#{tablename1}_#{tablename2}" : "#{tablename2}_#{tablename1}"
486
- value = instance.method(instance.class.primary_key).call
487
- @db.query <<QUERY
488
- DELETE FROM #{union_class} WHERE #{instance.class.foreign_key}=#{value};
489
- QUERY
490
- end
299
+ @db.delete_all_links(instance, link, type)
491
300
  end
492
301
 
493
302
  # Instance method
@@ -503,23 +312,7 @@ QUERY
503
312
  # [param value:] relationship data that was set, either a primary key value,
504
313
  # or an instance, or an array of both
505
314
  def create_all_links(instance, link, type, value)
506
- to_add = Array.new
507
- if value.is_a? Array
508
- value.each do |v|
509
- if v.instance_of? link
510
- to_add.push(v.method(link.primary_key).call)
511
- else
512
- to_add.push v
513
- end
514
- end
515
- elsif value.instance_of? link
516
- to_add.push(value.method(link.primary_key).call)
517
- else
518
- to_add.push value
519
- end
520
- to_add.each do |fk|
521
- self.create_link instance, link, type, fk
522
- end
315
+ @db.create_all_links(instance, link, type, value)
523
316
  end
524
317
 
525
318
  # Instance method
@@ -534,55 +327,7 @@ QUERY
534
327
  # [param type:] relationship type
535
328
  # [param value:] primary key of the relationship, that is to be created
536
329
  def create_link(instance, link, type, fk)
537
- if type == "one_one"
538
- if (instance.class.name.downcase <=> link.name.downcase) < 0
539
- re = self.find(link.name, "WHERE #{link.primary_key}=#{fk};").first
540
- self.delete_all_links re, instance.class, type
541
- end
542
- firstclass =
543
- ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
544
- instance.class : link
545
- secondclass =
546
- ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
547
- link : instance.class
548
- key =
549
- ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
550
- instance.class.primary_key : link.primary_key
551
- value = instance.method(instance.class.primary_key).call
552
- fk_set =
553
- ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
554
- fk : value
555
- value_set =
556
- ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
557
- value : fk
558
- @db.query <<QUERY
559
- UPDATE #{firstclass.table_name} SET #{secondclass.foreign_key}=#{fk}
560
- WHERE #{key}=#{value};
561
- QUERY
562
- elsif type == "one_many" or type == "many_one"
563
- firstclass = (type == "one_many") ? instance.class : link
564
- secondclass = (type == "one_many") ? link : instance.class
565
- key = (type == "one_many") ? instance.class.primary_key :
566
- link.primary_key
567
- value = instance.method(instance.class.primary_key).call
568
- fk_set = (type == "one_many") ? fk : value
569
- value_set = (type == "one_many") ? value : fk
570
- @db.query <<QUERY
571
- UPDATE #{firstclass.table_name} SET #{secondclass.foreign_key}=#{fk_set}
572
- WHERE #{key}=#{value_set};
573
- QUERY
574
- elsif type == "many_many"
575
- tablename1 = instance.class.table_name
576
- tablename2 = link.table_name
577
- union_class =
578
- ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
579
- "#{tablename1}_#{tablename2}" : "#{tablename2}_#{tablename1}"
580
- value = instance.method(instance.class.primary_key).call
581
- @db.query <<QUERY
582
- INSERT INTO #{union_class}
583
- (#{instance.class.foreign_key}, #{link.foreign_key}) VALUES (#{value}, #{fk});
584
- QUERY
585
- end
330
+ @db.create_link(instance, link, type, fk)
586
331
  end
587
332
 
588
333
  # Class method
@@ -591,7 +336,7 @@ QUERY
591
336
  # [param date:] Time object
592
337
  # [returns:] Formatted string
593
338
  def self.format_date(date)
594
- (date.instance_of? Time) ? date.strftime("%Y-%m-%d %H:%M:%S") : ""
339
+ @db_class.format_date(date)
595
340
  end
596
341
 
597
342
  end
@@ -0,0 +1,443 @@
1
+ module Ink
2
+
3
+ # = Database Adapter class for SQL-Databases
4
+ #
5
+ # This class should be extended by any implementations of a database
6
+ # adapter. Adapters need to follow a naming convention:
7
+ #
8
+ # module Ink
9
+ # class <capitalized_db_gem_name>Adapter
10
+ # end
11
+ # end
12
+ #
13
+ # Example:
14
+ #
15
+ # module Ink
16
+ # class MysqlAdapater
17
+ # end
18
+ # end
19
+ #
20
+ # The database is extracted from the field :db_type in the config passed
21
+ # on to the adapter and also describes the gem name to include.
22
+ #
23
+ # All inheriting classes need to override these methods:
24
+ # initialize(config)
25
+ # tables
26
+ # query(query, type=Hash)
27
+ # close
28
+ # primary_key_autoincrement(pk="id")
29
+ #
30
+ # == Usage
31
+ #
32
+ # The necessary instance to connect to the database is automatically loaded
33
+ # by Ink::Database. As long as naming conventions are followed, additional
34
+ # modules can be included.
35
+ #
36
+ # == Convenience methods
37
+ #
38
+ # A series of convenience methods are located here instead of Ink::Database
39
+ # for the sole reason that timestamps etc. may differ in various database
40
+ # implementations and being able to override in adapters will help connecting
41
+ # to exactly those databases.
42
+ #
43
+ #
44
+ #
45
+ class SqlAdapter
46
+
47
+ # Abstract Constructor
48
+ #
49
+ # Uses the config parameter to create a database
50
+ # connection, and will throw an error, if that is not
51
+ # possible.
52
+ # [param config:] Hash of config parameters
53
+ def initialize(config)
54
+ raise NotImplementedError.new('Override initialize')
55
+ end
56
+
57
+ # Abstract Instance method
58
+ #
59
+ # This will retrieve all tables nested into
60
+ # the connected database.
61
+ # [returns:] Array of tables
62
+ def tables
63
+ raise NotImplementedError.new('Override tables')
64
+ end
65
+
66
+ # Abstract Instance method
67
+ #
68
+ # Send an SQL query string to the database
69
+ # and retrieve a result set
70
+ # [param query:] SQL query string
71
+ # [returns:] Array of Hashes of column_name => column_entry
72
+ def query(query, type=Hash)
73
+ raise NotImplementedError.new('Override query')
74
+ end
75
+
76
+ # Abstract Instance method
77
+ #
78
+ # Closes the database connection, there is no way
79
+ # to reopen without creating a new Ink::Database instance
80
+ def close
81
+ raise NotImplementedError.new('Override close')
82
+ end
83
+
84
+ # Abstract Instance method
85
+ #
86
+ # Creates the SQL syntax for the chosen database type
87
+ # to define a primary key, autoincrementing field
88
+ # [returns:] SQL syntax for a primary key field
89
+ def primary_key_autoincrement(pk="id")
90
+ raise NotImplementedError.new('Override primary_key_autoincrement')
91
+ end
92
+
93
+
94
+
95
+ # Class method
96
+ #
97
+ # Formats a Time object according to the SQL TimeDate standard
98
+ # [param date:] Time object
99
+ # [returns:] Formatted string
100
+ def self.format_date(date)
101
+ (date.instance_of? Time) ? date.strftime("%Y-%m-%d %H:%M:%S") : ""
102
+ end
103
+
104
+ # Class method
105
+ #
106
+ # Transform a value to sql representative values.
107
+ # This means quotes are escaped, nils are transformed
108
+ # and everything else is quoted.
109
+ # [param value:] Object
110
+ # [returns:] transformed String
111
+ def self.transform_to_sql(value)
112
+ if value.nil?
113
+ "NULL"
114
+ elsif value.is_a? String
115
+ "\'#{value.gsub(/'/, '&#39;')}\'"
116
+ elsif value.is_a? Numeric
117
+ value
118
+ else
119
+ "\'#{value}\'"
120
+ end
121
+ end
122
+
123
+ # Class method
124
+ #
125
+ # Transform a value from sql to objects.
126
+ # This means nils, integer, floats and strings
127
+ # are imported correctly.
128
+ # [param value:] String
129
+ # [returns:] Object
130
+ def self.transform_from_sql(value)
131
+ if value =~ /^NULL$/
132
+ nil
133
+ elsif value =~ /^\d+$/
134
+ value.to_i
135
+ elsif value =~ /^\d+\.\d+$/
136
+ value.to_f
137
+ else
138
+ value
139
+ end
140
+ end
141
+
142
+ # Instance method
143
+ #
144
+ # Attempts to fetch the last inserted primary key
145
+ # [param class_name:] Defines the __table__ name or class
146
+ # [returns:] primary key or nil
147
+ def last_inserted_pk(class_name)
148
+ unless class_name.is_a?(Class)
149
+ class_name = Ink::Model.classname(class_name)
150
+ end
151
+ table_name = class_name.table_name
152
+ pk_name = class_name.primary_key
153
+ return if table_name.nil? or pk_name.nil?
154
+ response = self.query("SELECT MAX(#{pk_name}) as id FROM #{table_name};")
155
+ return (response.empty?) ? nil : response.first["id"]
156
+ end
157
+
158
+ # Instance method
159
+ #
160
+ # Delete something from the database.
161
+ # [param class_name:] Defines the class name or class
162
+ # [param params:] Additional SQL syntax like WHERE conditions (optional)
163
+ def remove(class_name, params="")
164
+ table_name = (class_name.is_a? Class) ? class_name.table_name :
165
+ Ink::Model.str_to_tablename(class_name)
166
+ return if table_name.nil?
167
+ self.query("DELETE FROM #{table_name} #{params};")
168
+ end
169
+
170
+ # Instance method
171
+ #
172
+ # Retrieve class instances, that are loaded with the database result set.
173
+ # [param class_name:] Defines the class name or class which should be
174
+ # queried
175
+ # [param params:] Additional SQL syntax like WHERE conditions (optional)
176
+ # [returns:] Array of class_name instances from the SQL result set
177
+ def find(class_name, params="")
178
+ unless class_name.is_a?(Class)
179
+ class_name = Ink::Model.classname(class_name)
180
+ end
181
+ result = Array.new
182
+ table_name = class_name.table_name
183
+ return result if table_name.nil?
184
+
185
+ re = self.query("SELECT * FROM #{table_name} #{params};")
186
+ re.each do |entry|
187
+ instance = class_name.new entry
188
+ result.push instance
189
+ end
190
+ result
191
+ end
192
+
193
+ # Instance method
194
+ #
195
+ # Retrieve class2 instances, that are related to the class1 instance with
196
+ # primary key class1_id. This is done via an additional relationship table.
197
+ # Only relevant for many_many relationships.
198
+ # [param class1:] Reference classname or class
199
+ # [param class1_id:] Primary key value of the reference classname
200
+ # [param class2:] Match classname or class
201
+ # [param params:] Additional SQL syntax like GROUP BY (optional)
202
+ # [returns:] Array of class2 instances from the SQL result set
203
+ def find_union(class1, class1_id, class2, params="")
204
+ class1 = Ink::Model.classname(class1) unless class1.is_a? Class
205
+ class2 = Ink::Model.classname(class2) unless class2.is_a? Class
206
+ result = Array.new
207
+ relationship = nil
208
+ class1.foreign.each do |k,v|
209
+ relationship = v if k == class2.class_name
210
+ end
211
+ return result if relationship != "many_many"
212
+ fk1 = class1.foreign_key
213
+ pk2 = class2.primary_key
214
+ fk2 = class2.foreign_key
215
+ tablename1 = class1.table_name
216
+ tablename2 = class2.table_name
217
+ union_class = ((class1.class_name <=> class2.class_name) < 0) ?
218
+ "#{tablename1}_#{tablename2}" :
219
+ "#{tablename2}_#{tablename1}"
220
+ re = self.query <<QUERY
221
+ SELECT #{tablename2}.* FROM #{union_class}, #{tablename2}
222
+ WHERE #{union_class}.#{fk1} = #{class1_id}
223
+ AND #{union_class}.#{fk2} = #{tablename2}.#{pk2} #{params};
224
+ QUERY
225
+ re.each do |entry|
226
+ instance = class2.new entry
227
+ result.push instance
228
+ end
229
+ result
230
+ end
231
+
232
+ # Instance method
233
+ #
234
+ # Retrieve class2 instances, that are related to the class1 instance with
235
+ # primary key class1_id. Not relevant for many_many relationships
236
+ # [param class1:] Reference classname or class
237
+ # [param class1_id:] Primary key value of the reference classname
238
+ # [param class2:] Match classname or class
239
+ # [param params:] Additional SQL syntax like GROUP BY (optional)
240
+ # [returns:] Array of class2 instances from the SQL result set
241
+ def find_references(class1, class1_id, class2, params="")
242
+ class1 = Ink::Model.classname(class1) unless class1.is_a? Class
243
+ class2 = Ink::Model.classname(class2) unless class2.is_a? Class
244
+ result = Array.new
245
+ relationship = nil
246
+ class1.foreign.each do |k,v|
247
+ relationship = v if k == class2.class_name
248
+ end
249
+ return result if relationship == "many_many"
250
+ re = Array.new
251
+ fk1 = class1.foreign_key
252
+ tablename1 = class1.table_name
253
+ tablename2 = class2.table_name
254
+ if ((class1.class_name <=> class2.class_name) < 0 and
255
+ relationship == "one_one") or relationship == "one_many"
256
+ re = self.query <<QUERY
257
+ SELECT * FROM #{tablename2}
258
+ WHERE #{class2.primary_key}=(
259
+ SELECT #{class2.foreign_key} FROM #{tablename1}
260
+ WHERE #{class1.primary_key}=#{class1_id}
261
+ );
262
+ QUERY
263
+ else
264
+ re = self.query <<QUERY
265
+ SELECT * FROM #{tablename2} WHERE #{fk1} = #{class1_id} #{params};
266
+ QUERY
267
+ end
268
+
269
+ re.each do |entry|
270
+ instance = class2.new entry
271
+ result.push instance
272
+ end
273
+ result
274
+ end
275
+
276
+ # Instance method
277
+ #
278
+ # Retrieve one class2 instance, that is related to the class1 instance with
279
+ # primary key class1_id. Only relevant for one_one and one_many
280
+ # relationships
281
+ # [param class1:] Reference classname or class
282
+ # [param class1_id:] Primary key value of the reference classname
283
+ # [param class2:] Match classname or class
284
+ # [param params:] Additional SQL syntax like GROUP BY (optional)
285
+ # [returns:] single class2 instance from the SQL result set or nil
286
+ def find_reference(class1, class1_id, class2, params="")
287
+ result_array = self.find_references class1, class1_id, class2, params
288
+ if result_array.length == 1
289
+ result_array.first
290
+ else
291
+ nil
292
+ end
293
+ end
294
+
295
+ # Instance method
296
+ #
297
+ # This method attempts to remove all existing relationship data
298
+ # of instance with link of type: type. For one_one relationships
299
+ # this works only one way, requiring a second call later on before
300
+ # setting a new value.
301
+ # [param instance:] Instance of a class that refers to an existing database
302
+ # entry
303
+ # [param link:] the related class (not a String, but class reference)
304
+ # [param type:] relationship type
305
+ def delete_all_links(instance, link, type)
306
+ if type == "one_one"
307
+ firstclass =
308
+ ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
309
+ instance.class : link
310
+ secondclass =
311
+ ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
312
+ link : instance.class
313
+ key =
314
+ ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
315
+ instance.class.primary_key : instance.class.foreign_key
316
+ value = instance.method(instance.class.primary_key).call
317
+ @db.query <<QUERY
318
+ UPDATE #{firstclass.table_name}
319
+ SET #{secondclass.foreign_key}=NULL
320
+ WHERE #{key}=#{value};
321
+ QUERY
322
+ elsif type == "one_many" or type == "many_one"
323
+ firstclass = (type == "one_many") ? instance.class : link
324
+ secondclass = (type == "one_many") ? link : instance.class
325
+ key = (type == "one_many") ? instance.class.primary_key :
326
+ instance.class.foreign_key
327
+ value = instance.method(instance.class.primary_key).call
328
+ @db.query <<QUERY
329
+ UPDATE #{firstclass.table_name}
330
+ SET #{secondclass.foreign_key}=NULL
331
+ WHERE #{key}=#{value};
332
+ QUERY
333
+ elsif type == "many_many"
334
+ tablename1 = instance.class.table_name
335
+ tablename2 = link.table_name
336
+ union_class =
337
+ ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
338
+ "#{tablename1}_#{tablename2}" : "#{tablename2}_#{tablename1}"
339
+ value = instance.method(instance.class.primary_key).call
340
+ @db.query <<QUERY
341
+ DELETE FROM #{union_class} WHERE #{instance.class.foreign_key}=#{value};
342
+ QUERY
343
+ end
344
+ end
345
+
346
+ # Instance method
347
+ #
348
+ # Attempt to create links of instance to the data inside value.
349
+ # link is the class of the related data, and type refers to the
350
+ # relationship type of the two. When one tries to insert an array
351
+ # for a x_one relationship, the last entry will be set.
352
+ # [param instance:] Instance of a class that refers to an existing
353
+ # database entry
354
+ # [param link:] the related class (not a String, but class reference)
355
+ # [param type:] relationship type
356
+ # [param value:] relationship data that was set, either a primary key value,
357
+ # or an instance, or an array of both
358
+ def create_all_links(instance, link, type, value)
359
+ to_add = Array.new
360
+ if value.is_a? Array
361
+ value.each do |v|
362
+ if v.instance_of? link
363
+ to_add.push(v.method(link.primary_key).call)
364
+ else
365
+ to_add.push v
366
+ end
367
+ end
368
+ elsif value.instance_of? link
369
+ to_add.push(value.method(link.primary_key).call)
370
+ else
371
+ to_add.push value
372
+ end
373
+ to_add.each do |fk|
374
+ self.create_link instance, link, type, fk
375
+ end
376
+ end
377
+
378
+ # Instance method
379
+ #
380
+ # Creates a link between instance and a link with primary fk.
381
+ # The relationship between the two is defined by type. one_one
382
+ # relationships are placing an additional call to delete_all_links
383
+ # that will remove conflicts.
384
+ # [param instance:] Instance of a class that refers to an existing database
385
+ # entry
386
+ # [param link:] the related class (not a String, but class reference)
387
+ # [param type:] relationship type
388
+ # [param value:] primary key of the relationship, that is to be created
389
+ def create_link(instance, link, type, fk)
390
+ if type == "one_one"
391
+ if (instance.class.name.downcase <=> link.name.downcase) < 0
392
+ re = self.find(link.name, "WHERE #{link.primary_key}=#{fk};").first
393
+ self.delete_all_links re, instance.class, type
394
+ end
395
+ firstclass =
396
+ ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
397
+ instance.class : link
398
+ secondclass =
399
+ ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
400
+ link : instance.class
401
+ key =
402
+ ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
403
+ instance.class.primary_key : link.primary_key
404
+ value = instance.method(instance.class.primary_key).call
405
+ fk_set =
406
+ ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
407
+ fk : value
408
+ value_set =
409
+ ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
410
+ value : fk
411
+ @db.query <<QUERY
412
+ UPDATE #{firstclass.table_name} SET #{secondclass.foreign_key}=#{fk}
413
+ WHERE #{key}=#{value};
414
+ QUERY
415
+ elsif type == "one_many" or type == "many_one"
416
+ firstclass = (type == "one_many") ? instance.class : link
417
+ secondclass = (type == "one_many") ? link : instance.class
418
+ key = (type == "one_many") ? instance.class.primary_key :
419
+ link.primary_key
420
+ value = instance.method(instance.class.primary_key).call
421
+ fk_set = (type == "one_many") ? fk : value
422
+ value_set = (type == "one_many") ? value : fk
423
+ @db.query <<QUERY
424
+ UPDATE #{firstclass.table_name} SET #{secondclass.foreign_key}=#{fk_set}
425
+ WHERE #{key}=#{value_set};
426
+ QUERY
427
+ elsif type == "many_many"
428
+ tablename1 = instance.class.table_name
429
+ tablename2 = link.table_name
430
+ union_class =
431
+ ((instance.class.name.downcase <=> link.name.downcase) < 0) ?
432
+ "#{tablename1}_#{tablename2}" : "#{tablename2}_#{tablename1}"
433
+ value = instance.method(instance.class.primary_key).call
434
+ @db.query <<QUERY
435
+ INSERT INTO #{union_class}
436
+ (#{instance.class.foreign_key}, #{link.foreign_key}) VALUES (#{value}, #{fk});
437
+ QUERY
438
+ end
439
+ end
440
+
441
+ end
442
+
443
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webink
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-09-20 00:00:00.000000000 Z
12
+ date: 2014-01-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -36,12 +36,15 @@ extensions: []
36
36
  extra_rdoc_files: []
37
37
  files:
38
38
  - lib/webink.rb
39
+ - lib/sqlite3_adapter.rb
40
+ - lib/mysql_adapter.rb
39
41
  - lib/webink/beauty.rb
40
- - lib/webink/database.rb
41
- - lib/webink/controller.rb
42
42
  - lib/webink/model.rb
43
- - bin/webink_init
43
+ - lib/webink/controller.rb
44
+ - lib/webink/sql_adapter.rb
45
+ - lib/webink/database.rb
44
46
  - bin/webink_database
47
+ - bin/webink_init
45
48
  - LICENSE.md
46
49
  homepage: https://github.com/matthias-geier/WebInk
47
50
  licenses: []