Class: Spider::Model::Mapper Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/spiderfw/model/mappers/mapper.rb

Overview

This class is abstract.

The Mapper connects a BaseModel to a Storage; it fetches data from the Storage and converts it to objects, and vice versa.

Each model has one instance of the mapper, retrieved by BaseModel.mapper. The mapper has a pointer to its model, and one to a Storage instance, which is shared between all models accessing the same storage.

The BaseModel provides methods for interacting with the mapper; it is not usually called directly, but it can be if needed (for example, to call the #delete_all! method, which is not exposed on the model).

Its methods may be overridden with BaseModel.with_mapper.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, storage) ⇒ Mapper

Takes a BaseModel class and a storage instance.

Parameters:



33
34
35
36
37
38
39
# File 'lib/spiderfw/model/mappers/mapper.rb', line 33

def initialize(model, storage)
    @model = model
    @storage = storage
    @options = {}
    @no_map_elements = {}
    @sequences = []
end

Instance Attribute Details

#modelBaseModel (readonly)

Returns pointer to the model instance.

Returns:

  • (BaseModel)

    pointer to the model instance



17
18
19
# File 'lib/spiderfw/model/mappers/mapper.rb', line 17

def model
  @model
end

#storageStorage

Returns pointer to the Storage instance.

Returns:

  • (Storage)

    pointer to the Storage instance



19
20
21
# File 'lib/spiderfw/model/mappers/mapper.rb', line 19

def storage
  @storage
end

#typeSymbol (readonly)

A Symbolic name for the Mapper’s subclass

Returns:



22
23
24
# File 'lib/spiderfw/model/mappers/mapper.rb', line 22

def type
  @type
end

Class Method Details

.write?Boolean

Returns whether this Mapper can write to the storage. return [true]

Returns:

  • (Boolean)


26
27
28
# File 'lib/spiderfw/model/mappers/mapper.rb', line 26

def self.write?
    true
end

Instance Method Details

#after_delete(objects) ⇒ void

This method returns an undefined value.

Hook to provide custom preprocessing. Will be passed a QuerySet. The default implementation does nothing.

If needed, override using BaseModel.with_mapper

Parameters:



271
272
# File 'lib/spiderfw/model/mappers/mapper.rb', line 271

def after_delete(objects)
end

#after_save(obj, mode) ⇒ void

This method returns an undefined value.

Called after a succesful save. ‘mode’ can be :insert or :update.

If needed, override using BaseModel.with_mapper; but make sure to call super, since this method’s implementation is not empty. Otherwise, override #save_done

Parameters:



252
253
254
255
# File 'lib/spiderfw/model/mappers/mapper.rb', line 252

def after_save(obj, mode)
    obj.reset_modified_elements
    save_associations(obj, mode)
end

#association_elementsArray

Elements that are associated to this one externally.

