hayabusa 0.0.24 → 0.0.29

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +363 -0
  3. data/Rakefile +0 -14
  4. data/bin/hayabusa_fcgi.fcgi +1 -0
  5. data/bin/hayabusa_fcgi_server.rb +0 -1
  6. data/lib/hayabusa.rb +124 -118
  7. data/lib/hayabusa_cgi_tools.rb +4 -2
  8. data/lib/hayabusa_client_session.rb +3 -1
  9. data/lib/hayabusa_database.rb +84 -84
  10. data/lib/hayabusa_datarow.rb +873 -0
  11. data/lib/hayabusa_ext/mailing.rb +8 -7
  12. data/lib/hayabusa_ext/web.rb +5 -0
  13. data/lib/hayabusa_fcgi.rb +13 -4
  14. data/lib/hayabusa_http_session.rb +2 -0
  15. data/lib/hayabusa_http_session_contentgroup.rb +7 -1
  16. data/lib/hayabusa_http_session_request.rb +7 -1
  17. data/lib/hayabusa_http_session_response.rb +30 -29
  18. data/lib/hayabusa_objects.rb +1455 -0
  19. data/lib/hayabusa_revision.rb +347 -0
  20. data/lib/kernel_ext/magic_methods.rb +4 -0
  21. data/lib/models/log.rb +27 -27
  22. data/lib/models/log_access.rb +20 -20
  23. data/lib/models/log_data.rb +6 -6
  24. data/lib/models/log_data_link.rb +2 -2
  25. data/lib/models/log_data_value.rb +5 -5
  26. data/lib/models/log_link.rb +12 -12
  27. data/lib/models/session.rb +6 -6
  28. metadata +125 -126
  29. data/.document +0 -5
  30. data/.rspec +0 -1
  31. data/Gemfile +0 -24
  32. data/Gemfile.lock +0 -70
  33. data/README.rdoc +0 -315
  34. data/VERSION +0 -1
  35. data/bin/hayabusa_fcgi.fcgi +0 -42
  36. data/conf/apache2_cgi_rhtml_conf.conf +0 -10
  37. data/conf/apache2_fcgi_rhtml_conf.conf +0 -22
  38. data/conf/apache2_hayabusa_conf.conf +0 -15
  39. data/hayabusa.gemspec +0 -167
  40. data/pages/benchmark.rhtml +0 -0
  41. data/pages/benchmark_print.rhtml +0 -14
  42. data/pages/benchmark_simple.rhtml +0 -3
  43. data/pages/benchmark_threadded_content.rhtml +0 -21
  44. data/pages/config_cgi.rb +0 -22
  45. data/pages/config_fcgi.rb +0 -22
  46. data/pages/debug_database_connections.rhtml +0 -46
  47. data/pages/debug_http_sessions.rhtml +0 -40
  48. data/pages/debug_memory_usage.rhtml +0 -39
  49. data/pages/error_notfound.rhtml +0 -12
  50. data/pages/image.png +0 -0
  51. data/pages/logs_latest.rhtml +0 -57
  52. data/pages/logs_show.rhtml +0 -32
  53. data/pages/spec.rhtml +0 -41
  54. data/pages/spec_exit.rhtml +0 -5
  55. data/pages/spec_multiple_threads.rhtml +0 -18
  56. data/pages/spec_sleeper.rhtml +0 -4
  57. data/pages/spec_test_multiple_clients.rhtml +0 -3
  58. data/pages/spec_thread_joins.rhtml +0 -29
  59. data/pages/spec_threadded_content.rhtml +0 -40
  60. data/pages/spec_vars_get.rhtml +0 -4
  61. data/pages/spec_vars_header.rhtml +0 -3
  62. data/pages/spec_vars_post.rhtml +0 -4
  63. data/pages/spec_vars_post_fileupload.rhtml +0 -19
  64. data/pages/testpic.jpeg +0 -0
  65. data/pages/tests.rhtml +0 -14
  66. data/pages/threadded_content_test.rhtml +0 -23
  67. data/spec/fcgi_multiple_processes_spec.rb +0 -104
  68. data/spec/hayabusa_spec.rb +0 -423
  69. data/spec/spec_helper.rb +0 -12
  70. data/spec/test_upload.xlsx +0 -0
