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:



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

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
256
# 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



329
330
331
332
333
334
335
# File 'lib/spiderfw/model/mappers/mapper.rb', line 329

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



1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
# File 'lib/spiderfw/model/mappers/mapper.rb', line 1104

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 element.type == DateTime && v && !v.is_a?(Date) && !v.is_a?(Time)
            v = DateTime.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)


495
496
# File 'lib/spiderfw/model/mappers/mapper.rb', line 495

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



833
834
835
836
837
838
839
840
841
842
843
844
# File 'lib/spiderfw/model/mappers/mapper.rb', line 833

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)


706
707
708
709
710
# File 'lib/spiderfw/model/mappers/mapper.rb', line 706

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.



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

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
    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
end

#delete_all!void

This method returns an undefined value.

Deletes all objects from the storage.



574
575
576
577
578
# File 'lib/spiderfw/model/mappers/mapper.rb', line 574

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:



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/spiderfw/model/mappers/mapper.rb', line 350

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



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/spiderfw/model/mappers/mapper.rb', line 305

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:



626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
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
# File 'lib/spiderfw/model/mappers/mapper.rb', line 626

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



826
827
828
# File 'lib/spiderfw/model/mappers/mapper.rb', line 826

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:



467
468
469
470
471
472
473
474
475
# File 'lib/spiderfw/model/mappers/mapper.rb', line 467

def insert(obj)
    prev_autoload = obj.save_mode()
    storage.in_transaction
    before_save(obj, :insert)
    do_insert(obj)
    after_save(obj, :insert)
    storage.commit_or_continue
    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:



609
610
611
612
613
614
615
616
617
# File 'lib/spiderfw/model/mappers/mapper.rb', line 609

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:



590
591
592
# File 'lib/spiderfw/model/mappers/mapper.rb', line 590

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:



598
599
600
# File 'lib/spiderfw/model/mappers/mapper.rb', line 598

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:



794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
# File 'lib/spiderfw/model/mappers/mapper.rb', line 794

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:



753
754
755
756
757
758
759
760
# File 'lib/spiderfw/model/mappers/mapper.rb', line 753

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:



743
744
745
746
# File 'lib/spiderfw/model/mappers/mapper.rb', line 743

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:



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

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



509
510
511
512
513
514
515
# File 'lib/spiderfw/model/mappers/mapper.rb', line 509

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:

Raises:

  • (RuntimeError)


721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
# File 'lib/spiderfw/model/mappers/mapper.rb', line 721

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)
    raise RuntimeError, "Broken object path" if (obj && !path.empty? &&  res.length < 1)
    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)


279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/spiderfw/model/mappers/mapper.rb', line 279

def save(obj, request=nil)
    prev_autoload = obj.autoload?
    obj.save_mode
    storage.in_transaction
    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
    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:



457
458
459
460
461
462
# File 'lib/spiderfw/model/mappers/mapper.rb', line 457

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)



339
340
341
342
343
# File 'lib/spiderfw/model/mappers/mapper.rb', line 339

def save_associations(obj, mode)
    association_elements.select{ |el| obj.element_has_value?(el) }.each do |el|
        save_element_associations(obj, el, mode)
    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:



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

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:



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

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



1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
# File 'lib/spiderfw/model/mappers/mapper.rb', line 1163

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:



766
767
768
# File 'lib/spiderfw/model/mappers/mapper.rb', line 766

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:



480
481
482
483
484
485
486
487
488
# File 'lib/spiderfw/model/mappers/mapper.rb', line 480

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