Returns:

  • (Array)

    An Array of elements for which the storage does not hold keys (see #have_references?), and which must be associated through other ways



333
334
335
336
337
338
339
# File 'lib/spiderfw/model/mappers/mapper.rb', line 333

def association_elements
    return [] if Spider::Model.unit_of_work_running?
    els = @model.elements_array.select{ |el| 
        mapped?(el) && !el.integrated? && !have_references?(el) && !(el.attributes[:added_reverse] && el.type <= @model)
    }
    els
end

#base_type(type) ⇒ Class

Returns the base type corresponding to a type; see Spider::Model.base_type

Returns:

  • (Class)

    the base type corresponding to type



72
73
74
# File 'lib/spiderfw/model/mappers/mapper.rb', line 72

def base_type(type)
    Spider::Model.base_type(type)
end

#basic_preprocess(condition) ⇒ Object

This handles integrated elements, junctions, and prepares types



1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
# File 'lib/spiderfw/model/mappers/mapper.rb', line 1130

def basic_preprocess(condition) # :nodoc:
    condition.conditions_array.each do |k, v, c|
        next if k.is_a?(Spider::QueryFuncs::Function)
        next unless element = model.elements[k]
        changed_v = false
        if element.type < Spider::DataType && !v.is_a?(element.type) && element.type.force_wrap?
            begin
                v = element.type.from_value(v)
                changed_v = true
            rescue TypeError => exc
                raise TypeError, "Can't convert #{v} to #{element.type} for element #{k} (#{exc.message})"
            end
        elsif [DateTime, Date].include?(element.type) && v && !v.is_a?(Date) && !v.is_a?(Time)
            v = element.type.parse(v)
            changed_v = true
        elsif element.model? && v.is_a?(Spider::Model::Condition)
            unless v.primary_keys_only?(element.model)
                v = element.mapper.preprocess_condition(v)
                changed_v = true
            end
        end
        if element.integrated?
            condition.delete(k)
            integrated_from = element.integrated_from
            integrated_from_element = element.integrated_from_element
            sub = condition.get_deep_obj
            sub.set(integrated_from_element, c, v)
            unless sub.primary_keys_only?(integrated_from.model)
                sub = integrated_from.model.mapper.preprocess_condition(sub) 
            end
            condition[integrated_from.name] = sub
        elsif element.junction? && !v.is_a?(BaseModel) && !v.is_a?(Hash) && !v.nil? # conditions on junction id don't make sense
            condition.delete(k)
            sub = condition.get_deep_obj
            sub.set(element.attributes[:junction_their_element], c, v)
            condition[k] = element.model.mapper.preprocess_condition(sub)
        elsif changed_v
            condition.delete(k)
            condition.set(k, c, v)
        end
    end
    condition
end

#before_delete(objects) ⇒ void

This method returns an undefined value.

Hook to provide custom preprocessing. Will be passed a QuerySet. The default implementation does nothing.

If needed, override using BaseModel.with_mapper

Parameters:



241
242
# File 'lib/spiderfw/model/mappers/mapper.rb', line 241

def before_delete(objects)
end

#before_insert(obj) ⇒ void

This method returns an undefined value.

Hook to provide custom preprocessing. The default implementation does nothing.

If needed, override using BaseModel.with_mapper

Parameters:



225
226
# File 'lib/spiderfw/model/mappers/mapper.rb', line 225

def before_insert(obj)
end

#before_save(obj, mode) ⇒ void

This method returns an undefined value.

This method is called before a save operation, normalizing and preparing the object. ‘mode’ can be :insert or :update. This method is well suited for being overridden (with BaseModel.with_mapper, to add custom preprocessing of the object; just remember to call super, or use #before_insert and #before_update instead.

Parameters:



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
# File 'lib/spiderfw/model/mappers/mapper.rb', line 150

def before_save(obj, mode)
    obj.trigger(:before_save, mode)
    normalize(obj)
    if (mode == :insert)
        before_insert(obj)
    elsif (mode == :update)
        before_update(obj)
    end
    @model.elements_array.each do |el|
        if (el.attributes[:set_before_save])
            set_data = el.attributes[:set_before_save]
            if (el.model? && set_data.is_a?(Hash))
                if (obj.element_has_value?(el))
                    set_data.each{ |k, v| obj.get(el).set(k, v) }
                else
                    obj.set(el, el.model.new(set_data))
                end 
            else
                obj.set(el, set_data)
            end
        end
        if !el.integrated? && el.required? && (mode == :insert || obj.element_modified?(el)) && !obj.element_has_value?(el)
            raise RequiredError.new(el)
        end
        if el.unique? && !el.integrated? && obj.element_modified?(el) && curr_val = obj.get(el)
            existent = @model.where(el.name => curr_val)
            if (mode == :insert && existent.length > 0) || (mode == :update && existent.length > 1)
                raise NotUniqueError.new(el)
            end
        end
        if mode == :insert && !el.integrated?
            obj.set(el.name, el.type.auto_value) if el.type < Spider::DataType && el.type.auto?(el) && !obj.element_has_value?(el)
            obj.set(el, obj.get(el)) if el.attributes[:default] && !obj.element_modified?(el)
        end
    end
    done_extended = []
    unless Spider::Model.unit_of_work_running?
        save_extended_models(obj, mode)
        save_integrated(obj, mode)
    end
end

#before_update(obj) ⇒ void

This method returns an undefined value.

Hook to provide custom preprocessing. The default implementation does nothing.

If needed, override using BaseModel.with_mapper

Parameters:



233
234
# File 'lib/spiderfw/model/mappers/mapper.rb', line 233

def before_update(obj)
end

#bulk_update(values, conditon) ⇒ nil

This method is abstract.

Executes a mass update for given condition.

Parameters:

Returns:

  • (nil)


509
510
# File 'lib/spiderfw/model/mappers/mapper.rb', line 509

def bulk_update(values, conditon)
end

#children_for_unit_of_work(obj, action) ⇒ Array

Returns Objects to be added to the UnitOfWork when obj is added.

Parameters:

Returns:

  • (Array)

    Objects to be added to the UnitOfWork when obj is added



859
860
861
862
863
864
865
866
867
868
869
870
# File 'lib/spiderfw/model/mappers/mapper.rb', line 859

def children_for_unit_of_work(obj, action)
    children = []
    obj.class.elements_array.each do |el|
        next unless obj.element_has_value?(el)
        next unless el.model?
        next unless obj.element_modified?(el)
        val = obj.get(el)
        next unless val.modified?
        children << val
    end
    children
end

#count(condition) ⇒ Fixnum

Does a count query on the storage for given condition

Parameters:

Returns:

  • (Fixnum)


725
726
727
728
729
# File 'lib/spiderfw/model/mappers/mapper.rb', line 725

def count(condition)
    query = Query.new(condition)
    result = fetch(query)
    return result.length
end

#delete(obj_or_condition, force = false, options = {}) ⇒ void

This method returns an undefined value.

Deletes an object, or objects according to a condition. Will not delete with null condition (i.e. all objects) unless force is true

Parameters:

  • obj_or_condition (BaseModel|Condition)
  • force (bool) (defaults to: false)
  • options (Hash) (defaults to: {})

    Available options:

    • :keep_single_reverse: don’t delete associations that have a single reverse. Useful when an object will be re-inserted with the same keys.



521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
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
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
# File 'lib/spiderfw/model/mappers/mapper.rb', line 521

def delete(obj_or_condition, force=false, options={})

    def prepare_delete_condition(obj)
        condition = Condition.and
        @model.primary_keys.each do |key|
            condition[key.name] = map_condition_value(key.type, obj.get(key))
        end
        return condition
    end
    
    curr = nil
    if (obj_or_condition.is_a?(BaseModel))
        condition = prepare_delete_condition(obj_or_condition)
        curr = QuerySet.new(@model, obj_or_condition)
    elsif (obj_or_condition.is_a?(QuerySet))
        qs = obj_or_condition
        condition = Condition.or
        qs.each{ |obj| condition << prepare_delete_condition(obj) }
    else
        condition = obj_or_condition.is_a?(Condition) ? obj_or_condition : Condition.new(obj_or_condition)
    end
    Spider::Logger.debug("Deleting with condition:")
    Spider::Logger.debug(condition)
    preprocess_condition(condition)
    cascade = @model.elements_array.select{ |el| !el.integrated? && el.attributes[:delete_cascade] }
    assocs = association_elements.select do |el|
        !el.junction? && # done later from @model.referenced_by_junctions
        (!storage.supports?(:delete_cascade) || !schema.cascade?(el.name)) # TODO: implement
    end
    curr = @model.where(condition) unless curr
    before_delete(curr)
    vals = []
    started_transaction = false
    begin
        unless cascade.empty? && assocs.empty?
            storage.in_transaction
            started_transaction = true
            curr.each do |curr_obj|
                obj_vals = {}
                cascade.each do |el|
                    obj_vals[el] = curr_obj.get(el)
                end
                vals << obj_vals
                assocs.each do |el|
                    next if el.has_single_reverse? && options[:keep_single_reverse]
                    delete_element_associations(curr_obj, el)
                end
            end
        end
        @model.referenced_by_junctions.each do |junction, element|
            curr.each do |curr_obj|
                junction_condition = Spider::Model::Condition.new
                junction_condition[element] = curr_obj
                junction.mapper.delete(junction_condition)
            end
        end
        do_delete(condition, force)
        vals.each do |obj_vals|
            obj_vals.each do |el, val|
                el.model.mapper.delete(val)
            end
        end
        after_delete(curr)
        storage.commit_or_continue if started_transaction
    rescue
        storage.rollback_or_continue if started_transaction
        raise
    end
end

#delete_all!void

This method returns an undefined value.

Deletes all objects from the storage.



593
594
595
596
597
# File 'lib/spiderfw/model/mappers/mapper.rb', line 593

def delete_all!
    all = @model.all
    #all.fetch_window = 100
    delete(all, true)
end

#delete_element_associations(obj, element, associated = nil) ⇒ void

This method returns an undefined value.

Deletes all associations from the given object to the element.

Parameters:



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
# File 'lib/spiderfw/model/mappers/mapper.rb', line 354

def delete_element_associations(obj, element, associated=nil)
    if element.attributes[:junction]
        condition = {element.attributes[:reverse] => obj.primary_keys}
        condition[element.attributes[:junction_their_element]] = associated if associated
        element.mapper.delete(condition)
    else
        if element.multiple?
            condition = Condition.and
            if associated
                condition = associated.keys_to_condition
            else
                condition[element.reverse] = obj
            end
            # associated.each do |child|
            #     condition_row = Condition.or
            #     element.model.primary_keys.each{ |el| condition_row.set(el.name, '<>', child.get(el))}
            #     condition << condition_row
            # end
            if element.owned? || (element.reverse && element.model.elements[element.reverse].primary_key?)
                element.mapper.delete(condition)
            else
                element.mapper.bulk_update({element.reverse => nil}, condition)
            end
        end
    end
end

#determine_save_mode(obj) ⇒ Symbol

Determines whether the object needs to be inserted or updated.

Parameters:

Returns:

  • (Symbol)

    :insert or :update



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/spiderfw/model/mappers/mapper.rb', line 309

def determine_save_mode(obj)
    if @model.extended_models && !@model.extended_models.empty?
        is_insert = false
        # Load local primary keys if they exist
        
        @model.elements_array.select{ |el| el.attributes[:local_pk] }.each do |local_pk|
            if !obj.get(local_pk)
                is_insert = true
                break
            end
        end
    end
    save_mode = nil
    if obj.class.auto_primary_keys? && !obj._check_if_saved
        save_mode = (!is_insert && obj.primary_keys_set?) ? :update : :insert
    else
        save_mode = obj.in_storage? ? :update : :insert
    end
end

#execute_action(action, object, params = {}) ⇒ void

This method returns an undefined value.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/spiderfw/model/mappers/mapper.rb', line 83

def execute_action(action, object, params={})
    case action
    when :save
        if params[:force] == :insert
            insert(object)
        elsif params[:force] == :update
            update(object)
        else
            save(object)
        end
    when :keys
        # do nothing; keys will be set by save
    when :delete
        delete(object)
    else
        raise MapperError, "#{action} action not implemented"
    end
end

#find(query, query_set = nil, options = {}) ⇒ QuerySet

Finds objects according to a query, merging the results into a query_set if given.

Parameters:

  • query (Query)
  • query_set (QuerySet) (defaults to: nil)

    QuerySet to merge results into, if given

  • options (Hash) (defaults to: {})

    Options can be:

    • :no_expand_request: don’t expand request using lazy loading groups

Returns:



645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
# File 'lib/spiderfw/model/mappers/mapper.rb', line 645

def find(query, query_set=nil, options={})
    set = nil
    Spider::Model.with_identity_mapper do |im|
        im.put(query_set)
        query_set.update_loaded_elements if query_set
        set = query_set || QuerySet.new(@model)
        was_loaded = set.loaded
        set.loaded = true
        set.index_by(*@model.primary_keys)
        set.last_query = query
        if (query.request.with_superclass? && @model.superclass < BaseModel)
            return find_with_superclass(query, set, options)
        end
        
        if (@model.attributes[:condition])
            query.condition = Condition.and(query.condition, @model.attributes[:condition])
        end
        keys_loaded = true
        @model.primary_keys.each do |key|
            unless set.element_loaded?(key)
                keys_loaded = false
                break
            end
        end
        do_fetch = true
        if (keys_loaded)
            do_fetch = false
            query.request.each_key do |key|
                if (have_references?(key))
                    do_fetch = true
                    break
                end
            end
        end
        if (do_fetch)
            @model.primary_keys.each{ |key| query.request[key] = true}
            expand_request(query.request, set) unless options[:no_expand_request] || !query.request.expandable?
            query = prepare_query(query, query_set)
            result = fetch(query)
            if !result || result.empty?
                set.each_current do |obj|
                    query.request.keys.each do |element_name|
                        el = @model.elements[element_name]
                        next if el.primary_key?
                        next if el.integrated? || @model.extended_models[el.model]
                        obj.set_loaded_value(element_name, nil) 
                    end
                end
                return false
            end
            set.total_rows = result.total_rows if (!was_loaded)
            merged = {}
            result.each do |row|
                obj =  map(query.request, row, @model) # set.model ?!?
                next unless obj
                merged_obj = merge_object(set, obj, query.request)
                merged[merged_obj.object_id] = true
            end
            query.request.keys.each do |k, v|
                 # k may be a SelectFunction
                set.element_loaded(k) if !k.is_a?(QueryFuncs::SelectFunction) && have_references?(k)
            end
            set.each_current do |obj|
                next if merged[obj.object_id]
                query.request.keys.each do |element_name|
                    el = @model.elements[element_name]
                    next if el.primary_key?
                    next if el.integrated? || @model.extended_models[el.model]
                    obj.set_loaded_value(element_name, nil) 
                end
            end
        end
        set = get_external(set, query)
    end
    return set
end

#get_dependencies(task) ⇒ Array

This method is abstract.

Returns task dependecies for the UnitOfWork. May be implemented by subclasses.

Parameters:

Returns:

  • (Array)

    Dependencies for the task



852
853
854
# File 'lib/spiderfw/model/mappers/mapper.rb', line 852

def get_dependencies(task)
    return []
end

#have_references?(element) ⇒ bool

This method is abstract.

Returns true if information to find the given element is accessible to the mapper (see DbMapper#have_references? for an implementation)

Parameters:

Returns:

  • (bool)

    True if the storage has a field to write the element or a reference to the element (primary keys), false otherwise

Raises:



133
134
135
# File 'lib/spiderfw/model/mappers/mapper.rb', line 133

def have_references?(element)
    raise MapperError, "Unimplemented"
end

#insert(obj) ⇒ void

This method returns an undefined value.

Inserts the object in the storage.

Parameters:



471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/spiderfw/model/mappers/mapper.rb', line 471

def insert(obj)
    prev_autoload = obj.save_mode()
    storage.in_transaction
    begin
        before_save(obj, :insert)
        do_insert(obj)
        after_save(obj, :insert)
        storage.commit_or_continue
    rescue
        storage.rollback_or_continue
        raise
    end
    obj.autoload = prev_autoload
end

#load(objects, query, options = {}) ⇒ QuerySet

Loads elements of given objects according to query.request.

See also #find

Parameters:

  • objects (QuerySet)

    Objects to expand

  • query (Query)
  • options (Hash) (defaults to: {})

Returns:



628
629
630
631
632
633
634
635
636
# File 'lib/spiderfw/model/mappers/mapper.rb', line 628

def load(objects, query, options={})
    objects = queryset_siblings(objects) unless objects.is_a?(QuerySet)
    request = query.request
    condition = Condition.or
    objects.each_current do |obj|
        condition << obj.keys_to_condition if obj.primary_keys_set?
    end
    return find(Query.new(condition, request), objects, options)
end

#load_element(objects, element) ⇒ QuerySet

Loads an element. Other elements may be loaded as well, according to lazy groups.

Parameters:

  • objects (QuerySet)

    Objects for which to load given element

  • element (Element)

Returns:



609
610
611
# File 'lib/spiderfw/model/mappers/mapper.rb', line 609

def load_element(objects, element)
    load(objects, Query.new(nil, [element.name]))
end

#load_element!(objects, element) ⇒ QuerySet

Loads only the given element, ignoring lazy groups.

Parameters:

  • objects (QuerySet)

    Objects for which to load given element

  • element (Element)

Returns:



617
618
619
# File 'lib/spiderfw/model/mappers/mapper.rb', line 617

def load_element!(objects, element)
    load(objects, Query.new(nil, [element.name]), :no_expand_request => true)
end

#map_back_value(type, value) ⇒ Object

Converts a storage value back to the corresponding base type or DataType.

Parameters:

Returns:



820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
# File 'lib/spiderfw/model/mappers/mapper.rb', line 820

def map_back_value(type, value)
    value = value[0] if value.class == Array
    value = storage_value_to_mapper(Model.simplify_type(type), value)

    if type <= Spider::DataTypes::PK
        value = value.is_a?(Spider::DataTypes::PK) ? value.obj : value
    elsif type < Spider::DataType && type.maps_back_to
        type = type.maps_back_to
    end
    case type.name
    when 'Fixnum'
        return value ? value.to_i : nil
    when 'Float'
        return value ? value.to_f : nil
    end
    return nil unless value
    case type.name
    when 'Date', 'DateTime'
        return type.parse(value) unless value.is_a?(Date)
    end
    if type < Spider::DataType && type.force_wrap?
        value = type.from_value(value)
    end
    return value
end

#map_condition_value(type, value) ⇒ Object

Prepares a value for a condition.

Parameters:

  • type (Class)

    Value’s type

  • value (Object)
  • save_mode (Symbol)

    :insert, :update, or generically :save

Returns:



779
780
781
782
783
784
785
786
# File 'lib/spiderfw/model/mappers/mapper.rb', line 779

def map_condition_value(type, value)
    if value.is_a?(Range)
        return Range.new(map_condition_value(type, value.first), map_condition_value(type, value.last))
    end
    return value if ( type.class == Class && type.subclass_of?(Spider::Model::BaseModel) )
    value = map_value(type, value, :condition)
    return @storage.value_for_condition(Model.simplify_type(type), value)
end

#map_save_value(type, value, save_mode = :save) ⇒ Object

Prepares a value going to be bound to an insert or update statement

Parameters:

  • type (Class)

    Value’s type

  • value (Object)
  • save_mode (Symbol) (defaults to: :save)

    :insert, :update, or generically :save

Returns:



769
770
771
772
# File 'lib/spiderfw/model/mappers/mapper.rb', line 769

def map_save_value(type, value, save_mode=:save)
    value = map_value(type, value, :save)
    return @storage.value_for_save(Model.simplify_type(type), value, save_mode)
end

#map_value(type, value, mode = nil) ⇒ Object

Converts a value into one that is accepted by the storage.

Parameters:

  • type (Class)

    Value’s type

  • value (Object)
  • save_mode (Symbol)

    :insert, :update, or generically :save

Returns:



802
803
804
805
806
807
808
809
810
811
812
813
# File 'lib/spiderfw/model/mappers/mapper.rb', line 802

def map_value(type, value, mode=nil)
    return value if value.nil?
    if type == Spider::DataTypes::PK
        value = value.obj if value.is_a?(Spider::DataTypes::PK)
    elsif type < Spider::DataType
        value = type.from_value(value) unless value.is_a?(type)
        value = value.map(self.type)
    elsif type.class == Class && type.subclass_of?(Spider::Model::BaseModel)
        value = type.primary_keys.map{ |key| value.send(key.name) }
    end
    value
end

#mapped?(element) ⇒ bool

Returns whether the given element can be handled by the mapper.

Returns:

  • (bool)


53
54
55
56
57
58
59
60
# File 'lib/spiderfw/model/mappers/mapper.rb', line 53

def mapped?(element)
    element = element.name if (element.is_a? Element)
    element = @model.elements[element]
    return false if (element.attributes[:unmapped])
    return false if (element.attributes[:computed_from])
    return false if @no_map_elements[element.name]
    return true
end

#no_map(*els) ⇒ void

This method returns an undefined value.

Tells to the mapper that the given elements should not be handled.

Parameters:

  • Elements (*Element)

    which should not be mapped



47
48
49
# File 'lib/spiderfw/model/mappers/mapper.rb', line 47

def no_map(*els)
    els.each{ |el| @no_map_elements[el] = true }
end

#normalize(obj) ⇒ void

This method returns an undefined value.

Converts hashes and arrays inside an object to QuerySets and BaseModel instances.

Parameters:



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/spiderfw/model/mappers/mapper.rb', line 105

def normalize(obj)
    obj.no_autoload do
        @model.elements.select{ |n, el| 
                mapped?(el) &&  el.model? && obj.element_has_value?(el) 
        }.each do |name, element|
            val = obj.get(name)
            next if (val.is_a?(BaseModel) || val.is_a?(QuerySet))
            if (val.is_a? Array)
                val.each_index { |i| val[i] = Spider::Model.get(element.model, val[i]) unless val[i].is_a?(BaseModel) || val.is_a?(QuerySet) }
                obj.set(name, QuerySet.new(element.model, val))
            else
                val = Spider::Model.get(element.model, val)
                obj.set(name, val)
            end
        end
    end
end

#prepare_delete_condition(obj) ⇒ Object



523
524
525
526
527
528
529
# File 'lib/spiderfw/model/mappers/mapper.rb', line 523

def prepare_delete_condition(obj)
    condition = Condition.and
    @model.primary_keys.each do |key|
        condition[key.name] = map_condition_value(key.type, obj.get(key))
    end
    return condition
end

#queryset_siblings(obj) ⇒ QuerySet

Returns the siblings, if any, of the object, in its ancestor QuerySet.

Siblings are objects in the same branch of the object tree.

This method is used to load related data, avoiding N+1 queries

Parameters:

Returns:



740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
# File 'lib/spiderfw/model/mappers/mapper.rb', line 740

def queryset_siblings(obj)
    return QuerySet.new(@model, obj) unless obj._parent
    orig_obj = obj
    path = []
    seen = {obj => true}
    while (obj._parent && !seen[obj._parent])
        path.unshift(obj._parent_element) if (obj._parent_element) # otherwise it's a query set
        obj = obj._parent
        seen[obj] = true
    end
    res = path.empty? ? obj : obj.all_children(path)
    if obj && !path.empty? &&  res.length < 1
        if Spider.runmode == 'production'
            Spider.logger.error("Internal error: broken object path")
            res = [orig_obj]
        else
            raise RuntimeError, "Internal error: broken object path"
        end
    end
    res = QuerySet.new(@model, res) unless res.is_a?(QuerySet)
    res = res.select{ |obj| obj.primary_keys_set? }
    return res
end

#save(obj, request = nil) ⇒ true

Saves the object to the storage.

Parameters:

Returns:

  • (true)


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
# File 'lib/spiderfw/model/mappers/mapper.rb', line 278

def save(obj, request=nil)
    prev_autoload = obj.autoload?
    obj.save_mode
    storage.in_transaction
    begin
        save_mode = determine_save_mode(obj)
        before_save(obj, save_mode)
        if save_mode == :update
            do_update(obj)
        else
            do_insert(obj)
        end
        after_save(obj, save_mode)
        storage.commit_or_continue
    rescue
        storage.rollback_or_continue
        raise
    end
    obj.autoload = prev_autoload
    unless @doing_save_done
        @doing_save_done = true
        save_done(obj, save_mode) 
    end
    @doing_save_done = false
    obj.trigger(:saved, save_mode)
    true
end

#save_all(root) ⇒ void

This method returns an undefined value.

Saves the given object and all objects reachable from it.

Parameters:



461
462
463
464
465
466
# File 'lib/spiderfw/model/mappers/mapper.rb', line 461

def save_all(root)
    UnitOfWork.new do |uow|
        uow.add(root)
        uow.run()
    end
end

#save_associations(obj, mode) ⇒ void

This method returns an undefined value.

Saves externally associated objects (the ones corresponding to elements returned by #association_elements)



343
344
345
346
347
# File 'lib/spiderfw/model/mappers/mapper.rb', line 343

def save_associations(obj, mode)
    association_elements.select{ |el| obj.element_has_value?(el) }.each do |el|
        save_element_associations(obj, el, mode) # if obj.element_modified?(el)
    end
end

#save_done(obj, mode) ⇒ void

This method returns an undefined value.

Hook called after a succesful save, when the object is not in save mode (see BaseModel#save_mode) anymore.

If needed, override using BaseModel.with_mapper

Parameters:



263
264
# File 'lib/spiderfw/model/mappers/mapper.rb', line 263

def save_done(obj, mode)
end

#save_element_associations(obj, element, mode) ⇒ void

This method returns an undefined value.

Saves the associations from the given object to the element.

Parameters:



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
455
456
# File 'lib/spiderfw/model/mappers/mapper.rb', line 386

def save_element_associations(obj, element, mode)
    our_element = element.attributes[:reverse]
    val = obj.get(element)
    return if !element.multiple? && val.saving?
    if element.attributes[:junction]
        their_element = element.attributes[:junction_their_element]
        if val.model != element.model # dereferenced junction
            val = [val] unless val.is_a?(Enumerable)
            unless mode == :insert
                current = obj.get_new
                current_val = current.get(element)
                current_val = [current_val] unless current_val.is_a?(Enumerable)
                condition = Condition.and
                val_condition = Condition.or
                current_val.each do |row|

                    next if val.include?(row)
                    val_condition[their_element] = row
                end
                condition << val_condition
                unless condition.empty?
                    condition[our_element] = obj
                    element.model.mapper.delete(condition)
                end
            end
            val.each do |row|
                next if current_val && current_val.include?(row)
                junction = element.model.new({ our_element => obj, their_element => row })
                junction.mapper.insert(junction)
            end                    
        else
            unless mode == :insert
                condition = Condition.and
                condition[our_element] = obj
                if element.attributes[:junction_id]
                    val.each do |row|
                        next unless row_id = row.get(element.attributes[:junction_id])
                        condition.set(element.attributes[:junction_id], '<>', row_id)
                    end
                end
                element.model.mapper.delete(condition)
            end
            val.set(our_element, obj)
            if element.attributes[:junction_id]
                val.save!
            else
                val.insert
            end
        end
    else
        if element.multiple?
            condition = Condition.and
            condition[our_element] = obj
            val.each do |row|
                condition_row = Condition.or
                element.model.primary_keys.each{ |el| condition_row.set(el.name, '<>', row.get(el))}
                condition << condition_row
            end
            if element.owned?
                element.mapper.delete(condition)
            else
                element.mapper.bulk_update({our_element => nil}, condition)
            end
        end
        val = [val] unless val.is_a?(Enumerable) # one to one relationships
        val.each do |v|
             v.set(our_element, obj)
             v.mapper.save(v)
        end
    end
end

#save_extended_models(obj, mode) ⇒ void

This method returns an undefined value.

Saves models that obj’s model extends (see BaseModel.extend_model)

Parameters:



196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/spiderfw/model/mappers/mapper.rb', line 196

def save_extended_models(obj, mode)
    if @model.extended_models
        @model.extended_models.each do |m, el|
            sub = obj.get(el)
            if mode == :update || sub.class.auto_primary_keys? || sub._check_if_saved
                sub.save if (obj.element_modified?(el) || !obj.primary_keys_set?) && sub.mapper.class.write?
            else
                sub.insert unless sub.in_storage?
            end
        end
    end
end

#save_integrated(obj, mode) ⇒ void

This method returns an undefined value.

Saves objects integrated in obj (see BaseModel.integrate)

Parameters:



213
214
215
216
217
218
# File 'lib/spiderfw/model/mappers/mapper.rb', line 213

def save_integrated(obj, mode)
    @model.elements_array.select{ |el| !el.integrated? && el.attributes[:integrated_model] && !el.attributes[:extended_model] }.each do |el|
        sub_obj = obj.get(el)
        sub_obj.save if sub_obj && sub_obj.modified? && obj.element_modified?(el) && obj.get(el).mapper.class.write?
    end
end

#set_pks_condition(condition, el, val, prefix) ⇒ Object

Utility function to set conditions on



1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
# File 'lib/spiderfw/model/mappers/mapper.rb', line 1189

def set_pks_condition(condition, el, val, prefix) # :nodoc:
    el.model.primary_keys.each do |primary_key|
        new_prefix = "#{prefix}.#{primary_key.name}"
        if (primary_key.model?)
            if (primary_key.model.primary_keys.length == 1)
                # FIXME: this should not be needed, see below
                condition.set(new_prefix, '=', val.get(primary_key).get(primary_key.model.primary_keys[0]))
            else
                # FIXME! does not work, the subcondition does not get processed
                raise "Subconditions on multiple key elements not supported yet"
                subcond = Condition.new
                set_pks_condition(subcond,  primary_key, val.get(primary_key), new_prefix)
                condition << subcond
            end
        else
            condition.set(new_prefix, '=', val.get(primary_key))
        end
    end
end

#sortable?(element) ⇒ bool

Returns True if the mapper can sort by this element.

Parameters:

Returns:

  • (bool)

    True if the mapper can sort by this element



64
65
66
67
68
# File 'lib/spiderfw/model/mappers/mapper.rb', line 64

def sortable?(element)
    element = element.name if (element.is_a? Element)
    element = @model.elements[element]
    mapped?(element) || element.attributes[:sortable]
end

#storage_value_to_mapper(type, value) ⇒ Object

Calls Storage#value_to_mapper. It is repeated in Mapper for easier overriding.

Parameters:

Returns:



792
793
794
# File 'lib/spiderfw/model/mappers/mapper.rb', line 792

def storage_value_to_mapper(type, value)
    storage.value_to_mapper(type, value)
end

#update(obj) ⇒ void

This method returns an undefined value.

Updates the object in the storage.

Parameters:



489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/spiderfw/model/mappers/mapper.rb', line 489

def update(obj)
    prev_autoload = obj.save_mode()
    storage.in_transaction
    begin
        before_save(obj, :update)
        do_update(obj)
        after_save(obj, :update)
        storage.commit_or_continue
    rescue
        storage.rollback_or_continue
        raise
    end
    obj.autoload = prev_autoload
end