module Spider; module Model # The QuerySet expresses represents a Query applied on a Model. # It includes Enumerable, and can be accessed as an Array; but, the QuerySet is lazy, and the actual data will be # fetched only when actually requested, or when a {#load} is issued. # How much data is fetched and kept in memory can be controlled by setting the {#fetch_window} # and the {#keep_window}. class QuerySet include Enumerable # BaseModel instance pointing to this QuerySet # @return [BaseModel] attr_accessor :_parent # Element inside the _parent pointing to this QuerySet. # @return [Element] attr_accessor :_parent_element # Disables parent setting for this QuerySet # @return [bool] attr_accessor :_no_parent # Raw data returned by the mapper, if requested. # @return [Hash] attr_reader :raw_data # An Hash of autoloaded elements. # @return [Hash] attr_reader :loaded_elements # The actual fetched objects. # @return [Array] attr_reader :objects # The Query # @return [Model::Query] attr_accessor :query # Set by mapper # @return [Model::Query] attr_accessor :last_query # :nodoc: TODO: remove? # The BaseModel subclass # @return [Class cat, :other_animal => tiger) # cat.friends << friend # # since the junction was created setting append_element = :other_animal, one can do # cat.friends << lion # @return [Element] attr_accessor :append_element # If false, prevents the QuerySet from loading. # @return [bool] attr_accessor :loadable # If bool, on't put this queryset's objects into the IdentityMapper # @return [bool] attr_accessor :_no_identity_mapper # @return [bool] True when the QuerySet has been modified after loading attr_accessor :modified # Instantiates a non-autoloading queryset # @param [Class m_sizes[key] end end elements = @model.elements_array.select{ |el| m_sizes[el.name] > 0} elements.each do |el| m_sizes[el.name] = el.label.length if el.label.length > m_sizes[el.name] + 1 end reduce = columns.to_f/(m_sizes.values.inject{ |sum, s| sum + s }) sizes = {} m_sizes.each_key { |k| sizes[k] = m_sizes[k] * reduce } avail = columns - sizes.values.inject{ |sum, s| sum + s } while avail > 0 && (truncated = sizes.reject{ |k, v| v < m_sizes[k] }).length > 0 truncated.each_key do |k| break if avail < 1 sizes[k] += 1; avail -= 1 end end sizes.each do |k, v| sizes[k] = v.floor end print "\n" 1.upto(columns) { print "-" } print "\n" elements.each do |el| print "|" print el.label[0..sizes[el.name]-1].ljust(sizes[el.name]) end print "\n" 1.upto(columns) { print "-" } print "\n" a.each do |row| elements.each do |el| print "|" print row[el.name][0..sizes[el.name]-1].ljust(sizes[el.name]) end print "\n" end 1.upto(columns) { print "-" } print "\n" end # @return [Array] The Array corresponding to the QuerySet def to_a self.map{ |row| row } end # @return [Array] A reversed Array def reverse self.to_a.reverse end # @return [Array] Calls map on currently loaded objects def map_current a = [] each_current{ |row| a << yield(row) } a end # Returns an array of Hashes, with each value of the object is converted to string. # @return [Array] def to_flat_array map do |obj| h = {} obj.class.each_element do |el| h[el.name] = obj.element_has_value?(el) ? obj.get(el).to_s : '' end h end end # Removes the objects for which the block returns true from the QuerySet # @yield [BaseModel] # @return [void] def reject!(&proc) @objects.reject!(&proc) end # Removes all objects from the QuerySet # @return [void] def empty! @objects = [] end # @return [String] All the objects, to_s, joined by ', ' def to_s self.map{ |o| o.to_s }.join(', ') end # Missing methods will be sent to the query def method_missing(method, *args, &proc) el = @model.elements[method] if (el && el.model? && el.reverse) return element_queryset(el) end return @query.send(method, *args, &proc) if @query.respond_to?(method) return super end # @param [Element|Symbol] element # @return [QuerySet] The QuerySet corresponding to an element in the current QuerySet def element_queryset(el) el = @model.elements[el] if el.is_a?(Symbol) condition = el.condition if (el.attributes[:element_query]) el = @model.elements[el.attributes[:element_query]] end cond = Spider::Model::Condition.new cond[el.reverse] = self.map_current{ |row| row } cond = cond.and(condition) if (condition) return self.class.new(el.model, Query.new(cond)) end # Given a dotted path, will return an array of all objects reachable by that path # Example # objectset.all_children('friends.collegues.addresses.street_name') # @param [String] path # @return [Array] An array of all found objects def all_children(path) if (path.length > 0) children = @objects.map{ |obj| obj.all_children(path.clone) }.flatten else return @objects end end # Registers that the element has been loaded. # @param [Element|Symbol] # @return [void] def element_loaded(element) element = element.name if element.is_a?(Element) @loaded_elements[element] = true end # @param [Element|Symbol] # @return [bool] True if the element has been loaded from the Storage. def element_loaded?(element) element = element.name if element.is_a?(Element) @loaded_elements[element] end # Returns the current QuerySet IdentityMapper instance, or instantiates a new one # @return [IdentityMapper] The IdentityMapper def identity_mapper return Spider::Model.identity_mapper if Spider::Model.identity_mapper @identity_mapper ||= IdentityMapper.new end # Assigns an IdentityMapper to the QuerySet # @param [IdentityMapper] im # @return [void] def identity_mapper=(im) @identity_mapper = im end # Calls {Query#with_superclass} on the query. # @return [self] def with_superclass @query.with_superclass return self end ######################################## # Condition, request and query methods # ######################################## # Calls {Query#where} on the query. # @return {self} def where(*params, &proc) @query.where(*params, &proc) return self end # Calls {Query.limit} on the query # @return {self} def limit(n) @query.limit = n return self end # Calls {Query.offset} # @return {self} def offset(n) @query.offset = n return self end # Calls {Query.page} on the Query # @return {self} def page(page, rows) @query.page(page, rows) self end # @return [Fixnum|nil] Total number of available pages for current query (or nil if no limit is set) def pages return nil unless @query.limit (self.total_rows.to_f / @query.limit).ceil end # def unit_of_work # return Spider::Model.unit_of_work # end # Performs a deep copy # @return [QuerySet] def clone c = self.class.new(self.model, self.query.clone) c.autoload = self.autoload? c_objects = c.instance_variable_get(:@objects) @objects.each do |o| c_objects << o.clone end return c end end end; end