webink 3.0.2 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []