Class: Knj::Objects

Inherits:
Object show all
Defined in:
lib/knj/objects.rb,
lib/knj/objects/objects_sqlhelper.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args) ⇒ Objects

Returns a new instance of Objects.



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/knj/objects.rb', line 4

def initialize(args)
  require "#{$knjpath}arrayext"
  require "#{$knjpath}event_handler"
  require "#{$knjpath}hash_methods"
  
  @callbacks = {}
  @args = Knj::ArrayExt.hash_sym(args)
  @args[:col_id] = :id if !@args[:col_id]
  @args[:class_pre] = "class_" if !@args[:class_pre]
  @args[:module] = Kernel if !@args[:module]
  @args[:cache] = :weak if !@args.key?(:cache)
  @objects = {}
  @data = {}
  @mutex_require = Mutex.new
  
  require "weakref" if @args[:cache] == :weak and !Kernel.const_defined?(:WeakRef)
  
  @events = Knj::Event_handler.new
  @events.add_event(
    :name => :no_html,
    :connections_max => 1
  )
  @events.add_event(
    :name => :no_date,
    :connections_max => 1
  )
  @events.add_event(
    :name => :missing_class,
    :connections_max => 1
  )
  
  raise "No DB given." if !@args[:db] and !@args[:custom]
  raise "No class path given." if !@args[:class_path] and (@args[:require] or !@args.key?(:require))
  
  if args[:require_all]
    require "#{$knjpath}php"
    loads = []
    
    Dir.foreach(@args[:class_path]) do |file|
      next if file == "." or file == ".." or !file.match(/\.rb$/)
      file_parsed = file
      file_parsed.gsub!(@args[:class_pre], "") if @args.key?(:class_pre)
      file_parsed.gsub!(/\.rb$/, "")
      file_parsed = Knj::Php.ucwords(file_parsed)
      
      loads << file_parsed
      self.requireclass(file_parsed, {:load => false})
    end
    
    loads.each do |load_class|
      self.load_class(load_class)
    end
  end
end

Instance Attribute Details

#argsObject (readonly)

Returns the value of attribute args.



2
3
4
# File 'lib/knj/objects.rb', line 2

def args
  @args
end

#dataObject (readonly)

Returns the value of attribute data.



2
3
4
# File 'lib/knj/objects.rb', line 2

def data
  @data
end

#eventsObject (readonly)

Returns the value of attribute events.



2
3
4
# File 'lib/knj/objects.rb', line 2

def events
  @events
end

Instance Method Details

#add(classname, data = {}) ⇒ Object

Add a new object to the database and to the cache.



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/knj/objects.rb', line 439

def add(classname, data = {})
  classname = classname.to_sym
  self.requireclass(classname)
  
  if @args[:datarow]
    classobj = @args[:module].const_get(classname)
    if classobj.respond_to?(:add)
      classobj.add(Knj::Hash_methods.new(
        :ob => self,
        :db => self.db,
        :data => data
      ))
    end
    
    required_data = classobj.required_data
    required_data.each do |req_data|
      if !data.key?(req_data[:col])
        raise "No '#{req_data[:class]}' given by the data '#{req_data[:col]}'."
      end
      
      begin
        obj = self.get(req_data[:class], data[req_data[:col]])
      rescue Knj::Errors::NotFound
        raise "The '#{req_data[:class]}' by ID '#{data[req_data[:col]]}' could not be found with the data '#{req_data[:col]}'."
      end
    end
    
    ins_id = @args[:db].insert(classobj.table, data, {:return_id => true})
    retob = self.get(classname, ins_id)
  elsif @args[:custom]
    classobj = @args[:module].const_get(classname)
    retob = classobj.add(Knj::Hash_methods.new(
      :ob => self,
      :data => data
    ))
  else
    args = [data]
    args = args | @args[:extra_args] if @args[:extra_args]
    retob = @args[:module].const_get(classname).add(*args)
  end
  
  self.call("object" => retob, "signal" => "add")
  if retob.respond_to?(:add_after)
    retob.send(:add_after, {})
  end
  
  return retob
end

#adds(classname, datas) ⇒ Object

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.



489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/knj/objects.rb', line 489

def adds(classname, datas)
  if !@args[:datarow]
    datas.each do |data|
      @args[:module].const_get(classname).add(*args)
      self.call("object" => retob, "signal" => "add")
    end
  else
    if @args[:module].const_get(classname).respond_to?(:add)
      datas.each do |data|
        @args[:module].const_get(classname).add(Knj::Hash_methods.new(
          :ob => self,
          :db => self.db,
          :data => data
        ))
      end
    end
    
    db.insert_multi(classname, datas)
  end
end

#call(args, &block) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/knj/objects.rb', line 97

def call(args, &block)
  classstr = args["object"].class.to_s
  
  if @callbacks.key?(classstr)
    @callbacks[classstr].clone.each do |callback_key, callback|
      docall = false
      
      if callback.key?("signal") and args.key?("signal") and callback["signal"] == args["signal"]
        docall = true
      elsif callback["signals"] and args["signal"] and callback["signals"].index(args["signal"]) != nil
        docall = true
      end
      
      next if !docall
      
      if callback["block"]
        callargs = []
        arity = callback["block"].arity
        if arity <= 0
          #do nothing
        elsif arity == 1
          callargs << args["object"]
        else
          raise "Unknown number of arguments: #{arity}"
        end
        
        callback["block"].call(*callargs)
      elsif callback["callback"]
        Knj::Php.call_user_func(callback["callback"], args)
      else
        raise "No valid callback given."
      end
    end
  end
end

#clean(classn) ⇒ Object

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.



636
637
638
639
640
641
642
643
644
645
646
647
648
# File 'lib/knj/objects.rb', line 636

def clean(classn)
  return false if @args[:cache] == :weak or @args[:cache] == :none
  
  if classn.is_a?(Array)
    classn.each do |realclassn|
      self.clean(realclassn)
    end
  else
    return false if !@objects.key?(classn)
    @objects[classn] = {}
    GC.start
  end
end

#clean_allObject

Erases the whole cache and regenerates is from ObjectSpace if not running weak-link-caching. If running weaklink-caching then only removes the dead links.



651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
# File 'lib/knj/objects.rb', line 651

def clean_all
  return self.clean_all_weak if @args[:cache] == :weak
  return false if @args[:cache] == :none
  
  classnames = []
  @objects.keys.each do |classn|
    classnames << classn
  end
  
  classnames.each do |classn|
    @objects[classn] = {}
  end
  
  GC.start
  self.clean_recover
end

#clean_all_weakObject

Runs through all objects-weaklink-references and removes the weaklinks if the object has been recycled.



669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
# File 'lib/knj/objects.rb', line 669

def clean_all_weak
  @objects.keys.each do |classn|
    @objects[classn].keys.each do |object_id|
      object = @objects[classn][object_id]
      
      begin
        if !object or !object.weakref_alive?
          @objects[classn].delete(object_id)
        end
      rescue WeakRef::RefError
        #This happens if the object has been collected.
        @objects[classn].delete(object_id)
      end
    end
  end
end

#clean_recoverObject

Regenerates cache from ObjectSpace. Its pretty dangerous but can be used in envs where WeakRef is not supported (did someone say Rhodes?).



687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
# File 'lib/knj/objects.rb', line 687

def clean_recover
  return false if @args[:cache] == :weak or @args[:cache] == :none
  return false if RUBY_ENGINE == "jruby" and !JRuby.objectspace
  
  @objects.keys.each do |classn|
    data = @objects[classn]
    classobj = @args[:module].const_get(classn)
    ObjectSpace.each_object(classobj) do |obj|
      begin
        data[obj.id.to_i] = obj
      rescue => e
        if e.message == "No data on object."
          #Object has been unset - skip it.
          next
        end
        
        raise e
      end
    end
  end
end

#connect(args, &block) ⇒ Object



88
89
90
91
92
93
94
95
# File 'lib/knj/objects.rb', line 88

def connect(args, &block)
  raise "No object given." if !args["object"]
  raise "No signals given." if !args.key?("signal") and !args.key?("signals")
  args["block"] = block if block_given?
  @callbacks[args["object"]] = {} if !@callbacks[args["object"]]
  conn_id = @callbacks[args["object"]].length.to_s
  @callbacks[args["object"]][conn_id] = args
end

#count_objectsObject



79
80
81
82
83
84
85
86
# File 'lib/knj/objects.rb', line 79

def count_objects
  count = 0
  @objects.keys.each do |key|
    count += @objects[key].length
  end
  
  return count
end

#datarow_from_datarow_argument(datarow_argument) ⇒ Object



475
476
477
478
479
480
481
# File 'lib/knj/objects/objects_sqlhelper.rb', line 475

def datarow_from_datarow_argument(datarow_argument)
  if datarow_argument.is_a?(String)
    return Knj::Strings.const_get_full(datarow_argument)
  end
  
  return datarow_argument
end

#datarow_obj_from_args(args, list_args, class_name) ⇒ Object

Used by sqlhelper-method to look up datarow-classes and automatically load them if they arent loaded already.



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'lib/knj/objects/objects_sqlhelper.rb', line 457

def datarow_obj_from_args(args, list_args, class_name)
  class_name = class_name.to_sym
  
  if !args.key?(:joined_tables)
    raise "No joined tables on '#{args[:table]}' to find datarow for: '#{class_name}'."
  end
  
  args[:joined_tables].each do |table_name, table_data|
    next if table_name.to_sym != class_name
    return self.datarow_from_datarow_argument(table_data[:datarow]) if table_data[:datarow]
    
    self.requireclass(class_name) if @objects.key?(class_name)
    return @args[:module].const_get(class_name)
  end
  
  raise "Could not figure out datarow for: '#{class_name}'."
end

#dbObject



75
76
77
# File 'lib/knj/objects.rb', line 75

def db
  return @args[:db]
end

#delete(object) ⇒ Object

Delete an object. Both from the database and from the cache.



584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
# File 'lib/knj/objects.rb', line 584

def delete(object)
  self.call("object" => object, "signal" => "delete_before")
  self.unset(object)
  obj_id = object.id
  object.delete if object.respond_to?(:delete)
  
  if @args[:datarow]
    object.class.depending_data.each do |dep_data|
      objs = self.list(dep_data[:classname], {dep_data[:colname].to_s => object.id, "limit" => 1})
      if !objs.empty?
        raise "Cannot delete <#{object.class.name}:#{object.id}> because <#{objs[0].class.name}:#{objs[0].id}> depends on it."
      end
    end
    
    if object.class.translations
      _kas.trans_del(object)
    end
    
    @args[:db].delete(object.table, {:id => obj_id})
  end
  
  self.call("object" => object, "signal" => "delete")
  object.destroy
end

#deletes(objs) ⇒ Object

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…



610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
# File 'lib/knj/objects.rb', line 610

def deletes(objs)
  if !@args[:datarow]
    objs.each do |obj|
      self.delete(obj)
    end
  else
    arr_ids = []
    ids = []
    objs.each do |obj|
      ids << obj.id
      if ids.length >= 1000
        arr_ids << ids
        ids = []
      end
      
      obj.delete if obj.respond_to?(:delete)
    end
    
    arr_ids << ids if ids.length > 0
    arr_ids.each do |ids|
      @args[:db].delete(objs[0].table, {:id => ids})
    end
  end
end

#get(classname, data) ⇒ Object

Gets an object from the ID or the full data-hash in the database.



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/knj/objects.rb', line 185

def get(classname, data)
  classname = classname.to_sym
  
  if data.is_a?(Integer) or data.is_a?(String) or data.is_a?(Fixnum)
    id = data.to_i
  elsif data.is_a?(Hash) and data.key?(@args[:col_id].to_sym)
    id = data[@args[:col_id].to_sym].to_i
  elsif data.is_a?(Hash) and data.key?(@args[:col_id].to_s)
    id = data[@args[:col_id].to_s].to_i
  elsif
    raise Knj::Errors::InvalidData, "Unknown data: '#{data.class.to_s}'."
  end
  
  if @objects.key?(classname) and @objects[classname].key?(id)
    case @args[:cache]
      when :weak
        begin
          obj = @objects[classname][id].__getobj__
          
          if obj.is_a?(Knj::Datarow) and obj.respond_to?(:table) and obj.respond_to?(:id) and obj.table.to_sym == classname and obj.id.to_i == id
            return obj
          else
            #This actually happens sometimes... WTF!? - knj
            raise WeakRef::RefError
          end
        rescue WeakRef::RefError
          @objects[classname].delete(id)
        rescue NoMethodError => e
          #NoMethodError because the object might have been deleted from the cache, and __getobj__ then throws it.
          raise e if e.message != "undefined method `__getobj__' for nil:NilClass"
        end
      else
        return @objects[classname][id]
    end
  end
  
  self.requireclass(classname) if !@objects.key?(classname)
  
  if @args[:datarow] or @args[:custom]
    obj = @args[:module].const_get(classname).new(Knj::Hash_methods.new(:ob => self, :data => data))
  else
    args = [data]
    args = args | @args[:extra_args] if @args[:extra_args]
    obj = @args[:module].const_get(classname).new(*args)
  end
  
  case @args[:cache]
    when :weak
      @objects[classname][id] = WeakRef.new(obj)
    when :none
      return obj
    else
      @objects[classname][id] = obj
  end
  
  return obj
end

#get_by(classname, args = {}) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/knj/objects.rb', line 251

def get_by(classname, args = {})
  classname = classname.to_sym
  self.requireclass(classname)
  classob = @args[:module].const_get(classname)
  
  raise "list-function has not been implemented for #{classname}" if !classob.respond_to?("list")
  
  args["limit"] = 1
  self.list(classname, args) do |obj|
    return obj
  end
  
  return false
end

#get_try(obj, col_name, obj_name = nil) ⇒ Object



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/knj/objects.rb', line 266

def get_try(obj, col_name, obj_name = nil)
  if !obj_name
    if match = col_name.to_s.match(/^(.+)_id$/)
      obj_name = Knj::Php.ucwords(match[1]).to_sym
    else
      raise "Could not figure out objectname for: #{col_name}."
    end
  end
  
  id_data = obj[col_name].to_i
  return false if id_data.to_i <= 0
  
  begin
    return self.get(obj_name, id_data)
  rescue Knj::Errors::NotFound
    return false
  end
end

#init_class(classname) ⇒ Object



59
60
61
62
# File 'lib/knj/objects.rb', line 59

def init_class(classname)
  return false if @objects.key?(classname)
  @objects[classname] = {}
end

#list(classname, args = {}, &block) ⇒ Object

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.



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/knj/objects.rb', line 286

def list(classname, args = {}, &block)
  args = {} if args == nil
  classname = classname.to_sym
  self.requireclass(classname)
  classob = @args[:module].const_get(classname)
  
  raise "list-function has not been implemented for '#{classname}'." if !classob.respond_to?("list")
  
  if @args[:datarow] or @args[:custom]
    ret = classob.list(Knj::Hash_methods.new(:args => args, :ob => self, :db => @args[:db]), &block)
  else
    realargs = [args]
    realargs = realargs | @args[:extra_args] if @args[:extra_args]
    ret = classob.list(*realargs, &block)
  end
  
  #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.
  if block and ret.is_a?(Array)
    ret.each do |obj|
      block.call(obj)
    end
    return nil
  elsif block and ret != nil
    raise "Return should return nil because of block but didnt. It wasnt an array either..."
  elsif block
    return nil
  else
    return ret
  end
end

#list_bysql(classname, sql, d = nil, &block) ⇒ Object

Returns a list of a specific object by running specific SQL against the database.



424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/knj/objects.rb', line 424

def list_bysql(classname, sql, d = nil, &block)
  classname = classname.to_sym
  ret = [] if !block
  @args[:db].q(sql) do |d_obs|
    if block
      block.call(self.get(classname, d_obs))
    else
      ret << self.get(classname, d_obs)
    end
  end
  
  return ret if !block
end

#list_opts(classname, args = {}) ⇒ Object

Returns select-options-HTML for inserting into a HTML-select-element.



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/knj/objects.rb', line 318

def list_opts(classname, args = {})
  Knj::ArrayExt.hash_sym(args)
  classname = classname.to_sym
  
  if args[:list_args].is_a?(Hash)
    list_args = args[:list_args]
  else
    list_args = {}
  end
  
  html = ""
  
  if args[:addnew] or args[:add]
    html << "<option"
    html << " selected=\"selected\"" if !args[:selected]
    html << " value=\"\">#{_("Add new")}</option>"
  end
  
  self.list(classname, args[:list_args]) do |object|
    html << "<option value=\"#{object.id.html}\""
    
    selected = false
    if args[:selected].is_a?(Array) and args[:selected].index(object) != nil
      selected = true
    elsif args[:selected] and args[:selected].respond_to?("is_knj?") and args[:selected].id.to_s == object.id.to_s
      selected = true
    end
    
    html << " selected=\"selected\"" if selected
    
    obj_methods = object.class.instance_methods(false)
    
    begin
      if obj_methods.index("name") != nil or obj_methods.index(:name) != nil
        objhtml = object.name.html
      elsif obj_methods.index("title") != nil or obj_methods.index(:title) != nil
        objhtml = object.title.html
      elsif object.respond_to?(:data)
        obj_data = object.data
        
        if obj_data.key?(:name)
          objhtml = obj_data[:name]
        elsif obj_data.key?(:title)
          objhtml = obj_data[:title]
        end
      else
        objhtml = ""
      end
      
      raise "Could not figure out which name-method to call?" if !objhtml
      html << ">#{objhtml}</option>"
    rescue Exception => e
      html << ">[#{object.class.name}: #{e.message}]</option>"
    end
  end
  
  return html
end

#list_optshash(classname, args = {}) ⇒ Object

Returns a hash which can be used to generate HTML-select-elements.



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# File 'lib/knj/objects.rb', line 378

def list_optshash(classname, args = {})
  Knj::ArrayExt.hash_sym(args)
  classname = classname.to_sym
  
  if args[:list_args].is_a?(Hash)
    list_args = args[:list_args]
  else
    list_args = {}
  end
  
  if RUBY_VERSION[0..2] == 1.8 and Knj::Php.class_exists("Dictionary")
    print "Spawning dictionary.\n" if args[:debug]
    list = Dictionary.new
  else
    print "Spawning normal hash.\n" if args[:debug]
    list = {}
  end
  
  if args[:addnew] or args[:add]
    list["0"] = _("Add new")
  elsif args[:choose]
    list["0"] = _("Choose") + ":"
  elsif args[:all]
    list["0"] = _("All")
  elsif args[:none]
    list["0"] = _("None")
  end
  
  print "Doing loop\n" if args[:debug]
  self.list(classname, args[:list_args]) do |object|
    print "Object: #{object.id}\n" if args[:debug]
    
    if object.respond_to?(:name)
      list[object.id] = object.name
    elsif object.respond_to?(:title)
      list[object.id] = object.title
    else
      raise "Object of class '#{object.class.name}' doesnt support 'name' or 'title."
    end
  end
  
  print "Returning...\n" if args[:debug]
  return list
end

#load_class(classname, args = {}) ⇒ Object

Loads a Datarow-class by calling various static methods.



172
173
174
175
176
177
178
179
180
181
182
# File 'lib/knj/objects.rb', line 172

def load_class(classname, args = {})
  if args[:class]
    classob = args[:class]
  else
    classob = @args[:module].const_get(classname)
  end
  
  pass_arg = Knj::Hash_methods.new(:ob => self, :db => @args[:db])
  classob.load_columns(pass_arg) if classob.respond_to?(:load_columns)
  classob.datarow_init(pass_arg) if classob.respond_to?(:datarow_init)
end

#object_finalizer(id) ⇒ Object



243
244
245
246
247
248
249
# File 'lib/knj/objects.rb', line 243

def object_finalizer(id)
  classname = @objects_idclass[id]
  if classname
    @objects[classname].delete(id)
    @objects_idclass.delete(id)
  end
end

#objectsObject

Returns a cloned version of the @objects variable. Cloned because iteration on it may crash some of the other methods in Ruby 1.9+



65
66
67
68
69
70
71
72
73
# File 'lib/knj/objects.rb', line 65

