baza 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
1
+ require "#{$knjpath}event_handler"
2
+
3
+ class Baza::ModelCustom
4
+ #Used to determine if this is a knj-datarow-object.
5
+ def is_knj?
6
+ return true
7
+ end
8
+
9
+ #Initializes variables on the class from objects.
10
+ def self.datarow_init(d)
11
+ @@ob = d.ob
12
+ @@db = d.db
13
+ end
14
+
15
+ def self.has_one(arr)
16
+ arr.each do |val|
17
+ methodname = nil
18
+ colname = nil
19
+ classname = nil
20
+
21
+ if val.is_a?(Symbol)
22
+ classname = val
23
+ methodname = val.to_s.downcase.to_sym
24
+ colname = "#{val.to_s.downcase}_id".to_sym
25
+ elsif val.is_a?(Array)
26
+ classname, colname, methodname = *val
27
+ elsif val.is_a?(Hash)
28
+ classname, colname, methodname = val[:class], val[:col], val[:method]
29
+ else
30
+ raise "Unknown argument-type: '#{arr.class.name}'."
31
+ end
32
+
33
+ methodname = classname.to_s.downcase if !methodname
34
+ colname = "#{classname.to_s.downcase}_id".to_sym if !colname
35
+
36
+ define_method(methodname) do
37
+ return @@ob.get_try(self, colname, classname)
38
+ end
39
+
40
+ methodname_html = "#{methodname.to_s}_html".to_sym
41
+ define_method(methodname_html) do |*args|
42
+ obj = self.send(methodname)
43
+ return @@ob.events.call(:no_html, classname) if !obj
44
+
45
+ raise "Class '#{classname}' does not have a 'html'-method." if !obj.respond_to?(:html)
46
+ return obj.html(*args)
47
+ end
48
+ end
49
+ end
50
+
51
+ def self.events
52
+ if !@events
53
+ @events = Knj::Event_handler.new
54
+ @events.add_event(:name => :add, :connections_max => 1)
55
+ @events.add_event(:name => :update, :connections_max => 1)
56
+ @events.add_event(:name => :data_from_id, :connections_max => 1)
57
+ @events.add_event(:name => :delete, :connections_max => 1)
58
+ end
59
+
60
+ return @events
61
+ end
62
+
63
+ def self.classname
64
+ self.name.split("::").last
65
+ end
66
+
67
+ def self.add(d)
68
+ return @events.call(:add, d)
69
+ end
70
+
71
+ def self.table
72
+ return self.name.split("::").last
73
+ end
74
+
75
+ def deleted?
76
+ return true if !@data
77
+ return false
78
+ end
79
+
80
+ def table
81
+ return self.class.table
82
+ end
83
+
84
+ def initialize(data, args)
85
+ if data.is_a?(Hash)
86
+ @data = Knj::ArrayExt.hash_sym(data)
87
+ @id = self.id
88
+ else
89
+ @id = data
90
+ self.reload
91
+ end
92
+ end
93
+
94
+ def reload
95
+ raise "No 'data_from_id'-event connected to class." if !self.class.events.connected?(:data_from_id)
96
+ data = self.class.events.call(:data_from_id, Knj::Hash_methods.new(:id => @id))
97
+ raise "No data was received from the event: 'data_from_id'." if !data
98
+ raise "Data expected to be a hash but wasnt: '#{data.class.name}'." if !data.is_a?(Hash)
99
+ @data = Knj::ArrayExt.hash_sym(data)
100
+ end
101
+
102
+ def update(data)
103
+ ret = self.class.events.call(:update, Knj::Hash_methods.new(:object => self, :data => data))
104
+ self.reload
105
+ return ret
106
+ end
107
+
108
+ #Returns a key from the hash that this object is holding or raises an error if it doesnt exist.
109
+ def [](key)
110
+ raise "No data spawned on object." if !@data
111
+ raise "No such key: '#{key}'. Available keys are: '#{@data.keys.sort.join(", ")}'." if !@data.key?(key)
112
+ return @data[key]
113
+ end
114
+
115
+ #Returns the ID of the object.
116
+ def id
117
+ return self[:id]
118
+ end
119
+
120
+ #Returns the name of the object, which can be taken from various data or various defined methods.
121
+ def name
122
+ if @data.key?(:title)
123
+ return @data[:title]
124
+ elsif @data.key?(:name)
125
+ return @data[:name]
126
+ end
127
+
128
+ obj_methods = self.class.instance_methods(false)
129
+ [:name, :title].each do |method_name|
130
+ return self.method(method_name).call if obj_methods.index(method_name)
131
+ end
132
+
133
+ raise "Couldnt figure out the title/name of the object on class #{self.class.name}."
134
+ end
135
+
136
+ alias :title :name
137
+
138
+ def delete
139
+ self.class.events.call(:delete, Knj::Hash_methods.new(:object => self))
140
+ end
141
+
142
+ def destroy
143
+ @data = nil
144
+ end
145
+
146
+ def each(&args)
147
+ return @data.each(&args)
148
+ end
149
+
150
+ def to_hash
151
+ return @data.clone
152
+ end
153
+ end
@@ -0,0 +1,957 @@
1
+ class Baza::ModelHandler
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 wref_map = @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
+ end
956
+
957
+ require "#{$knjpath}objects/objects_sqlhelper"