lib/knj/objects.rb in knjrbfw-0.0.21 vs lib/knj/objects.rb in knjrbfw-0.0.22

- old
+ new

@@ -11,14 +11,17 @@ @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 = {} + @locks = {} @data = {} @mutex_require = Mutex.new - require "weakref" if @args[:cache] == :weak and !Kernel.const_defined?(:WeakRef) + if @args[:cache] == :weak + require "#{$knjpath}wref" + end @events = Knj::Event_handler.new @events.add_event( :name => :no_html, :connections_max => 1 @@ -55,12 +58,20 @@ end end end def init_class(classname) + classname = classname.to_sym return false if @objects.key?(classname) - @objects[classname] = {} + + if @args[:cache] == :weak + @objects[classname] = Knj::Wref_map.new + else + @objects[classname] = {} + end + + @locks[classname] = Mutex.new end #Returns a cloned version of the @objects variable. Cloned because iteration on it may crash some of the other methods in Ruby 1.9+ def objects objs_cloned = {} @@ -70,34 +81,38 @@ end return objs_cloned end + #Returns the database-connection used by this instance of Objects. def db return @args[:db] end + #Returns the total count of objects currently held by this instance. def count_objects count = 0 @objects.keys.each do |key| count += @objects[key].length end return count end + #This connects a block to an event. When the event is called the block will be executed. 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 + #This method is used to call the connected callbacks for an event. def call(args, &block) - classstr = args["object"].class.to_s + classstr = args["object"].class.to_s.split("::").last if @callbacks.key?(classstr) @callbacks[classstr].clone.each do |callback_key, callback| docall = false @@ -130,14 +145,16 @@ end end def requireclass(classname, args = {}) classname = classname.to_sym - return false if @objects.key?(classname) @mutex_require.synchronize do + #Maybe the classname got required meanwhile the synchronized wait - check again. + return false if @objects.key?(classname) + 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 @@ -162,11 +179,11 @@ 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] = {} + self.init_class(classname) end end #Loads a Datarow-class by calling various static methods. def load_class(classname, args = {}) @@ -193,52 +210,48 @@ 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) + if @objects.key?(classname) 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" + if obj = @objects[classname].get!(id) and obj.id.to_i == id + return obj end else - return @objects[classname][id] + return @objects[classname][id] if @objects[classname].key?(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 + @locks[classname].synchronize do + #Maybe the object got spawned while we waited for the lock? If so we shouldnt spawn another instance. + if obj = @objects[classname].get!(id) and obj.id.to_i == id return obj + end + + #Spawn object. + if @args[:datarow] or @args[:custom] + obj = @args[:module].const_get(classname).new(Knj::Hash_methods.new(:ob => self, :data => data)) else - @objects[classname][id] = obj + args = [data] + args = args | @args[:extra_args] if @args[:extra_args] + obj = @args[:module].const_get(classname).new(*args) + end + + #Save object in cache. + case @args[:cache] + when :none + return obj + else + @objects[classname][id] = obj + end end + #Return spawned object. return obj end def object_finalizer(id) classname = @objects_idclass[id] @@ -384,14 +397,12 @@ 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") @@ -401,43 +412,56 @@ 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 #Returns a list of a specific object by running specific SQL against the database. - def list_bysql(classname, sql, d = nil, &block) + def list_bysql(classname, sql, args = nil, &block) classname = classname.to_sym ret = [] if !block - @args[:db].q(sql) do |d_obs| + qargs = nil + + if args + args.each do |key, val| + case key + when :cloned_ubuf + qargs = {:cloned_ubuf => true} + else + raise "Invalid key: '#{key}'." + end + end + end + + @args[:db].q(sql, qargs) 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 + if !block + return ret + else + return nil + end end - # Add a new object to the database and to the cache. + #Add a new object to the database and to the cache. def add(classname, data = {}) classname = classname.to_sym self.requireclass(classname) if @args[:datarow] @@ -575,28 +599,37 @@ end classname = classname.to_sym return false if !@objects.key?(classname) - @objects[classname] = {} + @objects.delete(classname) end #Delete an object. Both from the database and from the cache. 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] + #If autodelete is set by 'has_many'-method, go through it and delete the various objects first. + object.class.autodelete_data.each do |adel_data| + self.list(adel_data[:classname], {adel_data[:colname].to_s => object.id}) do |obj_del| + self.delete(obj_del) + end + end + + #If depend is set by 'has_many'-method, check if any objects exists and raise error if so. 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." + obj = self.get_by(dep_data[:classname], {dep_data[:colname].to_s => object.id}) + if obj + raise "Cannot delete <#{object.class.name}:#{object.id}> because <#{obj.class.name}:#{obj.id}> depends on it." end end + #Delete any translations that has been set on the object by 'has_translation'-method. if object.class.translations _kas.trans_del(object) end @args[:db].delete(object.table, {:id => obj_id}) @@ -666,22 +699,11 @@ end #Runs through all objects-weaklink-references and removes the weaklinks if the object has been recycled. 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 + @objects[classn].clean end end #Regenerates cache from ObjectSpace. Its pretty dangerous but can be used in envs where WeakRef is not supported (did someone say Rhodes?). def clean_recover @@ -705,6 +727,6 @@ end end end end -require "#{$knjpath}objects/objects_sqlhelper" +require "#{$knjpath}objects/objects_sqlhelper" \ No newline at end of file