def objects
  objs_cloned = {}
  
  @objects.keys.each do |key|
    objs_cloned[key] = @objects[key].clone
  end
  
  return objs_cloned
end

#requireclass(classname, args = {}) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/knj/objects.rb', line 133

def requireclass(classname, args = {})
  classname = classname.to_sym
  
  return false if @objects.key?(classname)
  
  @mutex_require.synchronize do
    if (@args[:require] or !@args.key?(:require)) and (!args.key?(:require) or args[:require])
      filename = "#{@args[:class_path]}/#{@args[:class_pre]}#{classname.to_s.downcase}.rb"
      filename_req = "#{@args[:class_path]}/#{@args[:class_pre]}#{classname.to_s.downcase}"
      raise "Class file could not be found: #{filename}." if !File.exists?(filename)
      require filename_req
    end
    
    if args[:class]
      classob = args[:class]
    else
      begin
        classob = @args[:module].const_get(classname)
      rescue NameError => e
        if @events.connected?(:missing_class)
          @events.call(:missing_class, {
            :class => classname
          })
          classob = @args[:module].const_get(classname)
        else
          raise e
        end
      end
    end
    
    if (classob.respond_to?(:load_columns) or classob.respond_to?(:datarow_init)) and (!args.key?(:load) or args[:load])
      self.load_class(classname, args)
    end
    
    @objects[classname] = {}
  end
end

#sqlhelper(list_args, args_def) ⇒ Object

This method helps build SQL from Objects-instances list-method. It should not be called directly but only through Objects.list.



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/knj/objects/objects_sqlhelper.rb', line 3