@@ -0,0 +1,1455 @@
1
+ class Hayabusa::Objects
2
+ attr_reader :args, :events, :data, :ids_cache, :ids_cache_should
3
+
4
+ def initialize(args)
5
+ require "monitor"
6
+
7
+ @callbacks = {}
8
+ @args = args
9
+ @args[:col_id] = :id if !@args[:col_id]
10
+ @args[:class_pre] = "class_" if !@args[:class_pre]
11
+ @args[:module] = Kernel if !@args[:module]
12
+ @args[:cache] = :weak if !@args.key?(:cache)
13
+ @objects = {}
14
+ @locks = {}
15
+ @data = {}
16
+ @lock_require = Monitor.new
17
+
18
+ Knj.gem_require(:Wref, "wref") if @args[:cache] == :weak and !Kernel.const_defined?(:Wref)
19
+ require "#{@args[:array_enumerator_path]}array_enumerator" if @args[:array_enum] and !Kernel.const_defined?(:Array_enumerator)
20
+
21
+ #Set up various events.
22
+ @events = Knj::Event_handler.new
23
+ @events.add_event(:name => :no_html, :connections_max => 1)
24
+ @events.add_event(:name => :no_name, :connections_max => 1)
25
+ @events.add_event(:name => :no_date, :connections_max => 1)
26
+ @events.add_event(:name => :missing_class, :connections_max => 1)
27
+ @events.add_event(:name => :require_class, :connections_max => 1)
28
+
29
+ raise "No DB given." if !@args[:db] and !@args[:custom]
30
+ raise "No class path given." if !@args[:class_path] and (@args[:require] or !@args.key?(:require))
31
+
32
+ if args[:require_all]
33
+ Knj.gem_require(:Php4r, "php4r")
34
+ loads = []
35
+
36
+ Dir.foreach(@args[:class_path]) do |file|
37
+ next if file == "." or file == ".." or !file.match(/\.rb$/)
38
+ file_parsed = file
39
+ file_parsed.gsub!(@args[:class_pre], "") if @args.key?(:class_pre)
40
+ file_parsed.gsub!(/\.rb$/, "")
41
+ file_parsed = Php4r.ucwords(file_parsed)
42
+
43
+ loads << file_parsed
44
+ self.requireclass(file_parsed, {:load => false})
45
+ end
46
+
47
+ loads.each do |load_class|
48
+ self.load_class(load_class)
49
+ end
50
+ end
51
+
52
+ #Set up ID-caching.
53
+ @ids_cache_should = {}
54
+
55
+ if @args[:models]
56
+ @ids_cache = {}
57
+
58
+ @args[:models].each do |classname, classargs|
59
+ @ids_cache_should[classname] = true if classargs[:cache_ids]
60
+ self.cache_ids(classname)
61
+ end
62
+ end
63
+ end
64
+
65
+ #Caches all IDs for a specific classname.
66
+ def cache_ids(classname)
67
+ classname = classname.to_sym
68
+ return nil if !@ids_cache_should or !@ids_cache_should[classname]
69
+
70
+ newcache = {}
71
+ @args[:db].q("SELECT `#{@args[:col_id]}` FROM `#{classname}` ORDER BY `#{@args[:col_id]}`") do |data|
72
+ newcache[data[@args[:col_id]].to_i] = true
73
+ end
74
+
75
+ @ids_cache[classname] = newcache
76
+ end
77
+
78
+ def init_class(classname)
79
+ classname = classname.to_sym
80
+ return false if @objects.key?(classname)
81
+
82
+ if @args[:cache] == :weak
83
+ @objects[classname] = Wref::Map.new
84
+ else
85
+ @objects[classname] = {}
86
+ end
87
+
88
+ @locks[classname] = Monitor.new
89
+ end
90
+
91
+ def uninit_class(classname)
92
+ @objects.delete(classname)
93
+ @locks.delete(classname)
94
+ end
95
+
96
+ #Returns a cloned version of the @objects variable. Cloned because iteration on it may crash some of the other methods in Ruby 1.9+
97
+ def objects
98
+ objs_cloned = {}
99
+
100
+ @objects.keys.each do |key|
101
+ objs_cloned[key] = @objects[key].clone
102
+ end
103
+
104
+ return objs_cloned
105
+ end
106
+
107
+ #Returns the database-connection used by this instance of Objects.
108
+ def db
109
+ return @args[:db]
110
+ end
111
+
112
+ #Returns the total count of objects currently held by this instance.
113
+ def count_objects
114
+ count = 0
115
+ @objects.keys.each do |key|
116
+ count += @objects[key].length
117
+ end
118
+
119
+ return count
120
+ end
121
+
122
+ #This connects a block to an event. When the event is called the block will be executed.
123
+ def connect(args, &block)
124
+ raise "No object given." if !args["object"]
125
+ raise "No signals given." if !args.key?("signal") and !args.key?("signals")
126
+ args["block"] = block if block_given?
127
+ object = args["object"].to_sym
128
+
129
+ @callbacks[object] = {} if !@callbacks[object]
130
+ conn_id = @callbacks[object].length.to_s
131
+ @callbacks[object][conn_id] = args
132
+ return conn_id
133
+ end
134
+
135
+ #Returns true if the given signal is connected to the given object.
136
+ def connected?(args)
137
+ raise "No object given." if !args["object"]
138
+ raise "No signal given." if !args.key?("signal")
139
+ object = args["object"].to_sym
140
+
141
+ if @callbacks.key?(object)
142
+ @callbacks[object].clone.each do |ckey, callback|
143
+ return true if callback.key?("signal") and callback["signal"].to_s == args["signal"].to_s
144
+ return true if callback.key?("signals") and (callback["signals"].include?(args["signal"].to_s) or callback["signals"].include?(args["signal"].to_sym))
145
+ end
146
+ end
147
+
148
+ return false
149
+ end
150
+
151
+ #Unconnects a connect by 'object' and 'conn_id'.
152
+ def unconnect(args)
153
+ raise ArgumentError, "No object given." if !args["object"]
154
+ object = args["object"].to_sym
155
+ raise ArgumentError, "Object doesnt exist: '#{object}'." if !@callbacks.key?(object)
156
+
157
+ if args["conn_id"]
158
+ conn_ids = [args["conn_id"]]
159
+ elsif args["conn_ids"]
160
+ conn_ids = args["conn_ids"]
161
+ else
162
+ raise ArgumentError, "Could not figure out connection IDs."
163
+ end
164
+
165
+ conn_ids.each do |conn_id|
166
+ raise Errno::ENOENT, "Conn ID doest exist: '#{conn_id}' (#{args})." if !@callbacks[object].key?(conn_id)
167
+ @callbacks[object].delete(conn_id)
168
+ end
169
+ end
170
+
171
+ #This method is used to call the connected callbacks for an event.
172
+ def call(args, &block)
173
+ classstr = args["object"].class.classname.to_sym
174
+
175
+ if @callbacks.key?(classstr)
176
+ @callbacks[classstr].clone.each do |callback_key, callback|
177
+ docall = false
178
+
179
+ if callback.key?("signal") and args.key?("signal") and callback["signal"].to_s == args["signal"].to_s
180
+ docall = true
181
+ elsif callback["signals"] and args["signal"] and (callback["signals"].include?(args["signal"].to_s) or callback["signals"].include?(args["signal"].to_sym))
182
+ docall = true
183
+ end
184
+
185
+ next if !docall
186
+
187
+ if callback["block"]
188
+ callargs = []
189
+ arity = callback["block"].arity
190
+ if arity <= 0
191
+ #do nothing
192
+ elsif arity == 1
193
+ callargs << args["object"]
194
+ else
195
+ raise "Unknown number of arguments: #{arity}"
196
+ end
197
+
198
+ callback["block"].call(*callargs)
199
+ elsif callback["callback"]
200
+ require "php4r" if !Kernel.const_defined?(:Php4r)
201
+ Php4r.call_user_func(callback["callback"], args)
202
+ else
203
+ raise "No valid callback given."
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ def requireclass(classname, args = {})
210
+ classname = classname.to_sym
211
+ return false if @objects.key?(classname)
212
+
213
+ @lock_require.synchronize do
214
+ #Maybe the classname got required meanwhile the synchronized wait - check again.
215
+ return false if @objects.key?(classname)
216
+
217
+ if @events.connected?(:require_class)
218
+ @events.call(:require_class, {
219
+ :class => classname
220
+ })
221
+ else
222
+ doreq = false
223
+
224
+ if args[:require]
225
+ doreq = true
226
+ elsif args.key?(:require) and !args[:require]
227
+ doreq = false
228
+ elsif @args[:require] or !@args.key?(:require)
229
+ doreq = true
230
+ end
231
+
232
+ if doreq
233
+ filename = "#{@args[:class_path]}/#{@args[:class_pre]}#{classname.to_s.downcase}.rb"
234
+ filename_req = "#{@args[:class_path]}/#{@args[:class_pre]}#{classname.to_s.downcase}"
235
+ raise "Class file could not be found: #{filename}." if !File.exists?(filename)
236
+ require filename_req
237
+ end
238
+ end
239
+
240
+ if args[:class]
241
+ classob = args[:class]
242
+ else
243
+ begin
244
+ classob = @args[:module].const_get(classname)
245
+ rescue NameError => e
246
+ if @events.connected?(:missing_class)
247
+ @events.call(:missing_class, {
248
+ :class => classname
249
+ })
250
+ classob = @args[:module].const_get(classname)
251
+ else
252
+ raise e
253
+ end
254
+ end
255
+ end
256
+
257
+ if (classob.respond_to?(:load_columns) or classob.respond_to?(:datarow_init)) and (!args.key?(:load) or args[:load])
258
+ self.load_class(classname, args)
259
+ end
260
+
261
+ self.init_class(classname)
262
+ end
263
+ end
264
+
265
+ #Loads a Datarow-class by calling various static methods.
266
+ def load_class(classname, args = {})
267
+ if args[:class]
268
+ classob = args[:class]
269
+ else
270
+ classob = @args[:module].const_get(classname)
271
+ end
272
+
273
+ pass_arg = Knj::Hash_methods.new(:ob => self, :db => @args[:db])
274
+ classob.load_columns(pass_arg) if classob.respond_to?(:load_columns)
275
+ classob.datarow_init(pass_arg) if classob.respond_to?(:datarow_init)
276
+ end
277
+
278
+ #Returns the instance of classname, but only if it already exists.
279
+ def get_if_cached(classname, id)
280
+ classname = classname.to_sym
281
+ id = id.to_i
282
+
283
+ if @objects[classname] and obj = Wref::Map.get!(id)
284
+ return obj
285
+ end
286
+
287
+ return nil
288
+ end
289
+
290
+ #Returns true if a row of the given classname and the ID exists. Will use ID-cache if set in arguments and spawned otherwise it will do an actual lookup.
291
+ #===Examples
292
+ # print "User 5 exists." if ob.exists?(:User, 5)
293
+ def exists?(classname, id)
294
+ #Make sure the given data are in the correct types.
295
+ classname = classname.to_sym
296
+ id = id.to_i
297
+
298
+ #Check if ID-cache is enabled for that classname. Avoid SQL-lookup by using that.
299
+ if @ids_cache_should.key?(classname)
300
+ if @ids_cache[classname].key?(id)
301
+ return true
302
+ else
303
+ return false
304
+ end
305
+ end
306
+
307
+ #If the object currently exists in cache, we dont have to do a lookup either.
308
+ return true if @objects.key?(classname) and obj = @objects[classname].get!(id) and !obj.deleted?
309
+
310
+ #Okay - no other options than to actually do a real lookup.
311
+ begin
312
+ table = @args[:module].const_get(classname).table
313
+ row = @args[:db].single(table, {@args[:col_id] => id})
314
+
315
+ if row
316
+ return true
317
+ else
318
+ return false
319
+ end
320
+ rescue Errno::ENOENT
321
+ return false
322
+ end
323
+ end
324
+
325
+ #Gets an object from the ID or the full data-hash in the database.
326
+ #===Examples
327
+ # inst = ob.get(:User, 5)
328
+ def get(classname, data, args = nil)
329
+ classname = classname.to_sym
330
+
331
+ if data.is_a?(Integer) or data.is_a?(String) or data.is_a?(Fixnum)
332
+ id = data.to_i
333
+ elsif data.is_a?(Hash) and data.key?(@args[:col_id].to_sym)
334
+ id = data[@args[:col_id].to_sym].to_i
335
+ elsif data.is_a?(Hash) and data.key?(@args[:col_id].to_s)
336
+ id = data[@args[:col_id].to_s].to_i
337
+ elsif
338
+ raise ArgumentError, "Unknown data for class '#{classname}': '#{data.class.to_s}' (#{data})."
339
+ end
340
+
341
+ if @objects.key?(classname)
342
+ case @args[:cache]
343
+ when :weak
344
+ if obj = @objects[classname].get(id) and obj.id.to_i == id
345
+ return obj
346
+ end
347
+ else
348
+ return @objects[classname][id] if @objects[classname].key?(id)
349
+ end
350
+ end
351
+
352
+ self.requireclass(classname) if !@objects.key?(classname)
353
+
354
+ @locks[classname].synchronize do
355
+ #Maybe the object got spawned while we waited for the lock? If so we shouldnt spawn another instance.
356
+ if @args[:cache] == :weak and obj = @objects[classname].get(id) and obj.id.to_i == id
357
+ return obj
358
+ end
359
+
360
+ #Spawn object.
361
+ if @args[:datarow] or @args[:custom]
362
+ obj = @args[:module].const_get(classname).new(data, args)
363
+ else
364
+ pass_args = [data]
365
+ pass_args = pass_args | @args[:extra_args] if @args[:extra_args]
366
+ obj = @args[:module].const_get(classname).new(*pass_args)
367
+ end
368
+
369
+ #Save object in cache.
370
+ case @args[:cache]
371
+ when :none
372
+ return obj
373
+ else
374
+ @objects[classname][id] = obj
375
+ return obj
376
+ end
377
+ end
378
+
379
+ raise "Unexpected run?"
380
+ end
381
+
382
+ #Same as normal get but returns false if not found instead of raising error.
383
+ def get!(*args, &block)
384
+ begin
385
+ return self.get(*args, &block)
386
+ rescue Errno::ENOENT
387
+ return false
388
+ end
389
+ end
390
+
391
+ def object_finalizer(id)
392
+ classname = @objects_idclass[id]
393
+ if classname
394
+ @objects[classname].delete(id)
395
+ @objects_idclass.delete(id)
396
+ end
397
+ end
398
+
399
+ #Returns the first object found from the given arguments. Also automatically limits the results to 1.
400
+ def get_by(classname, args = {})
401
+ classname = classname.to_sym
402
+ self.requireclass(classname)
403
+ classob = @args[:module].const_get(classname)
404
+
405
+ raise "list-function has not been implemented for '#{classname}'." if !classob.respond_to?(:list)
406
+
407
+ args["limit"] = 1
408
+ self.list(classname, args) do |obj|
409
+ return obj
410
+ end
411
+
412
+ return false
413
+ end
414
+
415
+ def get_try(obj, col_name, obj_name = nil)
416
+ if !obj_name
417
+ if match = col_name.to_s.match(/^(.+)_id$/)
418
+ obj_name = Php4r.ucwords(match[1]).to_sym
419
+ else
420
+ raise "Could not figure out objectname for: #{col_name}."
421
+ end
422
+ end
423
+
424
+ id_data = obj[col_name].to_i
425
+ return false if id_data.to_i <= 0
426
+
427
+ begin
428
+ return self.get(obj_name, id_data)
429
+ rescue Errno::ENOENT
430
+ return false
431
+ end
432
+ end
433
+
434
+ #Returns an array-list of objects. If given a block the block will be called for each element and memory will be spared if running weak-link-mode.
435
+ #===Examples
436
+ # ob.list(:User) do |user|
437
+ # print "Username: #{user.name}\n"
438
+ # end
439
+ def list(classname, args = {}, &block)
440
+ args = {} if args == nil
441
+ classname = classname.to_sym
442
+ self.requireclass(classname)
443
+ classob = @args[:module].const_get(classname)
444
+
445
+ raise "list-function has not been implemented for '#{classname}'." if !classob.respond_to?("list")
446
+
447
+ if @args[:datarow] or @args[:custom]
448
+ ret = classob.list(Knj::Hash_methods.new(:args => args, :ob => self, :db => @args[:db]), &block)
449
+ else
450
+ realargs = [args]
451
+ realargs = realargs | @args[:extra_args] if @args[:extra_args]
452
+ ret = classob.list(*realargs, &block)
453
+ end
454
+
455
+ #If 'ret' is an array and a block is given then the list-method didnt return blocks. We emulate it instead with the following code.
456
+ if block and ret.is_a?(Array)
457
+ ret.each do |obj|
458
+ block.call(obj)
459
+ end
460
+ return nil
461
+ elsif block and ret != nil
462
+ raise "Return should return nil because of block but didnt. It wasnt an array either..."
463
+ elsif block
464
+ return nil
465
+ else
466
+ return ret
467
+ end
468
+ end
469
+
470
+ #Yields every object that is missing certain required objects (based on 'has_many' required-argument).
471
+ def list_invalid_required(args, &block)
472
+ enum = Enumerator.new do |yielder|
473
+ classname = args[:class]
474
+ classob = @args[:module].const_get(classname)
475
+ required_data = classob.required_data
476
+
477
+ if required_data and !required_data.empty?
478
+ required_data.each do |req_data|
479
+ self.list(args[:class], :cloned_ubuf => true) do |obj|
480
+ puts "Checking #{obj.classname}(#{obj.id}) for required #{req_data[:class]}." if args[:debug]
481
+ id = obj[req_data[:col]]
482
+
483
+ begin
484
+ raise Errno::ENOENT if !id
485
+ obj_req = self.get(req_data[:class], id)
486
+ rescue Errno::ENOENT
487
+ yielder << {:obj => obj, :type => :required, :id => id, :data => req_data}
488
+ end
489
+ end
490
+ end
491
+ end
492
+ end
493
+
494
+ return Knj.handle_return(:enum => enum, :block => block)
495
+ end
496
+
497
+ #Returns select-options-HTML for inserting into a HTML-select-element.
498
+ def list_opts(classname, args = {})
499
+ Knj::ArrayExt.hash_sym(args)
500
+ classname = classname.to_sym
501
+
502
+ if args[:list_args].is_a?(Hash)
503
+ list_args = args[:list_args]
504
+ else
505
+ list_args = {}
506
+ end
507
+
508
+ html = ""
509
+
510
+ if args[:addnew] or args[:add]
511
+ html << "<option"
512
+ html << " selected=\"selected\"" if !args[:selected]
513
+ html << " value=\"\">#{_("Add new")}</option>"
514
+ elsif args[:none]
515
+ html << "<option"
516
+ html << " selected=\"selected\"" if !args[:selected]
517
+ html << " value=\"\">#{_("None")}</option>"
518
+ end
519
+
520
+ self.list(classname, args[:list_args]) do |object|
521
+ html << "<option value=\"#{object.id.html}\""
522
+
523
+ selected = false
524
+ if args[:selected].is_a?(Array) and args[:selected].index(object) != nil
525
+ selected = true
526
+ elsif args[:selected] and args[:selected].respond_to?("is_knj?") and args[:selected].id.to_s == object.id.to_s
527
+ selected = true
528
+ end
529
+
530
+ html << " selected=\"selected\"" if selected
531
+
532
+ obj_methods = object.class.instance_methods(false)
533
+
534
+ begin
535
+ if obj_methods.index("name") != nil or obj_methods.index(:name) != nil
536
+ objhtml = object.name.html
537
+ elsif obj_methods.index("title") != nil or obj_methods.index(:title) != nil
538
+ objhtml = object.title.html
539
+ elsif object.respond_to?(:data)
540
+ obj_data = object.data
541
+
542
+ if obj_data.key?(:name)
543
+ objhtml = obj_data[:name]
544
+ elsif obj_data.key?(:title)
545
+ objhtml = obj_data[:title]
546
+ end
547
+ else
548
+ objhtml = ""
549
+ end
550
+
551
+ raise "Could not figure out which name-method to call?" if !objhtml
552
+ html << ">#{objhtml}</option>"
553
+ rescue => e
554
+ html << ">[#{object.class.name}: #{e.message}]</option>"
555
+ end
556
+ end
557
+
558
+ return html
559
+ end
560
+
561
+ #Returns a hash which can be used to generate HTML-select-elements.
562
+ def list_optshash(classname, args = {})
563
+ Knj::ArrayExt.hash_sym(args)
564
+ classname = classname.to_sym
565
+
566
+ if args[:list_args].is_a?(Hash)
567
+ list_args = args[:list_args]
568
+ else
569
+ list_args = {}
570
+ end
571
+
572
+ if RUBY_VERSION[0..2] == 1.8 and Php4r.class_exists("Dictionary")
573
+ list = Dictionary.new
574
+ else
575
+ list = {}
576
+ end
577
+
578
+ if args[:addnew] or args[:add]
579
+ list["0"] = _("Add new")
580
+ elsif args[:choose]
581
+ list["0"] = _("Choose") + ":"
582
+ elsif args[:all]
583
+ list["0"] = _("All")
584
+ elsif args[:none]
585
+ list["0"] = _("None")
586
+ end
587
+
588
+ self.list(classname, args[:list_args]) do |object|
589
+ if object.respond_to?(:name)
590
+ list[object.id] = object.name
591
+ elsif object.respond_to?(:title)
592
+ list[object.id] = object.title
593
+ else
594
+ raise "Object of class '#{object.class.name}' doesnt support 'name' or 'title."
595
+ end
596
+ end
597
+
598
+ return list
599
+ end
600
+
601
+ #Returns a list of a specific object by running specific SQL against the database.
602
+ def list_bysql(classname, sql, args = nil, &block)
603
+ classname = classname.to_sym
604
+ ret = [] if !block
605
+ qargs = nil
606
+
607
+ if args
608
+ args.each do |key, val|
609
+ case key
610
+ when :cloned_ubuf
611
+ qargs = {:cloned_ubuf => true}
612
+ else
613
+ raise "Invalid key: '#{key}'."
614
+ end
615
+ end
616
+ end
617
+
618
+ if @args[:array_enum]
619
+ enum = Enumerator.new do |yielder|
620
+ @args[:db].q(sql, qargs) do |d_obs|
621
+ yielder << self.get(classname, d_obs)
622
+ end
623
+ end
624
+
625
+ if block
626
+ enum.each(&block)
627
+ return nil
628
+ else
629
+ return Array_enumerator.new(enum)
630
+ end
631
+ else
632
+ @args[:db].q(sql, qargs) do |d_obs|
633
+ if block
634
+ block.call(self.get(classname, d_obs))
635
+ else
636
+ ret << self.get(classname, d_obs)
637
+ end
638
+ end
639
+
640
+ if !block
641
+ return ret
642
+ else
643
+ return nil
644
+ end
645
+ end
646
+ end
647
+
648
+ #Add a new object to the database and to the cache.
649
+ #===Examples
650
+ # obj = ob.add(:User, {:username => "User 1"})
651
+ def add(classname, data = {}, args = nil)
652
+ raise "data-variable was not a hash: '#{data.class.name}'." if !data.is_a?(Hash)
653
+ classname = classname.to_sym
654
+ self.requireclass(classname)
655
+
656
+ if @args[:datarow]
657
+ classobj = @args[:module].const_get(classname)
658
+
659
+ #Run the class 'add'-method to check various data.
660
+ classobj.add(Knj::Hash_methods.new(:ob => self, :db => @args[:db], :data => data)) if classobj.respond_to?(:add)
661
+
662
+ #Check if various required data is given. If not then raise an error telling about it.
663
+ required_data = classobj.required_data
664
+ required_data.each do |req_data|
665
+ raise "No '#{req_data[:class]}' given by the data '#{req_data[:col]}'." if !data.key?(req_data[:col])
666
+ raise "The '#{req_data[:class]}' by ID '#{data[req_data[:col]]}' could not be found with the data '#{req_data[:col]}'." if !self.exists?(req_data[:class], data[req_data[:col]])
667
+ end
668
+
669
+ #If 'skip_ret' is given, then the ID wont be looked up and the object wont be spawned. Be aware the connected events wont be executed either. In return it will go a lot faster.
670
+ if args and args[:skip_ret] and !@ids_cache_should.key?(classname)
671
+ ins_args = nil
672
+ else
673
+ ins_args = {:return_id => true}
674
+ end
675
+
676
+ #Insert and (maybe?) get ID.
677
+ ins_id = @args[:db].insert(classobj.table, data, ins_args).to_i
678
+
679
+ #Add ID to ID-cache if ID-cache is active for that classname.
680
+ @ids_cache[classname][ins_id] = true if ins_id != 0 and @ids_cache_should.key?(classname)
681
+
682
+ #Skip the rest if we are told not to return result.
683
+ return nil if args and args[:skip_ret]
684
+
685
+ #Spawn the object.
686
+ retob = self.get(classname, ins_id, {:skip_reload => true})
687
+ elsif @args[:custom]
688
+ classobj = @args[:module].const_get(classname)
689
+ retob = classobj.add(Knj::Hash_methods.new(
690
+ :ob => self,
691
+ :data => data
692
+ ))
693
+ else
694
+ args = [data]
695
+ args = args | @args[:extra_args] if @args[:extra_args]
696
+ retob = @args[:module].const_get(classname).add(*args)
697
+ end
698
+
699
+ self.call("object" => retob, "signal" => "add")
700
+ retob.send(:add_after, {}) if retob.respond_to?(:add_after)
701
+
702
+ return retob
703
+ end
704
+
705
+ #Adds several objects to the database at once. This is faster than adding every single object by itself, since this will do multi-inserts if supported by the database.
706
+ #===Examples
707
+ # ob.adds(:User, [{:username => "User 1"}, {:username => "User 2"})
708
+ def adds(classname, datas)
709
+ if !@args[:datarow]
710
+ datas.each do |data|
711
+ @args[:module].const_get(classname).add(*args)
712
+ self.call("object" => retob, "signal" => "add")
713
+ end
714
+ else
715
+ if @args[:module].const_get(classname).respond_to?(:add)
716
+ datas.each do |data|
717
+ @args[:module].const_get(classname).add(Knj::Hash_methods.new(
718
+ :ob => self,
719
+ :db => self.db,
720
+ :data => data
721
+ ))
722
+ end
723
+ end
724
+
725
+ db.insert_multi(classname, datas)
726
+ end
727
+
728
+ self.cache_ids(classname)
729
+ end
730
+
731
+ #Calls a static method on a class. Passes the d-variable which contains the Objects-object, database-reference and more...
732
+ def static(class_name, method_name, *args, &block)
733
+ raise "Only available with datarow enabled." if !@args[:datarow] and !@args[:custom]
734
+ class_name = class_name
735
+ method_name = method_name
736
+
737
+ self.requireclass(class_name)
738
+ class_obj = @args[:module].const_get(class_name)
739
+
740
+ #Sometimes this raises the exception but actually responds to the class? Therefore commented out. - knj
741
+ #raise "The class '#{class_obj.name}' has no such method: '#{method_name}' (#{class_obj.methods.sort.join(", ")})." if !class_obj.respond_to?(method_name)
742
+
743
+ pass_args = []
744
+
745
+ if @args[:datarow]
746
+ pass_args << Knj::Hash_methods.new(:ob => self, :db => self.db)
747
+ else
748
+ pass_args << Knj::Hash_methods.new(:ob => self)
749
+ end
750
+
751
+ args.each do |arg|
752
+ pass_args << arg
753
+ end
754
+
755
+ class_obj.send(method_name, *pass_args, &block)
756
+ end
757
+
758
+ #Unset object. Do this if you are sure, that there are no more references left. This will be done automatically when deleting it.
759
+ def unset(object)
760
+ if object.is_a?(Array)
761
+ object.each do |obj|
762
+ unset(obj)
763
+ end
764
+ return nil
765
+ end
766
+
767
+ classname = object.class.name
768
+
769
+ if @args[:module]
770
+ classname = classname.gsub(@args[:module].name + "::", "")
771
+ end
772
+
773
+ classname = classname.to_sym
774
+ @objects[classname].delete(object.id.to_i)
775
+ end
776
+
777
+ def unset_class(classname)
778
+ if classname.is_a?(Array)
779
+ classname.each do |classn|
780
+ self.unset_class(classn)
781
+ end
782
+
783
+ return false
784
+ end
785
+
786
+ classname = classname.to_sym
787
+
788
+ return false if !@objects.key?(classname)
789
+ @objects.delete(classname)
790
+ end
791
+
792
+ #Delete an object. Both from the database and from the cache.
793
+ #===Examples
794
+ # user = ob.get(:User, 1)
795
+ # ob.delete(user)
796
+ def delete(object, args = nil)
797
+ #Return false if the object has already been deleted.
798
+ return false if object.deleted?
799
+ classname = object.class.classname.to_sym
800
+
801
+ self.call("object" => object, "signal" => "delete_before")
802
+ self.unset(object)
803
+ obj_id = object.id
804
+ object.delete if object.respond_to?(:delete)
805
+
806
+ if @args[:datarow]
807
+ #If autodelete is set by 'has_many'-method, go through it and delete the various objects first.
808
+ if autodelete_data = object.class.autodelete_data
809
+ autodelete_data.each do |adel_data|
810
+ self.list(adel_data[:classname], {adel_data[:colname].to_s => object.id}) do |obj_del|
811
+ self.delete(obj_del, args)
812
+ end
813
+ end
814
+ end
815
+
816
+ #If depend is set by 'has_many'-method, check if any objects exists and raise error if so.
817
+ if dep_datas = object.class.depending_data
818
+ dep_datas.each do |dep_data|
819
+ if obj = self.get_by(dep_data[:classname], {dep_data[:colname].to_s => object.id})
820
+ raise "Cannot delete <#{object.class.name}:#{object.id}> because <#{obj.class.name}:#{obj.id}> depends on it."
821
+ end
822
+ end
823
+ end
824
+
825
+ #If autozero is set by 'has_many'-method, check if any objects exists and set the ID to zero.
826
+ if autozero_datas = object.class.autozero_data
827
+ autozero_datas.each do |zero_data|
828
+ self.list(zero_data[:classname], {zero_data[:colname].to_s => object.id}) do |obj_zero|
829
+ obj_zero[zero_data[:colname].to_sym] = 0
830
+ end
831
+ end
832
+ end
833
+
834
+ #Delete any translations that has been set on the object by 'has_translation'-method.
835
+ if object.class.translations
836
+ begin
837
+ _hb.trans_del(object)
838
+ rescue NameError
839
+ _kas.trans_del(object)
840
+ end
841
+ end
842
+
843
+
844
+ #If a buffer is given in arguments, then use that to delete the object.
845
+ if args and buffer = args[:db_buffer]
846
+ buffer.delete(object.table, {:id => obj_id})
847
+ else
848
+ @args[:db].delete(object.table, {:id => obj_id})
849
+ end
850
+ end
851
+
852
+ @ids_cache[classname].delete(obj_id.to_i) if @ids_cache_should.key?(classname)
853
+ self.call("object" => object, "signal" => "delete")
854
+ object.destroy
855
+ return nil
856
+ end
857
+
858
+ #Deletes several objects as one. If running datarow-mode it checks all objects before it starts to actually delete them. Its faster than deleting every single object by itself...
859
+ def deletes(objs)
860
+ if !@args[:datarow]
861
+ objs.each do |obj|
862
+ self.delete(obj)
863
+ end
864
+ else
865
+ tables = {}
866
+
867
+ begin
868
+ objs.each do |obj|
869
+ next if obj.deleted?
870
+ tablen = obj.table
871
+
872
+ if !tables.key?(tablen)
873
+ tables[tablen] = []
874
+ end
875
+
876
+ tables[tablen] << obj.id
877
+ obj.delete if obj.respond_to?(:delete)
878
+
879
+ #Remove from ID-cache.
880
+ classname = obj.class.classname.to_sym
881
+ @ids_cache[classname].delete(obj.id.to_i) if @ids_cache_should.key?(classname)
882
+
883
+ #Unset any data on the object, so it seems deleted.
884
+ obj.destroy
885
+ end
886
+ ensure
887
+ #An exception may occur, and we should make sure, that objects that has gotten 'delete' called also are deleted from their tables.
888
+ tables.each do |table, ids|
889
+ ids.each_slice(1000) do |ids_slice|
890
+ @args[:db].delete(table, {:id => ids_slice})
891
+ end
892
+ end
893
+ end
894
+ end
895
+ end
896
+
897
+ #Deletes all objects with the given IDs 500 at a time to prevent memory exhaustion or timeout.
898
+ #===Examples
899
+ # ob.delete_ids(:class => :Person, :ids => [1, 3, 5, 6, 7, 8, 9])
900
+ def delete_ids(args)
901
+ while !args[:ids].empty? and ids = args[:ids].shift(500)
902
+ objs = self.list(args[:class], "id" => ids)
903
+ self.deletes(objs)
904
+ end
905
+
906
+ return nil
907
+ end
908
+
909
+ #Try to clean up objects by unsetting everything, start the garbagecollector, get all the remaining objects via ObjectSpace and set them again. Some (if not all) should be cleaned up and our cache should still be safe... dirty but works.
910
+ def clean(classn)
911
+ if classn.is_a?(Array)
912
+ classn.each do |realclassn|
913
+ self.clean(realclassn)
914
+ end
915
+
916
+ return nil
917
+ end
918
+
919
+ if @args[:cache] == :weak
920
+ @objects[classn].clean
921
+ elsif @args[:cache] == :none
922
+ return false
923
+ else
924
+ return false if !@objects.key?(classn)
925
+ @objects[classn] = {}
926
+ GC.start
927
+
928
+ @objects.keys.each do |classn|
929
+ data = @objects[classn]
930
+ classobj = @args[:module].const_get(classn)
931
+ ObjectSpace.each_object(classobj) do |obj|
932
+ begin
933
+ data[obj.id.to_i] = obj
934
+ rescue => e
935
+ if e.message == "No data on object."
936
+ #Object has been unset - skip it.
937
+ next
938
+ end
939
+
940
+ raise e
941
+ end
942
+ end
943
+ end
944
+ end
945
+ end
946
+
947
+ #Erases the whole cache and regenerates it from ObjectSpace if not running weak-link-caching. If running weaklink-caching then it will only removes the dead links.
948
+ def clean_all
949
+ self.clean(@objects.keys)
950
+ end
951
+
952
+ def classes_loaded
953
+ return @objects.keys
954
+ end
955
+
956
+ #This method helps build SQL from Objects-instances list-method. It should not be called directly but only through Objects.list.
957
+ def sqlhelper(list_args, args_def)
958
+ args = args_def
959
+
960
+ if args[:db]
961
+ db = args[:db]
962
+ else
963
+ db = @args[:db]
964
+ end
965
+
966
+ if args[:table]
967
+ table_def = "`#{db.escape_table(args[:table])}`."
968
+ else
969
+ table_def = ""
970
+ end
971
+
972
+ sql_joins = ""
973
+ sql_where = ""
974
+ sql_order = ""
975
+ sql_limit = ""
976
+ sql_groupby = ""
977
+
978
+ do_joins = {}
979
+
980
+ limit_from = nil
981
+ limit_to = nil
982
+
983
+ if list_args.key?("orderby")
984
+ orders = []
985
+ orderstr = list_args["orderby"]
986
+ list_args["orderby"] = [list_args["orderby"]] if list_args["orderby"].is_a?(Hash)
987
+
988
+ if list_args["orderby"].is_a?(String)
989
+ found = false
990
+ found = true if args[:cols].key?(orderstr)
991
+
992
+ if found
993
+ sql_order << " ORDER BY "
994
+ ordermode = " ASC"
995
+ if list_args.key?("ordermode")
996
+ if list_args["ordermode"] == "desc"
997
+ ordermode = " DESC"
998
+ elsif list_args["ordermode"] == "asc"
999
+ ordermode = " ASC"
1000
+ raise "Unknown ordermode: #{list_args["ordermode"]}"
1001
+ end
1002
+
1003
+ list_args.delete("ordermode")
1004
+ end
1005
+
1006
+ sql_order << "#{table_def}`#{db.escape_column(list_args["orderby"])}`#{ordermode}"
1007
+ list_args.delete("orderby")
1008
+ end
1009
+ elsif list_args["orderby"].is_a?(Array)
1010
+ sql_order << " ORDER BY "
1011
+
1012
+ list_args["orderby"].each do |val|
1013
+ ordermode = nil
1014
+ orderstr = nil
1015
+ found = false
1016
+
1017
+ if val.is_a?(Array)
1018
+ if val[1] == "asc"
1019
+ ordermode = " ASC"
1020
+ elsif val[1] == "desc"
1021
+ ordermode = " DESC"
1022
+ end
1023
+
1024
+ if val[0].is_a?(Array)
1025
+ if args[:joined_tables]
1026
+ args[:joined_tables].each do |table_name, table_data|
1027
+ next if table_name.to_s != val[0][0].to_s
1028
+ do_joins[table_name] = true
1029
+ orders << "`#{db.escape_table(table_name)}`.`#{db.escape_column(val[0][1])}`#{ordermode}"
1030
+ found = true
1031
+ break
1032
+ end
1033
+ end
1034
+
1035
+ raise "Could not find joined table for ordering: '#{val[0][0]}'." if !found
1036
+ else
1037
+ orderstr = val[0]
1038
+ end
1039
+ elsif val.is_a?(String)
1040
+ orderstr = val
1041
+ ordermode = " ASC"
1042
+ elsif val.is_a?(Hash) and val[:type] == :sql
1043
+ orders << val[:sql]
1044
+ found = true
1045
+ elsif val.is_a?(Hash) and val[:type] == :case
1046
+ caseorder = " CASE"
1047
+
1048
+ val[:case].each do |key, caseval|
1049
+ col = key.first
1050
+ isval = key.last
1051
+ col_str = nil
1052
+
1053
+ if col.is_a?(Array)
1054
+ raise "No joined tables for '#{args[:table]}'." if !args[:joined_tables]
1055
+
1056
+ found = false
1057
+ args[:joined_tables].each do |table_name, table_data|
1058
+ if table_name == col.first
1059
+ do_joins[table_name] = true
1060
+ col_str = "`#{db.escape_table(table_name)}`.`#{db.escape_column(col.last)}`"
1061
+ found = true
1062
+ break
1063
+ end
1064
+ end
1065
+
1066
+ raise "No such joined table on '#{args[:table]}': '#{col.first}' (#{col.first.class.name}) with the following joined table:\n#{Php4r.print_r(args[:joined_tables], true)}" if !found
1067
+ elsif col.is_a?(String) or col.is_a?(Symbol)
1068
+ col_str = "#{table_def}`#{col}`"
1069
+ found = true
1070
+ else
1071
+ raise "Unknown type for case-ordering: '#{col.class.name}'."
1072
+ end
1073
+
1074
+ raise "'colstr' was not set." if !col_str
1075
+ caseorder << " WHEN #{col_str} = '#{db.esc(isval)}' THEN '#{db.esc(caseval)}'"
1076
+ end
1077
+
1078
+ if val[:else]
1079
+ caseorder << " ELSE '#{db.esc(val[:else])}'"
1080
+ end
1081
+
1082
+ caseorder << " END"
1083
+ orders << caseorder
1084
+ elsif val.is_a?(Hash)
1085
+ raise "No joined tables." if !args.key?(:joined_tables)
1086
+
1087
+ if val[:mode] == "asc"
1088
+ ordermode = " ASC"
1089
+ elsif val[:mode] == "desc"
1090
+ ordermode = " DESC"
1091
+ end
1092
+
1093
+ if args[:joined_tables]
1094
+ args[:joined_tables].each do |table_name, table_data|
1095
+ if table_data[:parent_table]
1096
+ table_name_real = table_name
1097
+ elsif table_data[:datarow]
1098
+ table_name_real = self.datarow_from_datarow_argument(table_data[:datarow]).classname
1099
+ else
1100
+ table_name_real = @args[:module].const_get(table_name).classname
1101
+ end
1102
+
1103
+ if table_name.to_s == val[:table].to_s
1104
+ do_joins[table_name] = true
1105
+
1106
+ if val[:sql]
1107
+ orders << val[:sql]
1108
+ elsif val[:col]
1109
+ orders << "`#{db.escape_table(table_name_real)}`.`#{db.escape_column(val[:col])}`#{ordermode}"
1110
+ else
1111
+ raise "Couldnt figure out how to order based on keys: '#{val.keys.sort}'."
1112
+ end
1113
+
1114
+ found = true
1115
+ break
1116
+ end
1117
+ end
1118
+ end
1119
+ else
1120
+ raise "Unknown object: #{val.class.name}"
1121
+ end
1122
+
1123
+ found = true if args[:cols].key?(orderstr)
1124
+
1125
+ if !found
1126
+ raise "Column not found for ordering: #{orderstr}."
1127
+ end
1128
+
1129
+ orders << "#{table_def}`#{db.escape_column(orderstr)}`#{ordermode}" if orderstr
1130
+ end
1131
+
1132
+ sql_order << orders.join(", ")
1133
+ list_args.delete("orderby")
1134
+ else
1135
+ raise "Unknown orderby object: #{list_args["orderby"].class.name}."
1136
+ end
1137
+ end
1138
+
1139
+ list_args.each do |realkey, val|
1140
+ found = false
1141
+
1142
+ if realkey.is_a?(Array)
1143
+ if !args[:joins_skip]
1144
+ datarow_obj = self.datarow_obj_from_args(args_def, list_args, realkey[0])
1145
+ args = datarow_obj.columns_sqlhelper_args
1146
+ raise "Couldnt get arguments from SQLHelper." if !args
1147
+ else
1148
+ datarow_obj = @args[:module].const_get(realkey[0])
1149
+ args = args_def
1150
+ end
1151
+
1152
+ table_sym = realkey[0].to_sym
1153
+ do_joins[table_sym] = true
1154
+ list_table_name_real = table_sym
1155
+ table = "`#{db.escape_table(list_table_name_real)}`."
1156
+ key = realkey[1]
1157
+ else
1158
+ table = table_def
1159
+ args = args_def
1160
+ key = realkey
1161
+ end
1162
+
1163
+ if args.key?(:cols_bools) and args[:cols_bools].index(key) != nil
1164
+ val_s = val.to_s
1165
+
1166
+ if val_s == "1" or val_s == "true"
1167
+ realval = "1"
1168
+ elsif val_s == "0" or val_s == "false"
1169
+ realval = "0"
1170
+ else
1171
+ raise "Could not make real value out of class: #{val.class.name} => #{val}."
1172
+ end
1173
+
1174
+ sql_where << " AND #{table}`#{db.escape_column(key)}` = '#{db.esc(realval)}'"
1175
+ found = true
1176
+ elsif args[:cols].key?(key)
1177
+ if val.is_a?(Array)
1178
+ if val.empty? and db.opts[:type].to_s == "mysql"
1179
+ sql_where << " AND false"
1180
+ else
1181
+ escape_sql = Knj::ArrayExt.join(
1182
+ :arr => val,
1183
+ :callback => proc{|value|
1184
+ db.escape(value)
1185
+ },
1186
+ :sep => ",",
1187
+ :surr => "'"
1188
+ )
1189
+ sql_where << " AND #{table}`#{db.escape_column(key)}` IN (#{escape_sql})"
1190
+ end
1191
+ elsif val.is_a?(Hash) and val[:type].to_sym == :col
1192
+ raise "No table was given for join: '#{val}', key: '#{key}' on table #{table}." if !val.key?(:table)
1193
+ do_joins[val[:table].to_sym] = true
1194
+ sql_where << " AND #{table}`#{db.escape_column(key)}` = `#{db.escape_table(val[:table])}`.`#{db.escape_column(val[:name])}`"
1195
+ elsif val.is_a?(Hash) and val[:type] == :sqlval and val[:val] == :null
1196
+ sql_where << " AND #{table}`#{db.escape_column(key)}` IS NULL"
1197
+ elsif val.is_a?(Proc)
1198
+ call_args = Knj::Hash_methods.new(:ob => self, :db => db)
1199
+ sql_where << " AND #{table}`#{db.escape_column(key)}` = '#{db.esc(val.call(call_args))}'"
1200
+ else
1201
+ sql_where << " AND #{table}`#{db.escape_column(key)}` = '#{db.esc(val)}'"
1202
+ end
1203
+
1204
+ found = true
1205
+ elsif key.to_s == "limit_from"
1206
+ limit_from = val.to_i
1207
+ found = true
1208
+ elsif key.to_s == "limit_to"
1209
+ limit_to = val.to_i
1210
+ found = true
1211
+ elsif key.to_s == "limit"
1212
+ limit_from = 0
1213
+ limit_to = val.to_i
1214
+ found = true
1215
+ elsif args.key?(:cols_dbrows) and args[:cols_dbrows].index("#{key.to_s}_id") != nil
1216
+ if val == false
1217
+ sql_where << " AND #{table}`#{db.escape_column(key.to_s + "_id")}` = '0'"
1218
+ elsif val.is_a?(Array)
1219
+ if val.empty?
1220
+ sql_where << " AND false"
1221
+ else
1222
+ sql_where << " AND #{table}`#{db.escape_column("#{key}_id")}` IN (#{Knj::ArrayExt.join(:arr => val, :sep => ",", :surr => "'", :callback => proc{|obj| obj.id.sql})})"
1223
+ end
1224
+ else
1225
+ sql_where << " AND #{table}`#{db.escape_column(key.to_s + "_id")}` = '#{db.esc(val.id)}'"
1226
+ end
1227
+
1228
+ found = true
1229
+ elsif match = key.match(/^([A-z_\d]+)_(search|has)$/) and args[:cols].key?(match[1]) != nil
1230
+ if match[2] == "search"
1231
+ Knj::Strings.searchstring(val).each do |str|
1232
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` LIKE '%#{db.esc(str)}%'"
1233
+ end
1234
+ elsif match[2] == "has"
1235
+ if val
1236
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` != ''"
1237
+ else
1238
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` = ''"
1239
+ end
1240
+ end
1241
+
1242
+ found = true
1243
+ elsif match = key.match(/^([A-z_\d]+)_(not|lower)$/) and args[:cols].key?(match[1])
1244
+ if match[2] == "not"
1245
+ if val.is_a?(Array)
1246
+ if val.empty?
1247
+ #ignore.
1248
+ else
1249
+ escape_sql = Knj::ArrayExt.join(
1250
+ :arr => val,
1251
+ :callback => proc{|value|
1252
+ db.escape(value)
1253
+ },
1254
+ :sep => ",",
1255
+ :surr => "'"
1256
+ )
1257
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` NOT IN (#{escape_sql})"
1258
+ end
1259
+ else
1260
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` != '#{db.esc(val)}'"
1261
+ end
1262
+ elsif match[2] == "lower"
1263
+ sql_where << " AND LOWER(#{table}`#{db.escape_column(match[1])}`) = LOWER('#{db.esc(val)}')"
1264
+ else
1265
+ raise "Unknown mode: '#{match[2]}'."
1266
+ end
1267
+
1268
+ found = true
1269
+ elsif args.key?(:cols_date) and match = key.match(/^(.+)_(day|week|month|year|from|to|below|above)(|_(not))$/) and args[:cols_date].index(match[1]) != nil
1270
+ not_v = match[4]
1271
+ val = Datet.in(val) if val.is_a?(Time)
1272
+
1273
+ if match[2] == "day"
1274
+ if val.is_a?(Array)
1275
+ sql_where << " AND ("
1276
+ first = true
1277
+
1278
+ val.each do |realval|
1279
+ if first
1280
+ first = false
1281
+ else
1282
+ sql_where << " OR "
1283
+ end
1284
+
1285
+ sql_where << "#{db.sqlspecs.strftime("%d %m %Y", "#{table}`#{db.escape_column(match[1])}`")} #{self.not(not_v, "!")}= #{db.sqlspecs.strftime("%d %m %Y", "'#{db.esc(realval.dbstr)}'")}"
1286
+ end
1287
+
1288
+ sql_where << ")"
1289
+ else
1290
+ sql_where << " AND #{db.sqlspecs.strftime("%d %m %Y", "#{table}`#{db.escape_column(match[1])}`")} #{self.not(not_v, "!")}= #{db.sqlspecs.strftime("%d %m %Y", "'#{db.esc(val.dbstr)}'")}"
1291
+ end
1292
+ elsif match[2] == "week"
1293
+ sql_where << " AND #{db.sqlspecs.strftime("%W %Y", "#{table}`#{db.escape_column(match[1])}`")} #{self.not(not_v, "!")}= #{db.sqlspecs.strftime("%W %Y", "'#{db.esc(val.dbstr)}'")}"
1294
+ elsif match[2] == "month"
1295
+ sql_where << " AND #{db.sqlspecs.strftime("%m %Y", "#{table}`#{db.escape_column(match[1])}`")} #{self.not(not_v, "!")}= #{db.sqlspecs.strftime("%m %Y", "'#{db.esc(val.dbstr)}'")}"
1296
+ elsif match[2] == "year"
1297
+ sql_where << " AND #{db.sqlspecs.strftime("%Y", "#{table}`#{db.escape_column(match[1])}`")} #{self.not(not_v, "!")}= #{db.sqlspecs.strftime("%Y", "'#{db.esc(val.dbstr)}'")}"
1298
+ elsif match[2] == "from" or match[2] == "above"
1299
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` >= '#{db.esc(val.dbstr)}'"
1300
+ elsif match[2] == "to" or match[2] == "below"
1301
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` <= '#{db.esc(val.dbstr)}'"
1302
+ else
1303
+ raise "Unknown date-key: #{match[2]}."
1304
+ end
1305
+
1306
+ found = true
1307
+ elsif args.key?(:cols_num) and match = key.match(/^(.+)_(from|to|above|below|numeric)$/) and args[:cols_num].index(match[1]) != nil
1308
+ if match[2] == "from"
1309
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` >= '#{db.esc(val)}'"
1310
+ elsif match[2] == "to"
1311
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` <= '#{db.esc(val)}'"
1312
+ elsif match[2] == "above"
1313
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` > '#{db.esc(val)}'"
1314
+ elsif match[2] == "below"
1315
+ sql_where << " AND #{table}`#{db.escape_column(match[1])}` < '#{db.esc(val)}'"
1316
+ else
1317
+ raise "Unknown method of treating cols-num-argument: #{match[2]}."
1318
+ end
1319
+
1320
+ found = true
1321
+ elsif match = key.match(/^(.+)_lookup$/) and args[:cols].key?("#{match[1]}_id") and args[:cols].key?("#{match[1]}_class")
1322
+ sql_where << " AND #{table}`#{db.escape_column("#{match[1]}_class")}` = '#{db.esc(val.table)}'"
1323
+ sql_where << " AND #{table}`#{db.escape_column("#{match[1]}_id")}` = '#{db.esc(val.id)}'"
1324
+ found = true
1325
+ elsif realkey == "groupby"
1326
+ found = true
1327
+
1328
+ if val.is_a?(Array)
1329
+ val.each do |col_name|
1330
+ raise "Column '#{val}' not found on table '#{table}'." if !args[:cols].key?(col_name)
1331
+ sql_groupby << ", " if sql_groupby.length > 0
1332
+ sql_groupby << "#{table}`#{db.escape_column(col_name)}`"
1333
+ end
1334
+ elsif val.is_a?(String)
1335
+ sql_groupby << ", " if sql_groupby.length > 0
1336
+ sql_groupby << "#{table}`#{db.escape_column(val)}`"
1337
+ else
1338
+ raise "Unknown class given for 'groupby': '#{val.class.name}'."
1339
+ end
1340
+ end
1341
+
1342
+ list_args.delete(realkey) if found
1343
+ end
1344
+
1345
+ args = args_def
1346
+
1347
+ if !args[:joins_skip]
1348
+ raise "No joins defined on '#{args[:table]}' for: '#{args[:table]}'." if !do_joins.empty? and !args[:joined_tables]
1349
+
1350
+ do_joins.each do |table_name, temp_val|
1351
+ raise "No join defined on table '#{args[:table]}' for table '#{table_name}'." if !args[:joined_tables].key?(table_name)
1352
+ table_data = args[:joined_tables][table_name]
1353
+
1354
+ if table_data.key?(:parent_table)
1355
+ join_table_name_real = table_name
1356
+ sql_joins << " LEFT JOIN `#{table_data[:parent_table]}` AS `#{table_name}` ON 1=1"
1357
+ else
1358
+ const = @args[:module].const_get(table_name)
1359
+ join_table_name_real = const.classname
1360
+ sql_joins << " LEFT JOIN `#{const.table}` AS `#{const.classname}` ON 1=1"
1361
+ end
1362
+
1363
+ if table_data[:ob]
1364
+ ob = table_data[:ob]
1365
+ else
1366
+ ob = self
1367
+ end
1368
+
1369
+ class_name = args[:table].to_sym
1370
+
1371
+ if table_data[:datarow]
1372
+ datarow = self.datarow_from_datarow_argument(table_data[:datarow])
1373
+ else
1374
+ self.requireclass(class_name) if @objects.key?(class_name)
1375
+ datarow = @args[:module].const_get(class_name)
1376
+ end
1377
+
1378
+ if !datarow.columns_sqlhelper_args
1379
+ ob.requireclass(datarow.table.to_sym)
1380
+ raise "No SQL-helper-args on class '#{datarow.table}' ???" if !datarow.columns_sqlhelper_args
1381
+ end
1382
+
1383
+ newargs = datarow.columns_sqlhelper_args.clone
1384
+ newargs[:table] = join_table_name_real
1385
+ newargs[:joins_skip] = true
1386
+
1387
+ #Clone the where-arguments and run them against another sqlhelper to sub-join.
1388
+ join_args = table_data[:where].clone
1389
+ ret = self.sqlhelper(join_args, newargs)
1390
+ sql_joins << ret[:sql_where]
1391
+
1392
+ #If any of the join-arguments are left, then we should throw an error.
1393
+ join_args.each do |key, val|
1394
+ raise "Invalid key '#{key}' when trying to join table '#{table_name}' on table '#{args_def[:table]}'."
1395
+ end
1396
+ end
1397
+ end
1398
+
1399
+ #If limit arguments has been given then add them.
1400
+ if limit_from and limit_to
1401
+ sql_limit = " LIMIT #{limit_from}, #{limit_to}"
1402
+ end
1403
+
1404
+ sql_groupby = nil if sql_groupby.length <= 0
1405
+
1406
+ return {
1407
+ :sql_joins => sql_joins,
1408
+ :sql_where => sql_where,
1409
+ :sql_limit => sql_limit,
1410
+ :sql_order => sql_order,
1411
+ :sql_groupby => sql_groupby
1412
+ }
1413
+ end
1414
+
1415
+ #Used by sqlhelper-method to look up datarow-classes and automatically load them if they arent loaded already.
1416
+ def datarow_obj_from_args(args, list_args, class_name)
1417
+ class_name = class_name.to_sym
1418
+
1419
+ if !args.key?(:joined_tables)
1420
+ raise "No joined tables on '#{args[:table]}' to find datarow for: '#{class_name}'."
1421
+ end
1422
+
1423
+ args[:joined_tables].each do |table_name, table_data|
1424
+ next if table_name.to_sym != class_name
1425
+ return self.datarow_from_datarow_argument(table_data[:datarow]) if table_data[:datarow]
1426
+
1427
+ self.requireclass(class_name) if @objects.key?(class_name)
1428
+ return @args[:module].const_get(class_name)
1429
+ end
1430
+
1431
+ raise "Could not figure out datarow for: '#{class_name}'."
1432
+ end
1433
+
1434
+ def datarow_from_datarow_argument(datarow_argument)
1435
+ if datarow_argument.is_a?(String)
1436
+ const = Knj::Strings.const_get_full(datarow_argument)
1437
+ else
1438
+ const = datarow_argument
1439
+ end
1440
+
1441
+ self.load_class(datarow_argument.to_s.split("::").last) if !const.initialized? #Make sure the class is initialized.
1442
+
1443
+ return const
1444
+ end
1445
+
1446
+ def not(not_v, val)
1447
+ if not_v == "not" or not_v == "not_"
1448
+ return val
1449
+ end
1450
+
1451
+ return ""
1452
+ end
1453
+ end
1454
+
1455
+ require "#{$knjpath}objects/objects_sqlhelper"