def sqlhelper(list_args, args_def)
  args = args_def
  
  if args[:db]
    db = args[:db]
  else
    db = @args[:db]
  end
  
  if args[:table]
    table_def = "`#{db.esc_table(args[:table])}`."
  else
    table_def = ""
  end
  
  sql_joins = ""
  sql_where = ""
  sql_order = ""
  sql_limit = ""
  sql_groupby = ""
  
  do_joins = {}
  
  limit_from = nil
  limit_to = nil
  
  if list_args.key?("orderby")
    orders = []
    orderstr = list_args["orderby"]
    list_args["orderby"] = [list_args["orderby"]] if list_args["orderby"].is_a?(Hash)
    
    if list_args["orderby"].is_a?(String)
      found = false
      found = true if args[:cols].key?(orderstr)
      
      if found
        sql_order << " ORDER BY "
        ordermode = " ASC"
        if list_args.key?("ordermode")
          if list_args["ordermode"] == "desc"
            ordermode = " DESC"
          elsif list_args["ordermode"] == "asc"
            ordermode = " ASC"
            raise "Unknown ordermode: #{list_args["ordermode"]}"
          end
          
          list_args.delete("ordermode")
        end
        
        sql_order << "#{table_def}`#{db.esc_col(list_args["orderby"])}`#{ordermode}"
        list_args.delete("orderby")
      end
    elsif list_args["orderby"].is_a?(Array)
      sql_order << " ORDER BY "
      
      list_args["orderby"].each do |val|
        ordermode = nil
        orderstr = nil
        found = false
        
        if val.is_a?(Array)
          if val[1] == "asc"
            ordermode = " ASC"
          elsif val[1] == "desc"
            ordermode = "DESC"
          end
          
          if val[0].is_a?(Array)
            if args[:joined_tables]
              args[:joined_tables].each do |table_name, table_data|
                next if table_name.to_s != val[0][0].to_s
                do_joins[table_name] = true
                orders << "`#{db.esc_table(table_name)}`.`#{db.esc_col(val[0][1])}`#{ordermode}"
                found = true
                break
              end
            end
            
            raise "Could not find joined table for ordering: '#{val[0][0]}'." if !found
          else
            orderstr = val[0]
          end
        elsif val.is_a?(String)
          orderstr = val
          ordermode = " ASC"
        elsif val.is_a?(Hash) and val[:type] == :sql
          orders << val[:sql]
          found = true
        elsif val.is_a?(Hash) and val[:type] == :case
          caseorder = " CASE"
          
          val[:case].each do |key, caseval|
            col = key.first
            isval = key.last
            col_str = nil
            
            if col.is_a?(Array)
              raise "No joined tables for '#{args[:table]}'." if !args[:joined_tables]
              
              found = false
              args[:joined_tables].each do |table_name, table_data|
                if table_name == col.first
                  do_joins[table_name] = true
                  col_str = "`#{db.esc_table(table_name)}`.`#{db.esc_col(col.last)}`"
                  found = true
                  break
                end
              end
              
              raise "No such joined table on '#{args[:table]}': '#{col.first}' (#{col.first.class.name}) with the following joined table:\n#{Knj::Php.print_r(args[:joined_tables], true)}" if !found
            elsif col.is_a?(String) or col.is_a?(Symbol)
              col_str = "#{table_def}`#{col}`"
              found = true
            else
              raise "Unknown type for case-ordering: '#{col.class.name}'."
            end
            
            raise "'colstr' was not set." if !col_str
            caseorder << " WHEN #{col_str} = '#{db.esc(isval)}' THEN '#{db.esc(caseval)}'"
          end
          
          if val[:else]
            caseorder << " ELSE '#{db.esc(val[:else])}'"
          end
          
          caseorder << " END"
          orders << caseorder
        elsif val.is_a?(Hash)
          raise "No joined tables." if !args.key?(:joined_tables)
          
          if val[:mode] == "asc"
            ordermode = " ASC"
          elsif val[:mode] == "desc"
            ordermode = " DESC"
          end
          
          if args[:joined_tables]
            args[:joined_tables].each do |table_name, table_data|
              if table_data[:parent_table]
                table_name_real = table_name
              elsif table_data[:datarow]
                table_name_real = self.datarow_from_datarow_argument(table_data[:datarow]).classname
              else
                table_name_real = @args[:module].const_get(table_name).classname
              end
              
              if table_name.to_s == val[:table].to_s
                do_joins[table_name] = true
                
                if val[:sql]
                  orders << val[:sql]
                elsif val[:col]
                  orders << "`#{db.esc_table(table_name_real)}`.`#{db.esc_col(val[:col])}`#{ordermode}"
                else
                  raise "Couldnt figure out how to order based on keys: '#{val.keys.sort}'."
                end
                
                found = true
                break
              end
            end
          end
        else
          raise "Unknown object: #{val.class.name}"
        end
        
        found = true if args[:cols].key?(orderstr)
        
        if !found
          raise "Column not found for ordering: #{orderstr}."
        end
        
        orders << "#{table_def}`#{db.esc_col(orderstr)}`#{ordermode}" if orderstr
      end
      
      sql_order << orders.join(", ")
      list_args.delete("orderby")
    else
      raise "Unknown orderby object: #{list_args["orderby"].class.name}."
    end
  end
  
  list_args.each do |realkey, val|
    found = false
    
    if realkey.is_a?(Array)
      if !args[:joins_skip]
        datarow_obj = self.datarow_obj_from_args(args_def, list_args, realkey[0])
        args = datarow_obj.columns_sqlhelper_args
      else
        datarow_obj = @args[:module].const_get(realkey[0])
        args = args_def
      end
      
      table_sym = realkey[0].to_sym
      do_joins[table_sym] = true
      list_table_name_real = table_sym
      table = "`#{db.esc_table(list_table_name_real)}`."
      key = realkey[1]
    else
      table = table_def
      args = args_def
      key = realkey
    end
    
    if args[:cols].key?(key)
      if val.is_a?(Array)
        if val.empty?
          sql_where << " AND false"
        else
          escape_sql = Knj::ArrayExt.join(
            :arr => val,
            :callback => proc{|value|
              db.escape(value)
            },
            :sep => ",",
            :surr => "'"
          )
          sql_where << " AND #{table}`#{db.esc_col(key)}` IN (#{escape_sql})"
        end
      elsif val.is_a?(Hash) and val[:type] == "col"
        raise "No table was given for join." if !val.key?(:table)
        
        do_joins[val[:table].to_sym] = true
        sql_where << " AND #{table}`#{db.esc_col(key)}` = `#{db.esc_table(val[:table])}`.`#{db.esc_col(val[:name])}`"
      elsif val.is_a?(Hash) and val[:type] == :sqlval and val[:val] == :null
        sql_where << " AND #{table}`#{db.esc_col(key)}` IS NULL"
      elsif val.is_a?(Proc)
        call_args = Knj::Hash_methods.new(:ob => self, :db => db)
        sql_where << " AND #{table}`#{db.esc_col(key)}` = '#{db.esc(val.call(call_args))}'"
      else
        sql_where << " AND #{table}`#{db.esc_col(key)}` = '#{db.esc(val)}'"
      end
      
      found = true
    elsif args.key?(:cols_bools) and args[:cols_bools].index(key) != nil
      if val.is_a?(TrueClass) or (val.is_a?(Integer) and val.to_i == 1) or (val.is_a?(String) and (val == "true" or val == "1"))
        realval = "1"
      elsif val.is_a?(FalseClass) or (val.is_a?(Integer) and val.to_i == 0) or (val.is_a?(String) and (val == "false" or val == "0"))
        realval = "0"
      else
        raise "Could not make real value out of class: #{val.class.name} => #{val}."
      end
      
      sql_where << " AND #{table}`#{db.esc_col(key)}` = '#{db.esc(realval)}'"
      found = true
    elsif key.to_s == "limit_from"
      limit_from = val.to_i
      found = true
    elsif key.to_s == "limit_to"
      limit_to = val.to_i
      found = true
    elsif key.to_s == "limit"
      limit_from = 0
      limit_to = val.to_i
      found = true
    elsif args.key?(:cols_dbrows) and args[:cols_dbrows].index("#{key.to_s}_id") != nil
      if val == false
        sql_where << " AND #{table}`#{db.esc_col(key.to_s + "_id")}` = '0'"
      elsif val.is_a?(Array)
        if val.empty?
          sql_where << " AND false"
        else
          sql_where << " AND #{table}`#{db.esc_col("#{key}_id")}` IN (#{Knj::ArrayExt.join(:arr => val, :sep => ",", :surr => "'", :callback => proc{|obj| obj.id.sql})})"
        end
      else
        sql_where << " AND #{table}`#{db.esc_col(key.to_s + "_id")}` = '#{db.esc(val.id)}'"
      end
      
      found = true
    elsif match = key.match(/^([A-z_\d]+)_(search|has)$/) and args[:cols].key?(match[1]) != nil
      if match[2] == "search"
        Knj::Strings.searchstring(val).each do |str|
          sql_where << " AND #{table}`#{db.esc_col(match[1])}` LIKE '%#{db.esc(str)}%'"
        end
      elsif match[2] == "has"
        if val
          sql_where << " AND #{table}`#{db.esc_col(match[1])}` != ''"
        else
          sql_where << " AND #{table}`#{db.esc_col(match[1])}` = ''"
        end
      end
      
      found = true
    elsif match = key.match(/^([A-z_\d]+)_(not|lower)$/) and args[:cols].key?(match[1])
      if match[2] == "not"
        if val.is_a?(Array)
          if val.empty?
            sql_where << " AND false"
          else
            escape_sql = Knj::ArrayExt.join(
              :arr => val,
              :callback => proc{|value|
                db.escape(value)
              },
              :sep => ",",
              :surr => "'"
            )
            sql_where << " AND #{table}`#{db.esc_col(match[1])}` NOT IN (#{escape_sql})"
          end
        else
          sql_where << " AND #{table}`#{db.esc_col(match[1])}` != '#{db.esc(val)}'"
        end
      elsif match[2] == "lower"
        sql_where << " AND LOWER(#{table}`#{db.esc_col(match[1])}`) = LOWER('#{db.esc(val)}')"
      else
        raise "Unknown mode: '#{match[2]}'."
      end
      
      found = true
    elsif args.key?(:cols_date) and match = key.match(/^(.+)_(day|month|year|from|to|below|above)$/) and args[:cols_date].index(match[1]) != nil
      val = Knj::Datet.in(val) if val.is_a?(Time)
      
      if match[2] == "day"
        if val.is_a?(Array)
          sql_where << " AND ("
          first = true
          
          val.each do |realval|
            if first
              first = false
            else
              sql_where << " OR "
            end
            
            sql_where << "DATE_FORMAT(#{table}`#{db.esc_col(match[1])}`, '%d %m %Y') = DATE_FORMAT('#{db.esc(realval.dbstr)}', '%d %m %Y')"
          end
          
          sql_where << ")"
        else
          sql_where << " AND DATE_FORMAT(#{table}`#{db.esc_col(match[1])}`, '%d %m %Y') = DATE_FORMAT('#{db.esc(val.dbstr)}', '%d %m %Y')"
        end
      elsif match[2] == "month"
        sql_where << " AND DATE_FORMAT(#{table}`#{db.esc_col(match[1])}`, '%m %Y') = DATE_FORMAT('#{db.esc(val.dbstr)}', '%m %Y')"
      elsif match[2] == "year"
        sql_where << " AND DATE_FORMAT(#{table}`#{db.esc_col(match[1])}`, '%Y') = DATE_FORMAT('#{db.esc(val.dbstr)}', '%Y')"
      elsif match[2] == "from" or match[2] == "above"
        sql_where << " AND #{table}`#{db.esc_col(match[1])}` >= '#{db.esc(val.dbstr)}'"
      elsif match[2] == "to" or match[2] == "below"
        sql_where << " AND #{table}`#{db.esc_col(match[1])}` <= '#{db.esc(val.dbstr)}'"
      else
        raise "Unknown date-key: #{match[2]}."
      end
      
      found = true
    elsif args.key?(:cols_num) and match = key.match(/^(.+)_(from|to|above|below)$/) and args[:cols_num].index(match[1]) != nil
      if match[2] == "from"
        sql_where << " AND #{table}`#{db.esc_col(match[1])}` <= '#{db.esc(val)}'"
      elsif match[2] == "to"
        sql_where << " AND #{table}`#{db.esc_col(match[1])}` >= '#{db.esc(val)}'"
      elsif match[2] == "above"
        sql_where << " AND #{table}`#{db.esc_col(match[1])}` > '#{db.esc(val)}'"
      elsif match[2] == "below"
        sql_where << " AND #{table}`#{db.esc_col(match[1])}` < '#{db.esc(val)}'"
      else
        raise "Unknown method of treating cols-num-argument: #{match[2]}."
      end
      
      found = true
    elsif match = key.match(/^(.+)_lookup$/) and args[:cols].key?("#{match[1]}_id") and args[:cols].key?("#{match[1]}_class")
      sql_where << " AND #{table}`#{db.esc_col("#{match[1]}_class")}` = '#{db.esc(val.table)}'"
      sql_where << " AND #{table}`#{db.esc_col("#{match[1]}_id")}` = '#{db.esc(val.id)}'"
      found = true
    elsif realkey == "groupby"
      found = true
      
      if val.is_a?(Array)
        val.each do |col_name|
          raise "Column '#{val}' not found on table '#{table}'." if !args[:cols].key?(col_name)
          sql_groupby << ", " if sql_groupby.length > 0
          sql_groupby << "#{table}`#{db.esc_col(col_name)}`"
        end
      elsif val.is_a?(String)
        sql_groupby << ", " if sql_groupby.length > 0
        sql_groupby << "#{table}`#{db.esc_col(val)}`"
      else
        raise "Unknown class given for 'groupby': '#{val.class.name}'."
      end
    end
    
    list_args.delete(realkey) if found
  end
  
  args = args_def
  
  if !args[:joins_skip]
    raise "No joins defined on '#{args[:table]}' for: '#{args[:table]}'." if !do_joins.empty? and !args[:joined_tables]
    
    do_joins.each do |table_name, temp_val|
      raise "No join defined on table '#{args[:table]}' for table '#{table_name}'." if !args[:joined_tables].key?(table_name)
      table_data = args[:joined_tables][table_name]
      
      if table_data.key?(:parent_table)
        join_table_name_real = table_name
        sql_joins << " LEFT JOIN `#{table_data[:parent_table]}` AS `#{table_name}` ON 1=1"
      else
        const = @args[:module].const_get(table_name)
        join_table_name_real = const.classname
        sql_joins << " LEFT JOIN `#{const.table}` AS `#{const.classname}` ON 1=1"
      end
      
      if table_data[:ob]
        ob = table_data[:ob]
      else
        ob = self
      end
      
      class_name = args[:table].to_sym
      
      if table_data[:datarow]
        datarow = self.datarow_from_datarow_argument(table_data[:datarow])
      else
        self.requireclass(class_name) if @objects.key?(class_name)
        datarow = @args[:module].const_get(class_name)
      end
      
      if !datarow.columns_sqlhelper_args
        ob.requireclass(datarow.table.to_sym)
        raise "No SQL-helper-args on class '#{datarow.table}' ???" if !datarow.columns_sqlhelper_args
      end
      
      newargs = datarow.columns_sqlhelper_args.clone
      newargs[:table] = join_table_name_real
      newargs[:joins_skip] = true
      
      #Clone the where-arguments and run them against another sqlhelper to sub-join.
      join_args = table_data[:where].clone
      ret = self.sqlhelper(join_args, newargs)
      sql_joins << ret[:sql_where]
      
      #If any of the join-arguments are left, then we should throw an error.
      join_args.each do |key, val|
        raise "Invalid key '#{key}' when trying to join table '#{table_name}' on table '#{args_def[:table]}'."
      end
    end
  end
  
  #If limit arguments has been given then add them.
  if limit_from and limit_to
    sql_limit = " LIMIT #{limit_from}, #{limit_to}"
  end
  
  sql_groupby = nil if sql_groupby.length <= 0
  
  return {
    :sql_joins => sql_joins,
    :sql_where => sql_where,
    :sql_limit => sql_limit,
    :sql_order => sql_order,
    :sql_groupby => sql_groupby
  }
end

#static(class_name, method_name, *args, &block) ⇒ Object

Calls a static method on a class. Passes the d-variable which contains the Objects-object, database-reference and more…



511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/knj/objects.rb', line 511

def static(class_name, method_name, *args, &block)
  raise "Only available with datarow enabled." if !@args[:datarow] and !@args[:custom]
  class_name = class_name
  method_name = method_name
  
  self.requireclass(class_name)
  class_obj = @args[:module].const_get(class_name)
  
  #Sometimes this raises the exception but actually responds to the class? Therefore commented out. - knj
  #raise "The class '#{class_obj.name}' has no such method: '#{method_name}' (#{class_obj.methods.sort.join(", ")})." if !class_obj.respond_to?(method_name)
  
  pass_args = []
  
  if @args[:datarow]
    pass_args << Knj::Hash_methods.new(:ob => self, :db => self.db)
  else
    pass_args << Knj::Hash_methods.new(:ob => self)
  end
  
  args.each do |arg|
    pass_args << arg
  end
  
  class_obj.send(method_name, *pass_args, &block)
end

#unset(object) ⇒ Object

Unset object. Do this if you are sure, that there are no more references left. This will be done automatically when deleting it.



538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/knj/objects.rb', line 538

def unset(object)
  if object.is_a?(Array)
    object.each do |obj|
      unset(obj)
    end
    return nil
  end
  
  classname = object.class.name
  
  if @args[:module]
    classname = classname.gsub(@args[:module].name + "::", "")
  end
  
  classname = classname.to_sym
  
  #if [email protected]?(classname)
    #raise "Could not find object class in cache: #{classname}."
  #elsif !@objects[classname].key?(object.id.to_i)
    #errstr = ""
    #errstr << "Could not unset object from cache.\n"
    #errstr << "Class: #{object.class.name}.\n"
    #errstr << "ID: #{object.id}.\n"
    #errstr << "Could not find object ID in cache."
    #raise errstr
  #else
    @objects[classname].delete(object.id.to_i)
  #end
end

#unset_class(classname) ⇒ Object



568
569
570
571
572
573
574
575
576
577
578
579
580
581
# File 'lib/knj/objects.rb', line 568

def unset_class(classname)
  if classname.is_a?(Array)
    classname.each do |classn|
      self.unset_class(classn)
    end
    
    return false
  end
  
  classname = classname.to_sym
  
  return false if !@objects.key?(classname)
  @objects[classname] = {}
end