Class: Spider::Model::BaseModel

Inherits:
Object
  • Object
show all
Includes:
DataTypes, EventSource, Logger, QueryFuncs
Defined in:
lib/spiderfw/model/base_model.rb

Direct Known Subclasses

InlineModel, Managed, ProxyModel

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from EventSource

included, #on, #trigger

Methods included from QueryFuncs

add_query_func, included

Methods included from Logger

add, close, close_all, datetime_format, datetime_format=, #debug, debug, debug?, #debug?, enquire_loggers, #error, error, #error?, error?, #fatal, fatal, #fatal?, fatal?, info, #info, info?, #info?, method_missing, open, reopen, send_to_loggers, unknown, #unknown, #warn, warn, warn?, #warn?

Constructor Details

#initialize(values = nil) ⇒ BaseModel

The constructor may take:

  • an Hash of values, that will be set on the new instance; or

  • a BaseModel instance; its values will be set on the new instance; or

  • a single value; it will be set on the first primary key.



1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
# File 'lib/spiderfw/model/base_model.rb', line 1186

def initialize(values=nil)
    @_autoload = true
    @_has_values = false
    @loaded_elements = {}
    @_modified_elements = {}
    @value_observers = nil
    @all_values_observers = nil
    @_extra = {}
    @model = self.class
    @_primary_keys_set = false
    set_values(values) if values
    # if primary_keys_set?
    #     @_primary_keys_set = true
    #     if Spider::Model.identity_mapper
    #         Spider::Model.identity_mapper.put(self, true)
    #     else
    #         Spider::Model.unit_of_work.add(self) if Spider::Model.unit_of_work
    #     end
    # else
    #     #nil
    # end
        
    
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

Tries the method on integrated models

Raises:

  • (NoMethodError)


2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
# File 'lib/spiderfw/model/base_model.rb', line 2193

def method_missing(method, *args) #:nodoc:
    # UNUSED
    # case method.to_s
    # when /load_by_(.+)/
    #     element = $1
    #     if !self.class.elements[element.to_sym].attributes[:primary_key]
    #         raise ModelException, "load_by_ called for element #{element} which is not a primary key"
    #     elsif self.class.primary_keys.length > 1
    #         raise ModelException, "can't call #{method} because #{element} is not the only primary key"
    #     end
    #     query = Query.new
    #     query.condition[element.to_sym] = args[0]
    #     load(query)
    # else
    if (self.class.attributes[:integrated_models])
        self.class.attributes[:integrated_models].each do |model, name|
            obj = send(name)
            if (obj.respond_to?(method))
                return obj.send(method, *args)
            end
        end
    end
    raise NoMethodError, "undefined method `#{method}' for #{self.class}"
    #super
    # end
end

Class Attribute Details

.attributes(val = nil) ⇒ Object (readonly)

Sets or gets class attributes (a Hash). If given a hash of attributes, will merge them with class attributes. Model attributes are generally empty, and can be used by apps.



83
84
85
# File 'lib/spiderfw/model/base_model.rb', line 83

def attributes
  @attributes
end

.elements_orderObject (readonly)

An array of element names, in definition order.



85
86
87
# File 'lib/spiderfw/model/base_model.rb', line 85

def elements_order
  @elements_order
end

.integrated_modelsObject (readonly)

An Hash of integrated models => corresponding integrated element name.



87
88
89
# File 'lib/spiderfw/model/base_model.rb', line 87

def integrated_models
  @integrated_models
end

.polymorphic_modelsObject (readonly)

An Hash of polymorphic models => polymorphic params



89
90
91
# File 'lib/spiderfw/model/base_model.rb', line 89

def polymorphic_models
  @polymorphic_models
end

.sequencesObject (readonly)

Model sequences.



91
92
93
# File 'lib/spiderfw/model/base_model.rb', line 91

def sequences
  @sequences
end

Instance Attribute Details

#_check_if_savedObject

Returns the value of attribute _check_if_saved.



71
72
73
# File 'lib/spiderfw/model/base_model.rb', line 71

def _check_if_saved
  @_check_if_saved
end

#_modified_elementsObject (readonly)

Elements that were modified since a load or a save (note: this only keeps track of changed references on the object; it doesn’t consider if a associated object or QuerySet has changed. Use element_modified? to get that information)



79
80
81
# File 'lib/spiderfw/model/base_model.rb', line 79

def _modified_elements
  @_modified_elements
end

#_no_identity_mapperObject

This object won’t be put into the identity mapper



76
77
78
# File 'lib/spiderfw/model/base_model.rb', line 76

def _no_identity_mapper
  @_no_identity_mapper
end

#_parentObject

Model instance or QuerySet containing the object



67
68
69
# File 'lib/spiderfw/model/base_model.rb', line 67

def _parent
  @_parent
end

#_parent_elementObject

If _parent is a model instance, which element points to this one



69
70
71
# File 'lib/spiderfw/model/base_model.rb', line 69

def _parent_element
  @_parent_element
end

#_subclass_objectObject

If this object is used as a superclass in class_table_inheritance, points to the current subclass



74
75
76
# File 'lib/spiderfw/model/base_model.rb', line 74

def _subclass_object
  @_subclass_object
end

#loaded_elementsObject (readonly)

An Hash of loaded elements



65
66
67
# File 'lib/spiderfw/model/base_model.rb', line 65

def loaded_elements
  @loaded_elements
end

#modelObject (readonly)

The BaseModel class itself. Used when dealing with proxy objects.



63
64
65
# File 'lib/spiderfw/model/base_model.rb', line 63

def model
  @model
end

Class Method Details

._added_elements(&proc) ⇒ Object

Does nothing. This method is to keep note of elements created in other models.



852
853
# File 'lib/spiderfw/model/base_model.rb', line 852

def self._added_elements(&proc)
end

.add_element(el) ⇒ Object



527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'lib/spiderfw/model/base_model.rb', line 527

def self.add_element(el)
    @elements ||= {}
    @elements[el.name] = el
    @elements_order ||= []
    if (el.attributes[:element_position])
        @elements_order.insert(el.attributes[:element_position], el.name)
    else
        @elements_order << el.name
    end            
    @primary_keys ||= []
    if el.attributes[:primary_key] && !@primary_keys.include?(el)
        @primary_keys << el
    end
end

.allObject

Returns a queryset without conditions



1118
1119
1120
# File 'lib/spiderfw/model/base_model.rb', line 1118

def self.all
    return self.where
end

.all_values_observersObject



1972
1973
1974
# File 'lib/spiderfw/model/base_model.rb', line 1972

def self.all_values_observers
    @all_values_observers ||= []
end

.appObject

Returns the parent Spider::App of the module



116
117
118
119
120
121
122
123
# File 'lib/spiderfw/model/base_model.rb', line 116

def self.app
    return @app if @app
    app = self
    while (!app.include?(Spider::App))
        app = app.parent_module
    end
    @app = app
end

.attribute(name, value) ⇒ Object

Sets a model attribute. See #self.attributes



835
836
837
838
# File 'lib/spiderfw/model/base_model.rb', line 835

def self.attribute(name, value)
    @attributes ||= {}
    @attributes[name] = value
end

.auto_primary_keys?Boolean

Returns:

  • (Boolean)


892
893
894
# File 'lib/spiderfw/model/base_model.rb', line 892

def self.auto_primary_keys?
    self.primary_keys.select{ |k| !k.autogenerated? }.empty?
end

.choice(name, type, attributes = {}, &proc) ⇒ Object

Defines an element with choice association. Shorthand for

element(name, type, :association => :choice, ...)


664
665
666
667
# File 'lib/spiderfw/model/base_model.rb', line 664

def self.choice(name, type, attributes={}, &proc)
    attributes[:association] = :choice
    element(name, type, attributes, &proc)
end

.class_table_inheritance(params = {}) ⇒ Object

Externalizes the superclass elements making the superclass an external integrated element. Parameters may be:

  • :name (symbol) name of the created element

  • :delete_cascade (bool) delete cascade the superclass instance. True by default.

  • :no_local_pk (bool) do not define an id for this class



780
781
782
# File 'lib/spiderfw/model/base_model.rb', line 780

def self.class_table_inheritance(params={})
    self.extend_model(superclass, params)
end

.condition(condition) ⇒ Object

Sets a fixed condition.



795
796
797
# File 'lib/spiderfw/model/base_model.rb', line 795

def self.condition(condition)
    self.attributes[:condition] = condition
end

.containing_moduleObject



896
897
898
899
900
901
902
# File 'lib/spiderfw/model/base_model.rb', line 896

def self.containing_module
    par = self.parent_module
    while par <= BaseModel
        par = par.parent_module
    end
    par
end

.count(condition = nil) ⇒ Object

Returns the number of objects in storage



1170
1171
1172
# File 'lib/spiderfw/model/base_model.rb', line 1170

def self.count(condition=nil)
    mapper.count(condition)
end

.create(values) ⇒ Object



1219
1220
1221
1222
1223
# File 'lib/spiderfw/model/base_model.rb', line 1219

def self.create(values)
    obj = self.static(values)
    obj.insert
    return obj
end

.create_inline_model(name, hash, attributes = {}) ⇒ Object

Creates an inline model



710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
# File 'lib/spiderfw/model/base_model.rb', line 710

def self.create_inline_model(name, hash, attributes={}) #:nodoc:
    model = self.const_set(Spider::Inflector.camelize(name), Class.new(InlineModel))
    model.instance_eval do
        if attributes[:inline_model]
            attributes[:inline_model].each do |el|
                element(el[0], el[1], el[2] || {})
            end
        else
            hash.each do |key, val|
                key = key.to_s if key.is_a?(Symbol)
                element(:id, key.class, :primary_key => true)
                if (val.class == Hash)
                    # TODO: allow passing of multiple values like {:element1 => 'el1', :element2 => 'el2'}
                else
                    element(:desc, val.class, :desc => true)
                end
                break
            end
        end
    end
    model.data = hash
    return model
end

.define_element_methods(name) ⇒ Object



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
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/spiderfw/model/base_model.rb', line 403

def self.define_element_methods(name)
    ivar = :"@#{ name }"

    unless self.const_defined?(:ElementMethods)
        em = self.const_set(:ElementMethods, Module.new)
        include em

    end
    element_methods = self.const_get(:ElementMethods)

    #instance variable getter
    element_methods.send(:define_method, name) do                
        element = self.class.elements[name]
        raise "Internal error! Element method #{name} exists, but element not found" unless element
        return element.attributes[:fixed] if element.attributes[:fixed]
        if (element.integrated?)
            integrated = get(element.integrated_from.name)
            return integrated.send(element.integrated_from_element) if integrated
            return nil
        end
        if element_has_value?(name) || element_loaded?(name)
            val = instance_variable_get(ivar)
            val.set_parent(self, name) if val && element.model? && !val._parent # FIXME!!!
            return val
        end

        #                Spider.logger.debug("Element not loaded #{name} (i'm #{self.class} #{self.object_id})")
        if autoload? && primary_keys_set?
            if (autoload? == :save_mode)
                mapper.load_element!(self, element)
            else
                mapper.load_element(self, element)
            end
            val = instance_variable_get(ivar)
        end
        if !val && element.model? && (element.multiple? || element.attributes[:extended_model])
            val = instance_variable_set(ivar, instantiate_element(name))
        end
        if !val && element.attributes[:default]
            if element.attributes[:default].is_a?(Proc)
                val = element.attributes[:default].call(self)
            else
                val = element.attributes[:default]
            end
            val = element.model.new(val) if element.model? && !val.is_a?(BaseModel)
        end
        val.set_parent(self, name) if element.model? && val && val.respond_to?(:parent) && !val._parent # FIXME!!!
        return val
    end

    alias_method :"#{name}?", name if self.elements[name].type <= Spider::DataTypes::Bool

    #instance_variable_setter
    element_methods.send(:define_method, "#{name}=") do |val|
        element = self.class.elements[name]
        raise "Internal error! Element method #{name}= exists, but element not found" unless element
        return if element.attributes[:fixed]
        was_loaded = element_loaded?(element)
        #@_autoload = false unless element.primary_key?
        if (element.integrated?)
            integrated_obj = get(element.integrated_from)
            unless integrated_obj
                integrated_obj = instantiate_element(element.integrated_from.name) 
                set(element.integrated_from, integrated_obj)
            end
            #integrated_obj.autoload = false
            begin
                res = integrated_obj.send("#{element.integrated_from_element}=", val)
            rescue IdentityMapperException
                set(element.integrated_from, Spider::Model.get(integrated_obj))
                get(element.integrated_from).merge!(integrated_obj)
            end
            if !element.primary_key? && integrated_obj.element_modified?(name)
                @_modified_elements[name] = true
            end
            return res
        end
        if (val && element.model?)
            if (element.multiple?)
                unless (val.is_a?(QuerySet))
                    qs = instantiate_element(name)
                    if (val.is_a?(Enumerable))
                        val.each do |row|
                            row = element.type.new(row) unless row.is_a?(BaseModel)
                            qs << row
                        end
                    else
                        qs << val
                    end
                    val = qs
                end
            else
                val = element.model.get(val) unless val.is_a?(BaseModel)
            end
        end
        val = prepare_child(element.name, val)
        _check(name, val)
        notify_observers(name, val)
        @_has_values = true
        unless @_primary_keys_set
            if self.class.elements[element.name].primary_key? && primary_keys_set?
                @_primary_keys_set = true
                if Spider::Model.identity_mapper
                    Spider::Model.identity_mapper.put(self, true, true)
                else
                    Spider::Model.unit_of_work.add(self) if Spider::Model.unit_of_work
                end
            end
        end
        old_val = instance_variable_get(ivar)
        @_modified_elements[name] = true if !element.primary_key? && (!was_loaded || val != old_val)
        instance_variable_set(ivar, val)
        set_reverse(element, val) if element.model?
        if val && element.model? && !self.class.attributes[:no_type_check]
            klass = val.is_a?(QuerySet) ? val.model : val.class
            if val && !(klass <= element.type || klass <= element.model)
                raise TypeError, "Object #{val} (#{klass}) is of the wrong type for element #{element.name} in #{self.class} (expected #{element.model})"
            end
        end
        val
        #extend_element(name)
    end
end

.define_elements(&proc) ⇒ Object

Saves the element definition and evals it when first needed, avoiding problems with classes not available yet when the model is defined. FIXME: remove?



705
706
707
# File 'lib/spiderfw/model/base_model.rb', line 705

def self.define_elements(&proc) #:nodoc:
    @elements_definition = proc
end

.dump_element(el) ⇒ Object



2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
# File 'lib/spiderfw/model/base_model.rb', line 2567

def self.dump_element(el)
    remove_elements = []
    method = case el.attributes[:association]
    when :many
        :many
    when :choice
        :choice
    when :multiple_choice
        :multiple_choice
    when :tree
        :tree
    else
        :element
    end
    type = el.type
    attributes = el.attributes.clone
    if (method == :many || method == :multiple_choice)
        attributes.delete(:multiple)
    end
    attributes.delete(:association) if method != :element
    if (attributes[:association_type])
        attributes[:through] = attributes[:association_type] unless attributes[:anonymous_model]
        attributes.delete(:association_type)
    end
    attributes.delete(:lazy) if attributes[:lazy] == :default
    if (method == :tree)
        delete_attrs = [:queryset_module, :multiple]
        delete_attrs.each{ |a| attributes.delete(a) }
        remove_elements += [attributes[:reverse], attributes[:tree_left], attributes[:tree_right], attributes[:tree_depth]]
        type = nil
    end
    return {
        :name => el.name,
        :type => type,
        :attributes => attributes,
        :method => method,
        :remove_elements => remove_elements
    }
end

.each_elementObject

Yields each element in order.



946
947
948
949
950
951
# File 'lib/spiderfw/model/base_model.rb', line 946

def self.each_element
    return unless @elements_order
    @elements_order.each do |name|
        yield elements[name]
    end
end

.element(name, type, attributes = {}, &proc) ⇒ Object

Defines an element. Arguments are element name (a Symbol), element type, and a Hash of attributes.

Type may be a Class: a base type (see Spider::Model.base_types), a DataType subclass, or a BaseModel subclass; or an Array or a Hash, in which case an InlineModel will be created.

An Element instance will be available in Model::BaseModel.elements; getter and setter methods will be defined on the class.

If a block is passed to this method, type will be ‘extended’: a custom junction association will be created, effectively adding elements to the type only in this model’s context. Example:

class Animal < BaseModel
  element :name, String
  element :friends, Animal, :multiple => true do
    element :how_much, String
  end
end
cat = Animal.new(:name => 'Cat')
dog = Animal.new(:name => 'Dog')
cat.friends << dog
cat.friend[0].how_much = 'Not very much'

Returns the created Element.

Some used attributes:

:primary_key

(bool) The element is a primary key

:length

(number) Maximum length of the element (if meaningful)

:required

(bool) The element must always have a value

:multiple

(bool) defines a 1|n -> n relationship

:label

(string) a short description, used by the UI

:association

(symbol) A named association (such as :choice, :multiple_choice, etc.)

:lazy

(bool, array or symbol) If true, the element will be placed in the :default lazy group; if a symbol or an array of symbols is passed, the element will be placed in those groups. (see Element#lazy_groups)

:reverse

(symbol) The reverse element in the relationship to the other model

:add_reverse

(symbol) Adds an element on the other model, and sets it as the association reverse.

:add_multiple_reverse

(symbol) Adds a multiple element on the other model, and sets it as the association reverse.

:element_position

(number) inserts the element at the specified position in the elements order

:auto

(bool) Informative: the value is set automatically through some mechanism

:autoincrement

(bool) The value (which must be a Fixnum) will be autoincremented by the mapper

:integrate

(bool or symbol) type’s elements will be available to this class as if they were defined here (see #integrate)

:integrated_from

(symbol) the name of the element from which this element is integrated

:integrated_from_element

(symbol) the name of the element of the child object from which this element is integrated

:hidden

(bool) a hint that the element shouldn’t be shown by the UI

:computed_from

(array of symbols) the element is not mapped; its value is computed by the class from the given elements.

:unmapped

(bool) the element is not mapped.

:sortable

(bool or Array of symbols) specifies that an unmapped element can be used for sorting. The model must provide a meaningful order using the prepare_query method.

:check

(a Proc, or a Regexp, or a Hash of messages => Regexp|Proc). See #check

:through

(a BaseModel subclass) model representing the many to many relationship.

:read_only

(bool) hint to the UI that the element should not be user modifiable.

:owned

(bool) only this model holds references to type

:condition

(hash or Condition) Restricts an association always adding the condition.

:order

(true or Fixnum) When doing queries, sort by this element. More than one element can have the :order attribute; if it is a Fixnum, it will mean the position in the ordering.

:default

(Proc or value) default value for the element. If it is a Proc, it will be passed the object.

:desc

(true or Fixnum) Use this element for the to_s string. Multiple elements with the :desc attribute will be joined by spaces; order may be specified if a Fixnum is used for the parameter

Other attributes may be used by DataTypes (see #DataType::ClassMethods.take_attributes), and other code. See also Element.



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
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
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/spiderfw/model/base_model.rb', line 196

def self.element(name, type, attributes={}, &proc)
    name = name.to_sym
    @elements ||= {}
    @elements_order ||= []
    raise "Element called #{name} already exists in #{self}" if @elements[name]
    if type.class == Class
        default_attributes = case type.name
        when 'String'
            {:length => 255}
        else
            {}
        end
    else
        default_attributes = {}
    end
    attributes = default_attributes.merge(attributes)
    # if (type.class == Class && Model.base_type(type)) 
    #                 type = Model.base_type(type)
    #             els
    if (type.class <= Hash)
        type = create_inline_model(name, type, attributes)
        attributes[:inline] = true
    end
    if (attributes[:integrated_from])
        if (attributes[:integrated_from].class == String)
            parts = attributes[:integrated_from].split('.')
            attributes[:integrated_from] = @elements[parts[0].to_sym]
            attributes[:integrated_from_element] = parts[1].to_sym if parts[1]
        elsif (attributes[:integrated_from].is_a?(Symbol))
            attributes[:integrated_from] = @elements[attributes[:integrated_from]]
        end
        if (!attributes[:integrated_from_element])
            attributes[:integrated_from_element] = name
        end
    end
    if (attributes[:condition] && !attributes[:condition].is_a?(Condition))
        attributes[:condition] = Condition.new(attributes[:condition])
    end
    if attributes[:computed_from] && !attributes[:computed_from].is_a?(Enumerable)
        attributes[:computed_from] = [attributes[:computed_from]]
    end
    attributes[:embedded] = true if attributes[:owned] && attributes[:embedded].nil?
    type.set_element_attributes(attributes) if type < Spider::DataType


    orig_type = type
    assoc_type = nil
    if (proc || attributes[:junction] || (attributes[:multiple] && (!attributes[:add_reverse]) && (!attributes[:has_single_reverse]) && \
        # FIXME! the first check is needed when the referenced class has not been parsed yet 
        # but now it assumes that the reverse is not multiple if it is not defined
       (attributes[:has_single_reverse] == false || !attributes[:reverse] ||  (!type.elements[attributes[:reverse]] || type.elements[attributes[:reverse]].multiple?))))
        attributes[:anonymous_model] = true
        attributes[:owned] = true unless attributes[:owned] != nil
        first_model = self.first_definer(name, type)
        assoc_type_name = Spider::Inflector.camelize(name)
        create_junction = true
        if (attributes[:through])
            assoc_type = attributes[:through]
            create_junction = false
        elsif (first_model.const_defined?(assoc_type_name) )
            assoc_type = first_model.const_get(assoc_type_name)
            if (!assoc_type.attributes[:sub_model]) # other kind of inline model
                assoc_type_name += 'Junction'
                create_junction = false if (first_model.const_defined?(assoc_type_name))
            else
                create_junction = false
            end
        end
        attributes[:junction] = true
        attributes[:junction_id] = :id unless attributes.has_key?(:junction_id)
        if (attributes[:junction_our_element])
            self_name = attributes[:junction_our_element]
        else
            self_name = first_model.short_name.gsub('/', '_').downcase.to_sym
        end
        attributes[:reverse] = self_name
        unless attributes[:junction_their_element]
            other_name = Spider::Inflector.underscore(orig_type.short_name == self.short_name ? orig_type.name : orig_type.short_name).gsub('/', '_').downcase.to_sym
            other_name = :"#{other_name}_ref" if (orig_type.elements[other_name])
            attributes[:junction_their_element] = other_name
        end
        other_name = attributes[:junction_their_element]
        if (create_junction)
            assoc_type = first_model.const_set(assoc_type_name, Class.new(BaseModel))
            assoc_type.attributes[:sub_model] = self
            assoc_type.attributes[:sub_model_element] = name
            embedder = attributes[:junction_embedded] == false ? false : true
            assoc_type.element(attributes[:junction_id], Spider::DataTypes::PK, :primary_key => true, :autoincrement => true, :hidden => true) if attributes[:junction_id]
            assoc_type.element(self_name, self, :hidden => true, :reverse => name, :association => :choice, :junction_reference => true, :embedder => embedder) # FIXME: must check if reverse exists?
            # FIXME! fix in case of clashes with existent elements
            assoc_type.element(other_name, orig_type, :association => :choice, :junction_reference => true, :embedded => attributes[:embedded])
            assoc_type.integrate(other_name, :hidden => true, :no_pks => true) # FIXME: in some cases we want the integrated elements
            assoc_type.send(:include, Spider::Model::Junction)
            if (proc)                                   #        to be hidden, but the integrated el instead
                attributes[:extended] = true
                attributes[:keep_junction] = true
                assoc_type.class_eval(&proc)
            end
        end
        orig_type.referenced_by_junctions << [assoc_type, other_name]
        attributes[:keep_junction] = true if (attributes[:through] && attributes[:keep_junction] != false)
        attributes[:association_type] = assoc_type
        if attributes[:polymorph]
            assoc_type.elements[attributes[:junction_their_element]].attributes[:polymorph] = attributes[:polymorph]
            attributes.delete(:polymorph)
        end
        attributes[:embedded] = true unless attributes[:junction_embedded] == false
    end
    
    
    add_element(Element.new(name, type, attributes))
    
    
    if (attributes[:add_reverse] && attributes[:add_reverse].is_a?(Symbol))
        attributes[:add_reverse] = {:name => attributes[:add_reverse]}
    end
    if (attributes[:add_multiple_reverse] && attributes[:add_multiple_reverse].is_a?(Symbol))
        attributes[:add_multiple_reverse] = {:name => attributes[:add_multiple_reverse]}
    end
    
    if (attributes[:add_reverse])
        unless (orig_type.elements[attributes[:add_reverse]])
            attributes[:reverse] ||= attributes[:add_reverse][:name]
            rev = attributes[:add_reverse].merge(:reverse => name, :added_reverse => true, 
                :delete_cascade => attributes[:reverse_delete_cascade])
            rev_name = rev.delete(:name)
            if assoc_type
                rev[:junction] = true
                rev[:keep_junction] = false
                rev[:through] = assoc_type
                rev[:junction_their_element] = self_name
                rev[:junction_our_element] = other_name
            end
            if attributes[:embedded] && !attributes[:junction]
                rev[:embedder] = true
            end
            orig_type.element(rev_name, self, rev)
        end
    elsif (attributes[:add_multiple_reverse])
        unless (orig_type.elements[attributes[:add_reverse]])
            attributes[:reverse] ||= attributes[:add_multiple_reverse][:name]
            rev = attributes[:add_multiple_reverse].merge(:reverse => name, :multiple => true, 
                :added_reverse => true, :delete_cascade => attributes[:reverse_delete_cascade])
            rev_name = rev.delete(:name)
            if assoc_type
                rev[:junction] = true
                rev[:through] = assoc_type
                rev[:junction_their_element] = self_name
                rev[:junction_our_element] = other_name
            end
            orig_type.element(rev_name, self, rev)
        end
    end
    if (attributes[:lazy] == nil)
        # if attributes[:primary_key]
        #                     attributes[:lazy] = true
        #                 els
        if (type < BaseModel && (attributes[:multiple] || attributes[:polymorph]))
            # FIXME: we can load eagerly single relations if we can do a join
            attributes[:lazy] = true
        else
            attributes[:lazy_check_owner] = true if type < BaseModel
            attributes[:lazy] = :default
        end
    end
    
    

    
    # class element getter
    unless respond_to?(name)
        (class << self; self; end).instance_eval do
            define_method("#{name}") do
                @elements[name]
            end
        end
    end
    
    define_element_methods(name)
    
    attr_reader "#{name}_junction" if attributes[:junction] && !attributes[:keep_junction]
    
    if (attributes[:integrate])
        integrate_params = attributes[:integrate].is_a?(Hash) ? attributes[:integrate] : {}
        integrate(name, integrate_params)
    end
    if self.attributes[:integrated_from_elements]
        self.attributes[:integrated_from_elements].each do |imod, iel|
            imod.integrate_element(iel, self.elements[name]) unless imod.elements[name] || 
                (self.elements[name].reverse == iel && self.elements[name].type == imod)
        end
    end
    if (@subclasses)
        @subclasses.each do |sub|
            next if sub == type && attributes[:added_reverse] && sub.elements[attributes[:reverse]].type == self # subclass ref to parent
            next if sub.elements[name] # if subclass already defined an element with this name, don't overwrite it
            sub.elements[name] = @elements[name].clone
            sub.elements_order << name
        end
    end
    element_defined(@elements[name])
    @elements[name].model?
    return @elements[name]

end

.element_association?(element_name, association) ⇒ Boolean

Returns true if the element with given name is associated with the passed association. This method should be used instead of querying the element’s association directly, since subclasses and mixins may extend this method to provide association equivalence.

Returns:

  • (Boolean)


983
984
985
# File 'lib/spiderfw/model/base_model.rb', line 983

def self.element_association?(element_name, association)
    return true if elements[element_name].association = association
end

.element_attributes(element_name, attributes) ⇒ Object

Sets additional attributes on the element

Warning: for attributes which are parsed by the BaseModel during element definition, this will not have the desired effect; remove and redefine the element instead.



645
646
647
648
649
650
651
652
# File 'lib/spiderfw/model/base_model.rb', line 645

def self.element_attributes(element_name, attributes)
    elements[element_name].attributes = elements[element_name].attributes.merge(attributes)
    if attributes[:primary_key] && !@primary_keys.include?(elements[element_name])
        @primary_keys << elements[element_name]
    elsif !attributes[:primary_key]
        @primary_keys.delete(elements[element_name])
    end
end

.element_defined(el) ⇒ Object



569
570
571
572
573
574
575
# File 'lib/spiderfw/model/base_model.rb', line 569

def self.element_defined(el)
    if (@on_element_defined && @on_element_defined[el.name])
        @on_element_defined[el.name].each do |proc|
            proc.call(el)
        end
    end
end

.element_query(name, element_name, attributes = {}) ⇒ Object



676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
# File 'lib/spiderfw/model/base_model.rb', line 676

def self.element_query(name, element_name, attributes={})
    orig_element = self.elements[element_name]
    set_el_query = lambda do
        orig_element = self.elements[element_name]
        attributes = attributes.merge(orig_element.attributes)
        attributes[:unmapped] = true
        attributes[:element_query] = element_name
        attributes[:association] = :element_query
        attributes[:lazy] = true
        attributes.delete(:add_reverse)
        attributes.delete(:add_multiple_reverse)
        if (orig_element.attributes[:condition])
            cond = orig_element.attributes[:condition].clone
            cond = cond.and(attributes[:condition]) if attributes[:condition]
            attributes[:condition] = cond
        end
        element(name, orig_element.type, attributes)
    end
    if (orig_element)
        set_el_query.call
    else
        on_element_defined(element_name, &set_el_query)
    end
end

.elementsObject

An Hash of Elements, indexed by name.



936
937
938
# File 'lib/spiderfw/model/base_model.rb', line 936

def self.elements
    @elements
end

.elements_arrayObject

An array of the model’s Elements.



941
942
943
# File 'lib/spiderfw/model/base_model.rb', line 941

def self.elements_array
    @elements_order.map{ |key| @elements[key] }
end

.embeddable?Boolean

Returns:

  • (Boolean)


919
920
921
# File 'lib/spiderfw/model/base_model.rb', line 919

def self.embeddable?
    @embeddable.nil? ? true : @embeddable
end

.extend_model(model, params = {}) ⇒ Object

:nodoc:



740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
# File 'lib/spiderfw/model/base_model.rb', line 740

def self.extend_model(model, params={}) #:nodoc:
    if (model == superclass) # first undo table per class inheritance
        @elements = {}
        @elements_order = []
        @extended_models.delete(model.superclass) if @extended_models
    end
    primary_keys.each{ |k| remove_element(k) } if (params[:replace_pks])
    model.primary_keys.each{ |k| remove_element(k) }
    integrated_name = params[:name]
    if (!integrated_name)
        integrated_name = (self.parent_module == model.parent_module) ? model.short_name : model.name
        integrated_name = Spider::Inflector.underscore(integrated_name).gsub('/', '_')
    end
    integrated_name = integrated_name.to_sym
    @extended_models ||= {}
    @extended_models[model] = integrated_name
    attributes = {}
    attributes[:hidden] = true unless params[:hide_integrated] == false
    attributes[:delete_cascade] = params[:delete_cascade]
    attributes[:extended_model] = true
    attributes[:embedded] = true unless params[:embedded] == false
    attributes[:add_reverse] = params[:reverse]
    integrated = element(integrated_name, model, attributes)
    integrate_options = {:keep_pks => true}.merge((params[:integrate_options] || {}))
    integrate(integrated_name, integrate_options)
    model.elements_array.select{ |el| el.attributes[:local_pk] }.each{ |el| remove_element(el.name) }

    unless (params[:no_local_pk] || !elements_array.select{ |el| el.attributes[:local_pk] }.empty?)
        # FIXME: check if :id is already defined
        pk_name = @elements[:id] ? :"id_#{self.short_name.downcase}" : :id
        element(pk_name, Fixnum, :autoincrement => true, :local_pk => true, :hidden => true)
    end
    model.polymorphic(self, :through => integrated_name)
end

.extend_queryset(qs) ⇒ Object

Can be defined to provide functionality to this model’s querysets.



1175
1176
# File 'lib/spiderfw/model/base_model.rb', line 1175

def self.extend_queryset(qs)
end

.extended_modelsObject

An Hash of extended models => element name of the extended model element



988
989
990
# File 'lib/spiderfw/model/base_model.rb', line 988

def self.extended_models
    @extended_models ||= {}
end

.find(*params, &proc) ⇒ Object

Executes #self.where, and calls QuerySet#load on the result. Returns nil if the result is empty, the QuerySet otherwise See #self.where for parameter syntax



1104
1105
1106
1107
# File 'lib/spiderfw/model/base_model.rb', line 1104

def self.find(*params, &proc)
    qs = self.where(*params, &proc)
    return qs.empty? ? nil : qs
end

.first_definer(element_name, type = nil) ⇒ Object

Returns the model actually defining element_name; that could be the model itself, a superclass, or an integrated model.



965
966
967
968
969
970
971
972
973
974
975
976
977
# File 'lib/spiderfw/model/base_model.rb', line 965

def self.first_definer(element_name, type=nil)
    type ||= self.elements[element_name].type
    if @extended_models && @extended_models[self.superclass] && self.superclass.elements[element_name] && \
            self.superclass.elements[element_name].type == type
        return self.superclass.first_definer(element_name, type)
    end
    if self.attributes[:integrated_models]
        self.attributes[:integrated_models].keys.each do |mod|
            return mod.first_definer(element_name, type) if (mod.elements[element_name] && mod.elements[element_name].type == type)
        end
    end
    return self
end

.free_query_condition(q) ⇒ Object

Returns the condition for a “free” text query Examples:

condition = News.free_query_condition('animals')
animal_news = News.where(condition)


1159
1160
1161
1162
1163
1164
1165
1166
1167
# File 'lib/spiderfw/model/base_model.rb', line 1159

def self.free_query_condition(q)
    c = Condition.or
    self.elements_array.each do |el|
        if (el.type == String || el.type == Text)
            c.set(el.name, 'ilike', '%'+q+'%')
        end
    end
    return c
end

.from_hash_dump(h, options = {}) ⇒ Object



2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
# File 'lib/spiderfw/model/base_model.rb', line 2507

def self.from_hash_dump(h, options={})
    obj = self.static
    obj._check_if_saved = true if options[:check_if_saved]
    h.each do |key, val|
        el = self.elements[key.to_sym]
        next unless el
        if el.multiple? && val
            qs = obj.get(el)
            val.each do |v|
                v = el.model.from_hash_dump(v, options) if v.is_a?(Hash)
                qs << v
            end
        else
            val = el.model.from_hash_dump(val, options) if val.is_a?(Hash)
         case el.type.name.to_sym
            when :Date, :DateTime
                val =  el.type.parse(val) unless val.blank?
            end
					begin
                obj.set(el, val)
            rescue # FIXME: should be and option
            end
        end
    end
    obj
end

.from_yaml(yaml) ⇒ Object



2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
# File 'lib/spiderfw/model/base_model.rb', line 2453

def self.from_yaml(yaml)
    h = YAML::load(yaml)
    obj = self.static
    h.each do |key, value|
        el = elements[key.to_sym]
        if (el.multiple?)
            el_obj = el.model.static
            el.model.primary_keys.each do |pk|
                el_obj.set(pk, value.unshift)
            end
            obj.set(el, el_obj)
        else
            obj.set(el, value)
        end
    end
    return obj
end

.get(values) ⇒ Object



1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
# File 'lib/spiderfw/model/base_model.rb', line 1229

def self.get(values)
    return self.new(values) unless Spider::Model.identity_mapper
    values = [values] unless values.is_a?(Hash) || values.is_a?(Array)
    if values.is_a?(Array)
        vals = {}
        self.primary_keys.each_with_index do |k, i|
            vals[k.name] = values[i]
        end
        values = vals
    end
    curr = Spider::Model.identity_mapper.get(self, values)
    return curr if curr
    obj = self.new(values)
    Spider::Model.identity_mapper.put(obj)
    obj
end

.get_element(el) ⇒ Object



904
905
906
907
908
909
# File 'lib/spiderfw/model/base_model.rb', line 904

def self.get_element(el)
    el = el.name if el.is_a?(Element)
    el = el.to_sym
    raise "No element called #{el} in #{self}" unless @elements[el]
    @elements[el]
end

.get_mapper(storage) ⇒ Object

Returns an instance of the mapper for the given storage



1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
# File 'lib/spiderfw/model/base_model.rb', line 1074

def self.get_mapper(storage)
#            map_class = self.attributes[:inherit_storage] ? superclass : self
    mapper = storage.get_mapper(self)
    if (@mapper_modules)
        @mapper_modules.each{ |mod| mapper.extend(mod) }
    end
    if (@mapper_modules_for)
        @mapper_modules_for.each do |params, mod|
            if (params.length == 1 && params[0].class == String)
                mapper.extend(mod) if self.use_storage == params[0]
            end
        end
    end
    return mapper
end

.get_storage(storage_string = 'default') ⇒ Object

Returns an instancethe storage corresponding to the storage_string if it is given, or of the default storage otherwise. The storage string can be a storage url (see #Storage.get_storage), or a named storage defined in configuration – Mixin!



1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
# File 'lib/spiderfw/model/base_model.rb', line 1050

def self.get_storage(storage_string='default')
    storage_regexp = /([\w\d]+?):(.+)/
    orig_string = nil
    if (storage_string !~ storage_regexp)
        orig_string = storage_string
        storage_conf = Spider.conf.get('storages')[storage_string]
        storage_string = storage_conf['url'] if storage_conf
        if (!storage_string || storage_string !~ storage_regexp)
            raise ModelException, "No storage '#{orig_string}' found"
        end
    end
    type, url = $1, $2
    storage = Storage.get_storage(type, url)
    storage.instance_name = orig_string
    storage.configure(storage_conf) if storage_conf
    return storage
end

.group(name, &proc) ⇒ Object

– TODO: document me



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

def self.group(name, &proc) #:nodoc:
    proxy = Class.new(ProxyModel).proxy(name.to_s+'_', self)
    proxy.instance_eval(&proc)
    proxy.each_element do |el|
        element(name.to_s+'_'+el.name.to_s, el.type, el.attributes.clone)
    end
    define_method(name) do
        @proxies ||= {}
        return @proxies[name] ||= proxy.new
    end
    
end

.has_element?(name) ⇒ Boolean

Returns true if the model has given element name.

Returns:

  • (Boolean)


954
955
956
# File 'lib/spiderfw/model/base_model.rb', line 954

def self.has_element?(name)
    return elements[name] ? true : false
end

.in_transactionObject



2561
2562
2563
2564
2565
# File 'lib/spiderfw/model/base_model.rb', line 2561

def self.in_transaction
    self.storage.in_transaction
    yield
    self.storage.commit_or_continue
end

.inherit_storageObject

Makes the class use the superclass storage



785
786
787
788
789
790
791
792
# File 'lib/spiderfw/model/base_model.rb', line 785

def self.inherit_storage
    self.attributes[:inherit_storage] = true
    (class << self; self; end).instance_eval do
        define_method(:storage) do
            superclass.storage
        end
    end
end

.inherited(subclass) ⇒ Object

Copies this class’ elements to the subclass.



97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/spiderfw/model/base_model.rb', line 97

def self.inherited(subclass) #:nodoc:
    # FIXME: might need to clone every element
    @subclasses ||= []
    @subclasses << subclass
    each_element do |el|
        subclass.add_element(el.clone) unless el.attributes[:local_pk]
    end
    subclass.instance_variable_set("@mapper_modules", @mapper_modules.clone) if @mapper_modules
    subclass.instance_variable_set("@extended_models", @extended_models.clone) if @extended_models
    em = subclass.const_set(:ElementMethods, Module.new)
    subclass.send(:include, em)
    super
end

.integrate(element_name, params = {}) ⇒ Object

Integrates an element: any call to the child object’s elements will be passed to the child. The element must not be multiple. Example:

class Address < BaseModel
  element :street, String
  element :area_code, String
end
class Person < BaseModel
  element :name, String
  element :address, Address
  integrate :address
end
p = Person.new(...)
p.street == p.address.street


597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
# File 'lib/spiderfw/model/base_model.rb', line 597

def self.integrate(element_name, params={})
    params ||= {}
    elements[element_name].attributes[:integrated_model] = true
    model = elements[element_name].type
    self.attributes[:integrated_models] ||= {}
    self.attributes[:integrated_models][model] = element_name
    params[:except] ||= []
    model.each_element do |el|
        next if params[:except].include?(el.name)
        next if elements[el.name] unless params[:overwrite] # don't overwrite existing elements
        next if el.reverse == element_name && el.type == self
        integrate_element(element_name, el, params)
    end
    model.attributes[:integrated_from_elements] ||= []
    model.attributes[:integrated_from_elements] << [self, element_name]
end

.integrate_element(element_name, element_element, params = {}) ⇒ Object



614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
# File 'lib/spiderfw/model/base_model.rb', line 614

def self.integrate_element(element_name, element_element, params={})
    el = element_element
    el = self.elements[element_name].model.elements[el] if el.is_a?(Symbol)
    integrated_attributes = {}
    integrated_attributes[:primary_key] = false if params[:no_pks]
    integrated_attributes[:hidden] = params[:hidden] unless (params[:hidden].nil?)

    integrated_attributes[:primary_key] = false unless (params[:keep_pks])
    # attributes.delete(:required)
    # attributes.delete(:integrate)
    # attributes.delete(:local_pk)
    integrated_attributes[:local_pk] = false
    integrated_attributes[:lazy] = element_name
    name = params[:mapping] && params[:mapping][el.name] ? params[:mapping][el.name] : el.name
    add_element(IntegratedElement.new(name, self, element_name, el.name, integrated_attributes))
    define_element_methods(name)
end

.junction?Boolean

Returns:

  • (Boolean)


911
912
913
# File 'lib/spiderfw/model/base_model.rb', line 911

def self.junction?
    !!self.attributes[:sub_model]
end

.keys_string(keys) ⇒ Object



1735
1736
1737
# File 'lib/spiderfw/model/base_model.rb', line 1735

def self.keys_string(keys)
    keys.join(',')
end

.label(sing = nil, plur = nil) ⇒ Object

Sets the singolar and/or the plural label for the model Returns the singlular label



880
881
882
883
884
# File 'lib/spiderfw/model/base_model.rb', line 880

def self.label(sing=nil, plur=nil)
    @label = sing if sing
    @label_plural = plur if plur
    _(@label || self.name || '')
end

.label_plural(val = nil) ⇒ Object

Sets/retrieves the plural form for the label



887
888
889
890
# File 'lib/spiderfw/model/base_model.rb', line 887

def self.label_plural(val=nil)
    @label_plural = val if (val)
    _(@label_plural || self.name || '')
end

.listObject

Returns a queryset with the objects that should be shown to the user. By default returns self.all



1124
1125
1126
# File 'lib/spiderfw/model/base_model.rb', line 1124

def self.list
    return self.all
end

.load(*params, &proc) ⇒ Object

Executes #self.where, returning the first result. See #self.where for parameter syntax.



1111
1112
1113
1114
1115
# File 'lib/spiderfw/model/base_model.rb', line 1111

def self.load(*params, &proc)
    qs = self.where(*params, &proc)
    qs.only_one
    return qs[0]
end

.load_or_create(values) ⇒ Object



1225
1226
1227
# File 'lib/spiderfw/model/base_model.rb', line 1225

def self.load_or_create(values)
    self.load(values) || self.create(values)
end

.managed?Boolean

False for BaseModel (true for Spider::Model::Managed).

Returns:

  • (Boolean)


869
870
871
# File 'lib/spiderfw/model/base_model.rb', line 869

def self.managed?
    return false
end

.many(name, type, attributes = {}, &proc) ⇒ Object

Defines a multiple element. Equivalent to calling

element(name, type, :multiple => true, :association => :many, ...)


656
657
658
659
660
# File 'lib/spiderfw/model/base_model.rb', line 656

def self.many(name, type, attributes={}, &proc)
    attributes[:multiple] = true
    attributes[:association] ||= :many
    element(name, type, attributes, &proc)
end

.mapperObject

Returns an instance of the default mapper for the class.



1069
1070
1071
# File 'lib/spiderfw/model/base_model.rb', line 1069

def self.mapper
    @mapper ||= get_mapper(storage)
end

.mapper_include(mod) ⇒ Object

The given module will be mixed in any mapper used by the class.



997
998
999
1000
# File 'lib/spiderfw/model/base_model.rb', line 997

def self.mapper_include(mod)
    @mapper_modules ||= []
    @mapper_modules << mod
end

.mapper_include_for(params, mod) ⇒ Object



1002
1003
1004
1005
1006
# File 'lib/spiderfw/model/base_model.rb', line 1002

def self.mapper_include_for(params, mod)
    params = [params] unless params.is_a?(Array)
    @mapper_modules_for ||= []
    @mapper_modules_for << [params, mod]
end

.multiple_choice(name, type, attributes = {}, &proc) ⇒ Object

Defines a multiple element with :multiple_choice association. Shorthand for

many(name, type, :association => :multiple_choice, ...)


671
672
673
674
# File 'lib/spiderfw/model/base_model.rb', line 671

def self.multiple_choice(name, type, attributes={}, &proc)
    attributes[:association] = :multiple_choice
    many(name, type, attributes, &proc)
end

.not_embeddableObject



915
916
917
# File 'lib/spiderfw/model/base_model.rb', line 915

def self.not_embeddable
    @embeddable = false
end

.notify_observers(element_name, new_val, obj = nil) ⇒ Object

Calls the observers for element_name



1989
1990
1991
1992
1993
1994
1995
1996
# File 'lib/spiderfw/model/base_model.rb', line 1989

def self.notify_observers(element_name, new_val, obj=nil)
    if @value_observers && @value_observers[element_name]
        @value_observers[element_name].each{ |proc| proc.call(obj, element_name, new_val) }
    end
    if @all_values_observers
        @all_values_observers.each{ |proc| proc.call(obj, element_name, new_val) }
    end
end

.observe_element(element_name, &proc) ⇒ Object



1963
1964
1965
1966
# File 'lib/spiderfw/model/base_model.rb', line 1963

def self.observe_element(element_name, &proc)
    self.value_observers[element_name] ||= []
    @value_observers[element_name] << proc
end

.observer_all_values(&proc) ⇒ Object



1959
1960
1961
# File 'lib/spiderfw/model/base_model.rb', line 1959

def self.observer_all_values(&proc)
    self.all_values_observers << proc
end

.on_element_defined(el_name, &proc) ⇒ Object



577
578
579
580
581
# File 'lib/spiderfw/model/base_model.rb', line 577

def self.on_element_defined(el_name, &proc)
    @on_element_defined ||= {}
    @on_element_defined[el_name] ||= []
    @on_element_defined[el_name] << proc
end

.only_embeddedObject



923
924
925
# File 'lib/spiderfw/model/base_model.rb', line 923

def self.only_embedded
    @only_embedded = true
end

.only_embedded?Boolean

Returns:

  • (Boolean)


927
928
929
# File 'lib/spiderfw/model/base_model.rb', line 927

def self.only_embedded?
    @only_embedded
end

.polymorphic(model, options) ⇒ Object

Add a subclass, allowing polymorphic queries on it.



816
817
818
819
820
821
# File 'lib/spiderfw/model/base_model.rb', line 816

def self.polymorphic(model, options)
    through = options[:through] || Spider::Inflector.underscore(self.name).gsub('/', '_')
    through = through.to_sym
    @polymorphic_models ||= {}
    @polymorphic_models[model] = {:through => through}
end

.prepare_query(query) ⇒ Object

Method that will be called by the mapper before a query. May be overridden to preprocess the query. Must return the modified query. Note: to prepare conditions, use prepare_condition, since it will be called on subconditions as well.



2184
2185
2186
# File 'lib/spiderfw/model/base_model.rb', line 2184

def self.prepare_query(query)
    query
end

.prepare_to_codeObject



2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
# File 'lib/spiderfw/model/base_model.rb', line 2607

def self.prepare_to_code
    modules = self.name.split('::')[0..-2]
    included = (self.included_modules - Spider::Model::BaseModel.included_modules).select do |m|
        m.name !~ /^#{Regexp.quote(self.name)}/
    end
    local_name = self.name.split('::')[-1]
    superklass = self.superclass.name
    elements = []
    remove_elements = []
    self.elements_array.each do |el|
        next if el.integrated?
        next if (el.reverse && el.model.elements[el.reverse] && \
            (el.model.elements[el.reverse].attributes[:add_reverse] || \
            el.model.elements[el.reverse].attributes[:add_multiple_reverse]))
        el_hash = dump_element(el)
        return nil unless el_hash
        elements << el_hash
        remove_elements += el_hash[:remove_elements]
    end
    elements.reject!{ |el| remove_elements.include?(el[:name]) }
    return {
        :modules => modules,
        :included => included,
        :attributes => self.attributes,
        :elements => elements,
        :local_name => local_name,
        :superclass => superklass,
        :use_storage => @use_storage,
        :additional_code => []
    }
end

.prepare_value(element, value) ⇒ Object

Prepares a value going to be set on the object. Will convert the value to the appropriate type.



1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
# File 'lib/spiderfw/model/base_model.rb', line 1480

def self.prepare_value(element, value)
    element = self.class.elements[element] unless element.is_a?(Element)
    if element.type < Spider::DataType && element.type.force_wrap?
        value = element.type.from_value(value) unless value.is_a?(element.type)
        if value
            element.type.take_attributes.each do |a|
                if element.attributes[a].is_a?(Proc)
                    value.attributes[a] = value.instance_eval(&element.attributes[a])
                else
                    value.attributes[a] = element.attributes[a]
                end
            end
            value = value.prepare
        end
    elsif element.model?
        value.autoload(autoload?, true) if value && value.respond_to?(:autolad)
    else
        type = element.type
        type = mapper.base_type(type) if type < Spider::DataType && type.mapper_dependant?
        case type.name
        when 'Date', 'DateTime'
            return nil if value.is_a?(String) && value.empty?
            parsed = nil
            if (value.is_a?(String))
                begin
                    parsed = element.type.strptime(value, "%Y-%m-%dT%H:%M:%S") rescue parsed = nil
                    parsed ||= element.type.lparse(value, :short) rescue parsed = nil
                    parsed ||= element.type.parse(value)
                rescue ArgumentError => exc
                    raise FormatError.new(element, value, _("'%s' is not a valid date"))
                end
                value = parsed
            end
        when 'String'
        when 'Spider::DataTypes::Text'
            value = value.to_s
        when 'Fixnum'
            value = value.to_i
        end
    end
    value
end

.primary_keysObject

An array of elements with primary_key attribute set.



959
960
961
# File 'lib/spiderfw/model/base_model.rb', line 959

def self.primary_keys
    @primary_keys
end

.referenced_by_junctionsObject



855
856
857
# File 'lib/spiderfw/model/base_model.rb', line 855

def self.referenced_by_junctions
    @referenced_by_junctions ||= []
end

.remove_element(el) ⇒ Object

Removes a defined element



544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/spiderfw/model/base_model.rb', line 544

def self.remove_element(el)
    return unless @elements
    el = el.name if el.is_a?(Element)
    element = @elements[el]
    if self.attributes[:integrated_from_elements]
        self.attributes[:integrated_from_elements].each do |mod, iel|
            i = mod.elements[el]
            mod.remove_element(el) if i && i.integrated? && i.integrated_from.name == iel
        end
    end
    self.elements_array.select{ |e| e.integrated? && e.integrated_from.name == el}.each{ |e| remove_element(e) }
    self.const_get(:ElementMethods).send(:remove_method, :"#{el}") rescue NameError
    self.const_get(:ElementMethods).send(:remove_method, :"#{el}=") rescue NameError
    @extended_models.delete(element.type) if element && @extended_models && @extended_models[element.type] == el
    @elements.delete(el)
    @elements_order.delete(el)
    @primary_keys.delete_if{ |pk| pk.name == el}

    # if (@subclasses)
    #     @subclasses.each do |sub|
    #         sub.remove_element(el)
    #     end
    # end
end

.remove_integrate(element_name) ⇒ Object



632
633
634
635
636
637
638
639
# File 'lib/spiderfw/model/base_model.rb', line 632

def self.remove_integrate(element_name)
    element = element_name.is_a?(Element) ? element_name : self.elements[element_name]
    model = element.model
    self.elements_array.select{ |el| el.attributes[:integrated_from] && el.attributes[:integrated_from].name == element.name }.each do |el|
        self.remove_element(el)
    end
    model.attributes[:integrated_from_elements].reject!{ |item| item[0] == self }
end

.sequence(name) ⇒ Object

Adds a sequence to the model.



841
842
843
844
# File 'lib/spiderfw/model/base_model.rb', line 841

def self.sequence(name)
    @sequences ||= []
    @sequences << name
end

.short_nameObject

Underscored local name (without namespaces)



864
865
866
# File 'lib/spiderfw/model/base_model.rb', line 864

def self.short_name
    return Inflector.underscore(self.name.match(/([^:]+)$/)[1])
end

.split_keys_string(string) ⇒ Object



1739
1740
1741
# File 'lib/spiderfw/model/base_model.rb', line 1739

def self.split_keys_string(string)
    string.split(',')
end

.static(values = nil) ⇒ Object

Returns an instance of the Model with #autoload set to false



1212
1213
1214
1215
1216
1217
# File 'lib/spiderfw/model/base_model.rb', line 1212

def self.static(values=nil)
    obj = self.new
    obj.autoload = false
    obj.set_values(values) if values
    return obj
end

.storageObject

Returns the current default storage for the class The storage to use can be set with #use_storage



1038
1039
1040
1041
1042
# File 'lib/spiderfw/model/base_model.rb', line 1038

def self.storage
    return @storage if @storage
    st = self.use_storage
    return st ? get_storage(st) : get_storage
end

.subclassesObject



111
112
113
# File 'lib/spiderfw/model/base_model.rb', line 111

def self.subclasses
    @subclasses || []
end

.submodelsObject

An array of other models this class points to.



735
736
737
# File 'lib/spiderfw/model/base_model.rb', line 735

def self.submodels
    elements.select{ |name, el| el.model? }.map{ |name, el| el.model }
end

.sync_schema(options = {}) ⇒ Object

Syncs the schema with the storage.



1091
1092
1093
1094
1095
1096
1097
1098
1099
# File 'lib/spiderfw/model/base_model.rb', line 1091

def self.sync_schema(options={})
    options = ({
        :force => false,
        :drop_fields => false,
        :update_sequences => false,
        :no_foreign_key_constraints => true
    }).merge(options)
    Spider::Model.sync_schema(self, options[:force], options)
end

.to_code(options = {}) ⇒ Object



2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
# File 'lib/spiderfw/model/base_model.rb', line 2639

def self.to_code(options={})
    c = prepare_to_code
    str = ""
    indent = 0
    append = lambda do |val|
        str += " "*indent
        str += val
        str
    end
    str += c[:modules].map{ |m| "module #{m}" }.join('; ') + "\n"
    str += "\n"
    indent = 4
    append.call "class #{c[:local_name]} < #{c[:superclass]}\n"
    indent += 4
    c[:included].each do |i|
        append.call "include #{i.name}\n"
    end
    c[:attributes].each do |k, v|
        append.call "attribute :#{k}, #{v.inspect}"
    end
    str += "\n"
    c[:elements].each do |el|
        append.call("#{el[:method].to_s} #{el[:name].inspect}")
        str += ", #{el[:type]}" if el[:type]
        str += ", #{el[:attributes].inspect[1..-2]}\n" if el[:attributes] && !el[:attributes].empty?
    end
    str += "\n"
    append.call "use_storage '#{c[:use_storage]}'\n" if c[:use_storage]
    c[:additional_code].each do |block|
        block.each_line do |line|
            append.call line
        end
        str += "\n"
    end
    indent -= 4
    append.call("end\n")
    str += c[:modules].map{ "end" }.join(';')
    return str
end

.to_sObject

Name



874
875
876
# File 'lib/spiderfw/model/base_model.rb', line 874

def self.to_s
    self.name
end

.use_storage(name = nil) ⇒ Object

Sets the url or the name of the storage to use



1029
1030
1031
1032
1033
1034
# File 'lib/spiderfw/model/base_model.rb', line 1029

def self.use_storage(name=nil)
    @use_storage = name if name
    @use_storage ||= self.attributes[:sub_model].use_storage if self.attributes[:sub_model]
    @use_storage ||= self.superclass.use_storage if self.superclass.respond_to?(:use_storage) && self.superclass.use_storage
    @use_storage
end

.value_observersObject



1968
1969
1970
# File 'lib/spiderfw/model/base_model.rb', line 1968

def self.value_observers
    @value_observers ||= {}
end

.where(*params, &proc) ⇒ Object

Constructs a Query based on params, and returns a QuerySet Allowed parameters are:

  • a Query object

  • a Condition and an (optional) Request, or anything that can be parsed by Condition.new and Request.new

If a block is provided, it is passed to Condition.parse_block. Examples:

felines = Animals.where({:family => 'felines'})
felines = Animals.where({:family => 'felines'}, [:name, :description])
cool_animals = Animals.where{ (has_fangs == true) | (has_claws == true)}

See also Condition#parse_block



1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
# File 'lib/spiderfw/model/base_model.rb', line 1138

def self.where(*params, &proc)
    if (params[0] && params[0].is_a?(Query))
        query = params[0]
        qs = QuerySet.new(self, query)
    elsif(proc)
        qs = QuerySet.new(self)
        qs.autoload = true
        qs.where(&proc)
    else
        condition = Condition.and(params[0])
        request = Request.new(params[1])
        query = Query.new(condition, request)
        qs = QuerySet.new(self, query)
    end
    return qs
end

.with_mapper(&proc) ⇒ Object

The given proc will be mixed in the mapper used by this class Note that the proc will be converted to a Module, so any overridden methods will still have access to the super method.



1011
1012
1013
1014
1015
1016
# File 'lib/spiderfw/model/base_model.rb', line 1011

def self.with_mapper(&proc)
    mod = Module.new
    mod.send(:include, Spider::Model::MapperIncludeModule)
    mod.module_eval(&proc)
    mapper_include(mod)
end

.with_mapper_for(*params, &proc) ⇒ Object

Like #with_mapper, but will mixin the block only if the mapper matches params. Possible params are:

  • a String, matching the class’ use_storage



1021
1022
1023
1024
1025
1026
# File 'lib/spiderfw/model/base_model.rb', line 1021

def self.with_mapper_for(*params, &proc)
    mod = Module.new
    mod.send(:include, Spider::Model::MapperIncludeModule)
    mod.module_eval(&proc)
    mapper_include_for(params, mod)
end

Instance Method Details

#==(other) ⇒ Object

Returns true if other is_a?(self.class), and has the same values for this class’ primary keys.



1680
1681
1682
1683
1684
1685
1686
1687
# File 'lib/spiderfw/model/base_model.rb', line 1680

def ==(other)
    return false unless other
    return false unless other.is_a?(self.class)
    self.class.primary_keys.each do |k|
        return false unless get(k) == other.get(k)
    end
    return true
end

#[](element) ⇒ Object

Calls #get on element; whenever no getter responds, returns the extra data. See #[]=



1453
1454
1455
1456
1457
1458
1459
1460
# File 'lib/spiderfw/model/base_model.rb', line 1453

def [](element)
    element = element.name if element.is_a?(Element)
    begin
        get(element)
    rescue NoMethodError
        return @_extra[element]
    end
end

#[]=(element, value) ⇒ Object

If element is a model’s element, calls #set. Otherwise, stores the value in an “extra” hash, where it will be accessible by #[]



1464
1465
1466
1467
1468
1469
1470
1471
# File 'lib/spiderfw/model/base_model.rb', line 1464

def []=(element, value)
    element = element.name if element.is_a?(Element)
    if (self.class.elements[element])
        set(element, value)
    else
        @_extra[element] = value
    end
end

#_check(name, val) ⇒ Object

Apply element checks for given element name and value. (See also #element, :check attribute). Checks may be defined by the DataType, or be given as an element attribute. The check can be a Regexp, that will be checked against the value, or a Proc, which is expected to return true if the check is succesful, and false otherwise. Will raise a Model::FormatError when a check is not succesful. If the :check attribute is an Hash, the Hash keys will be used as messages, which will be passed to the FormatError.



1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
# File 'lib/spiderfw/model/base_model.rb', line 1566

def _check(name, val)
    element = self.class.elements[name]
    element.type.check(val) if (element.type.respond_to?(:check))
    if (checks = element.attributes[:check])
        checks = {(_("'%s' is not in the correct format") % element.label) => checks} unless checks.is_a?(Hash)
        checks.each do |msg, check|
            test = case check
            when Regexp
                val.blank? ? true : check.match(val.to_s)
            when Proc
                check.call(val)
            end
            raise FormatError.new(element, val, msg) unless test
        end
    end
end

#after_deleteObject



2125
2126
# File 'lib/spiderfw/model/base_model.rb', line 2125

def after_delete
end

#after_saveObject



2119
2120
2121
2122
2123
# File 'lib/spiderfw/model/base_model.rb', line 2119

def after_save
    reset_modified_elements
    autoload(@_saving[:prev_autoload]) if @_saving
    @_saving = nil
end

#all_children(path) ⇒ Object

Returns all children that can be reached from the current path. Path is expressed as a dotted String.



1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
# File 'lib/spiderfw/model/base_model.rb', line 1359

def all_children(path)
    children = []
    no_autoload do
        el = path.shift
        if self.class.elements[el] && element_has_value?(el) && children = get(el)
            if path.length >= 1
                children = children.all_children(path)
            end
        end
    end
    return children
end

#autoload(a, traverse = true) ⇒ Object

Sets autoload mode The first parameter the value of autoload to be set; it can be true, false or :save_mode (see #save_mode)) the second bool parameter specifies if the value should be propagated on all child objects.



1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
# File 'lib/spiderfw/model/base_model.rb', line 1635

def autoload(a, traverse=true) #:nodoc:
    return if @_tmp_autoload_walk
    @_tmp_autoload_walk = true
    @_autoload = a
    if (traverse)
        self.class.elements_array.select{ |el| el.model? && \
            (element_has_value?(el.name) || el.attributes[:extended_model])}.each do |el|
            val = get(el)
            val.autoload = a if val.respond_to?(:autoload=)
        end
    end
    @_tmp_autoload_walk = nil
end

#autoload=(val) ⇒ Object

Enables or disables autoloading. An autoloading object will try to load all missing elements on first access. (see also Element#lazy_groups)



1628
1629
1630
# File 'lib/spiderfw/model/base_model.rb', line 1628

def autoload=(val)
    autoload(val, false)
end

#autoload?Boolean

Returns the current autoload status

Returns:

  • (Boolean)


1621
1622
1623
# File 'lib/spiderfw/model/base_model.rb', line 1621

def autoload?
    @_autoload
end

#become(model) ⇒ Object



1602
1603
1604
1605
1606
# File 'lib/spiderfw/model/base_model.rb', line 1602

def become(model)
    return self if self.class == model
    obj = polymorphic_become(model) rescue ModelException
    return obj
end

#before_deleteObject



2113
2114
# File 'lib/spiderfw/model/base_model.rb', line 2113

def before_delete
end

#before_saveObject



2116
2117
# File 'lib/spiderfw/model/base_model.rb', line 2116

def before_save
end

#clear_valuesObject

Sets all values to nil



2171
2172
2173
2174
2175
# File 'lib/spiderfw/model/base_model.rb', line 2171

def clear_values()
    self.class.elements.each_key do |element_name|
        instance_variable_set(:"@#{element_name}", nil)
    end
end

#cloneObject

Returns a deep copy of the object



1896
1897
1898
1899
1900
# File 'lib/spiderfw/model/base_model.rb', line 1896

def clone
    obj = self.class.new
    obj.merge!(self)
    return obj
end

#cut(*params, &proc) ⇒ Object

Returns a part of the object tree, converted to Hashes, Arrays and Strings. Arguments can be:

  • a String, followed by a list of elements; the String will be sprintf’d with element values

or

  • a depth Fixnum; depth 0 means obj.to_s will be returned, depth 1 will return an hash containing the object’s element values converted to string, and so on

or

  • a Hash, whith element names as keys, and depths, or Hashes, or Procs as values; each element will be traversed up to the depth given, or recursively according to the has; or, if a Proc is given, it will be called with the current object and element name as arguments

or

  • a list of elements; this is equivalent to passing a hash of the elements with depth 0.

Examples:

obj.inspect
  => Zoo::Animal: {:name => Llama, :family => Camelidae, :friends => Sheep, Camel}
obj.cut(0) 
  => 'Llama'
obj.cut(:name, :friends) 
  => {:name => 'Llama', :friends => 'Sheep, Camel'}
obj.cut(:name => 0, :friends => 1)
  => {:name => 'Llama', :friends => [
        {:name => 'Sheep', :family => 'Bovidae', :friends => 'Llama'},
        {:name => 'Camel', :family => 'Camelidae', :friens => 'Dromedary, LLama'}
      ]}
obj.cut(:name => 0, :friends => {:name => 0})
  => {:name => 'Llama', :friends => [{:name => 'Sheep'}, {:name => 'Camel'}]}
objs.cut(:name => 0, :friends => lambda{ |instance, element| 
   instance.get(element).name.upcase
})
  => {:name => 'Llama', :friends => ['SHEEP', 'CAMEL']}
obj.cut("Hi, i'm a %s and my friends are %s", :name, :friends)
  => "Hi, i'm a Llama and my friends are Sheep, Camel"


2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
# File 'lib/spiderfw/model/base_model.rb', line 2362

def cut(*params, &proc)
    h = {}
    if (params[0].is_a?(String))
        return sprintf(params[0], *params[1..-1].map{ |el| get(el) })
    elsif (params[0].is_a?(Fixnum))
        p = params.shift
        if (p < 1)
            if (block_given?)
                return proc.call(self)
            else
                return self.to_s
            end
        end
        lev = p
        where = {}
        self.class.elements_array.each { |el| where[el.name] = lev-1}
    end
    if (params[0].is_a?(Hash))
        where ||= {}
        params[0].each{ |k, v| where[k.to_sym] = v}
    else
        where ||= {}
        params.each{ |p| where[p] = 0 if p.is_a?(Symbol)}
    end
    Spider::Model.with_identity_mapper do |im|
        where.keys.each do |name|
            next unless where[name]
            if (where[name].is_a?(Proc))
                val = where[name].call(self, name)
            else
                el = self.class.elements[name]
                if el
                    val = get(el)
                    val = val.cut(where[name], &proc) if el.model? && val
                else
                    raise ModelException, "Element #{name} does not exist" unless self.respond_to?(name)
                    val = self.send(name)
                    val = val.cut(where[name], &proc) if val.is_a?(BaseModel)
                end
            end
            h[name] = val
        end
    end
    return h
end

#deleteObject

Deletes the object from the storage (see Mapper#delete).



2101
2102
2103
2104
2105
2106
# File 'lib/spiderfw/model/base_model.rb', line 2101

def delete
    saving(:delete) do
        before_delete
        delete!
    end
end

#delete!Object



2108
2109
2110
2111
# File 'lib/spiderfw/model/base_model.rb', line 2108

def delete!
    mapper.delete(self)
    after_delete
end

#dump_to_all_data_hash(options = {}, h = {}, seen = {}) ⇒ Object



2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
# File 'lib/spiderfw/model/base_model.rb', line 2534

def dump_to_all_data_hash(options={}, h={}, seen={})
    Spider::Model.with_identity_mapper do |im|
        clname = self.class.name.to_sym
        seen[clname] ||= {}
        return if seen[clname][self.primary_keys]
        seen[clname][self.primary_keys] = true
        h[clname] ||= []
        h[clname] << self.dump_to_hash
        self.class.elements_array.each do |el|
            next unless el.model?
            next if el.model < Spider::Model::InlineModel
            next if options[:models] && !options[:models].include?(el.type)
            next if options[:except_models] && options[:except_models].include?(el.type)
            el_clname == el.type.name.to_sym
            next if options[:elements] && (options[:elements][el_clname] && !options[:elements][el_clname].include?(el.name))
            next if options[:except_elements] && (options[:except_elements][el_clname] && options[:except_elements][el_clname].include?(el.name))
            val = self.get(el)
            next unless val
            val = [val] unless val.is_a?(Enumerable)
            val.each do |v|
                v.dump_to_all_data_hash(options, h, seen)
            end
        end
    end
    h
end

#dump_to_hashObject



2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
# File 'lib/spiderfw/model/base_model.rb', line 2471

def dump_to_hash
    h = {}
    def obj_pks(obj, klass)
        unless obj
            return klass.primary_keys.length > 1 ? [] : nil
        end
        pks = obj.primary_keys
        return pks[0] if pks.length == 1
        return pks
    end 

    self.class.elements_array.each do |el|
        next unless mapper.have_references?(el) || (el.junction? && el.model.attributes[:sub_model] == self.class)
        if (el.model?)
            obj = get(el)
            if !obj
               h[el.name] = nil 
            elsif (el.multiple?)
                h[el.name] = obj.map{ |o| obj_pks(o, el.model) }
            else
                h[el.name] = obj_pks(obj, el.model)
            end
        else
            val = get(el)
            if val
                case val.class.name.to_sym
                when :Date, :DateTime, :Time
                    val = val.strftime
                end
            end
            h[el.name] = val
        end
    end
    h
end

#eachObject

Iterates over elements and yields name-value pairs



1701
1702
1703
1704
1705
# File 'lib/spiderfw/model/base_model.rb', line 1701

def each # :yields: element_name, element_value
    self.class.elements.each do |name, el|
        yield name, get(name)
    end
end

#each_valObject

Iterates over non-nil elements, yielding name-value pairs



1708
1709
1710
1711
1712
# File 'lib/spiderfw/model/base_model.rb', line 1708

def each_val # :yields: element_name, element_value
    self.class.elements.select{ |name, el| element_has_value?(name) }.each do |name, el|
        yield name, get(name)
    end
end

#element_has_value?(element) ⇒ Boolean

Returns true if the element instance variable is set – FIXME: should probably try to get away without this method it is the only method that relies on the mapper

Returns:

  • (Boolean)


1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
# File 'lib/spiderfw/model/base_model.rb', line 1749

def element_has_value?(element)
    element = self.class.get_element(element)
    if (element.integrated?)
        return false unless obj = instance_variable_get(:"@#{element.integrated_from.name}")
        return obj.element_has_value?(element.integrated_from_element)
    end
    if (element.attributes[:computed_from])
        element.attributes[:computed_from].each{ |el| return false unless element_has_value?(el) }
        return true
    end
    ivar = nil
    ivar = instance_variable_get(:"@#{element.name}") if instance_variable_defined?(:"@#{element.name}")
    return nil == ivar ? false : true
    # FIXME: is this needed?
    # if (!mapper.mapped?(element)
    #     return send("#{element_name}?") if (respond_to?("#{element_name}?"))
    #     return get(element) == nil ? false : true if (!mapper.mapped?(element))
    # end
end

#element_loaded(element_name) ⇒ Object

Records that the element has been loaded.



1548
1549
1550
1551
# File 'lib/spiderfw/model/base_model.rb', line 1548

def element_loaded(element_name)
    element_name = self.class.get_element(element_name).name
    @loaded_elements[element_name] = true
end

#element_loaded?(element) ⇒ Boolean

Returns true if the element has been loaded by the mapper.

Returns:

  • (Boolean)


1554
1555
1556
1557
# File 'lib/spiderfw/model/base_model.rb', line 1554

def element_loaded?(element)
    element = self.class.get_element(element).name
    return @loaded_elements[element]
end

#element_modified?(element) ⇒ Boolean

Returns true if the element value has been modified since instantiating or loading

Returns:

  • (Boolean)


1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
# File 'lib/spiderfw/model/base_model.rb', line 1770

def element_modified?(element)
    element = self.class.get_element(element)
    set_mod = @_modified_elements[element.name]
    return set_mod if set_mod
    if element.integrated?
        return false unless integrated = get_no_load(element.integrated_from)
        return integrated.element_modified?(element.integrated_from_element)
    end
    if element_has_value?(element)
        val = get(element)
        # don't call modified? on QuerySets, since we don't need to query sub objects (this could lead to infinite loops)
        return val.modified if val.is_a?(QuerySet) 
        return val.modified? if val.respond_to?(:modified?)
    end
    return false
end

#elements_modified?(*elements) ⇒ Boolean

Returns true if any of elements has been modified

Returns:

  • (Boolean)


1788
1789
1790
1791
1792
# File 'lib/spiderfw/model/base_model.rb', line 1788

def elements_modified?(*elements)
    elements = elements[0] if elements[0].is_a?(Array)
    elements.each{ |el| return true if element_modified?(el) }
    return false
end

#embedderObject

Returns the object’s embedder element, if any



1390
1391
1392
# File 'lib/spiderfw/model/base_model.rb', line 1390

def embedder
    self.class.elements_array.select{ |el| el.attributes[:embedder] && self.element_has_value?(el) }[0]
end

#empty?Boolean

Returns true if no element has a value

Returns:

  • (Boolean)


1864
1865
1866
# File 'lib/spiderfw/model/base_model.rb', line 1864

def empty?
    return @_has_values
end

#eql?(other) ⇒ Boolean

Like ==, but other must be of the same class (no subclasses)

Returns:

  • (Boolean)


1690
1691
1692
1693
# File 'lib/spiderfw/model/base_model.rb', line 1690

def eql?(other)
    return false unless other.class == self.class
    return self == other
end

#get(element) ⇒ Object

Returns an element. The element may be a symbol, or a dotted path String. Will call the associated getter.

cat.get('favorite_food.name')


1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
# File 'lib/spiderfw/model/base_model.rb', line 1403

def get(element)
    element = element.name if element.is_a?(Element)
    first, rest = element.to_s.split('.', 2)
    if (rest)
        sub_val = send(first)
        return nil unless sub_val
        return sub_val.get(rest)
    end
    return send(element)
end

#get_newObject

Returns a new instance with the same primary keys



1903
1904
1905
1906
1907
1908
1909
1910
1911
# File 'lib/spiderfw/model/base_model.rb', line 1903

def get_new
    obj = nil
    Spider::Model.no_identity_mapper do
        obj = self.class.new
        obj._no_identity_mapper = true
        self.class.primary_keys.each{ |k| obj.set(k, self.get(k)) }
    end
    return obj
end

#get_new_staticObject

Returns a new static instance with the same primary keys



1914
1915
1916
1917
1918
1919
1920
1921
1922
# File 'lib/spiderfw/model/base_model.rb', line 1914

def get_new_static
    obj = nil
    Spider::Model.no_identity_mapper do
        obj = self.class.static
        obj._no_identity_mapper = true
        self.class.primary_keys.each{ |k| obj.set(k, self.get(k)) }
    end
    return obj
end

#get_no_load(element) ⇒ Object

Returns an element without autoloading it.



1415
1416
1417
1418
1419
1420
1421
# File 'lib/spiderfw/model/base_model.rb', line 1415

def get_no_load(element)
    res = nil
    no_autoload do
        res = get(element)
    end
    return res
end

#identity_mapperObject

Returns the instance’s IdentityMapper



1274
1275
1276
1277
# File 'lib/spiderfw/model/base_model.rb', line 1274

def identity_mapper
    return Spider::Model.identity_mapper if Spider::Model.identity_mapper
    @identity_mapper ||= IdentityMapper.new
end

#identity_mapper=(im) ⇒ Object

Sets the instance’s IdentityMapper.



1280
1281
1282
# File 'lib/spiderfw/model/base_model.rb', line 1280

def identity_mapper=(im)
    @identity_mapper = im
end

#in_storage?Boolean

Returns:

  • (Boolean)


1807
1808
1809
1810
# File 'lib/spiderfw/model/base_model.rb', line 1807

def in_storage?
    return false unless primary_keys_set?
    return self.class.load(primary_keys_hash)
end

#insertObject



2062
2063
2064
2065
2066
2067
2068
# File 'lib/spiderfw/model/base_model.rb', line 2062

def insert
    saving(:insert) do
        before_save
        insert!
    end
    self
end

#insert!Object

Inserts the object in the storage Note: if the object is already present in the storage and unique indexes are enforced, this will raise an error. (See Mapper#insert).



2074
2075
2076
2077
# File 'lib/spiderfw/model/base_model.rb', line 2074

def insert!
    mapper.insert(self)
    after_save
end

#inspectObject

A compact representation of the object. Note: inspect will not autoload the object.



2268
2269
2270
2271
2272
# File 'lib/spiderfw/model/base_model.rb', line 2268

def inspect
    self.class.name+': {' +
    self.class.elements_array.select{ |el| (element_loaded?(el) || element_has_value?(el)) && !el.hidden? } \
        .map{ |el| ":#{el.name} => #{get(el.name).to_s}"}.join(',') + '}'
end

#instantiate_element(name) ⇒ Object

Returns a new instance for given element name.



1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
# File 'lib/spiderfw/model/base_model.rb', line 1285

def instantiate_element(name)
    element = self.class.elements[name]
    if element.model?
        if element.multiple?
            val = QuerySet.static(element.model)
            val.modified = true
        else
            val = element.type.new
            val.autoload = autoload?
        end
    end
    val = prepare_child(name, val)
    instance_variable_set("@#{name}", val)
    set_reverse(element, val) if element.model?
    val
end

#keys_stringObject

Returns a string with the primary keys joined by ‘,’



1730
1731
1732
# File 'lib/spiderfw/model/base_model.rb', line 1730

def keys_string
    self.class.keys_string(self.primary_keys)
end

#keys_to_conditionObject

Returns a condition based on the current primary keys



1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
# File 'lib/spiderfw/model/base_model.rb', line 1925

def keys_to_condition
    c = Condition.and
    self.class.primary_keys.each do |key|
        val = get(key)
        if (key.model?)
            c[key.name] = val.keys_to_condition
        else
            c[key.name] = val
        end
    end
    return c
end

#load(*params) ⇒ Object

Loads the object from the storage Acceptable arguments are:

  • a Query object, or

  • a Request object, or a Hash, which will be converted to a Request, or

  • a list of elements to request

It will then construct a Condition with current primary keys, and call Mapper#load Note that an error will be raised by the Mapper if not all primary keys are set.



2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
# File 'lib/spiderfw/model/base_model.rb', line 2144

def load(*params)
    if (params[0].is_a? Query)
        query = params[0]
    else
        return false unless primary_keys_set?
        query = Query.new
        if (params[0].is_a?(Request))
            query.request = params.shift
        elsif (params[0].is_a?(Hash))
            query.request = Request.new(params.shift)
        end
        
        elements = params.length > 0 ? params : self.class.elements.keys
        return true unless elements.select{ |el| !element_loaded?(el) }.length > 0
        elements.each do |name|
            query.request[name] = true
        end
        query.condition.conjunction = :and
        self.class.primary_keys.each do |key|
            query.condition[key.name] = get(key.name)
        end
    end
    #clear_values()
    return mapper.load(self, query) 
end

#mapperObject

Returns the current mapper, or instantiates a new one (base on the current storage, if set)



2020
2021
2022
2023
2024
2025
2026
2027
2028
# File 'lib/spiderfw/model/base_model.rb', line 2020

def mapper
    @storage ||= nil
    if (@storage)
        @mapper ||= self.class.get_mapper(@storage)
    else
        @mapper ||= self.class.mapper
    end
    return @mapper
end

#mapper=(mapper) ⇒ Object

Sets the current mapper



2031
2032
2033
# File 'lib/spiderfw/model/base_model.rb', line 2031

def mapper=(mapper)
    @mapper = mapper
end

#merge!(obj, only = nil) ⇒ Object

Sets all values of obj on the current object



1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
# File 'lib/spiderfw/model/base_model.rb', line 1869

def merge!(obj, only=nil)
    return self if obj.object_id == self.object_id
    obj.class.elements_array.select{ |el| 
        (only || obj.element_has_value?(el)) && !el.integrated? && !el.attributes[:computed_from]
    }.each do |el|
        next if only && !only.key?(el.name)
        val = obj.element_has_value?(el.name) ? obj.get_no_load(el) : nil
        our_val = self.element_has_value?(el.name) ? self.get_no_load(el) : nil
        next if our_val == val
        if el.model? && only && only[el.name].is_a?(Hash) && our_val \
            && val.is_a?(BaseModel) && our_val.is_a?(BaseModel)
            val = our_val.merge!(val, only[el.name])                    
        else
            Spider.logger.warn("Element #{el.name} overwritten in #{obj.inspect}") if our_val && our_val != val
        end
        set_loaded_value(el, val, false) # unless val.nil? && element.multiple?
    end
    self
end

#merge_hash(h) ⇒ Object



1889
1890
1891
1892
1893
# File 'lib/spiderfw/model/base_model.rb', line 1889

def merge_hash(h)
    h.each do |k, v|
        self.set(k.to_sym, v)
    end
end

#modified?Boolean

Returns true if any element, or any child object, has been modified

Returns:

  • (Boolean)


1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
# File 'lib/spiderfw/model/base_model.rb', line 1795

def modified?
    return true unless @_modified_elements.reject{ |key, val| !val }.empty?
    self.class.elements_array.select{ |el| 
        element_has_value?(el) && (el.type.is_a?(Spider::DataType) || el.multiple?)
    }.each do |el|
        obj = get(el)
        check = obj.is_a?(QuerySet) ? obj.modified : obj.modified?
        return true if check
    end
    return false
end

#no_autoloadObject

Disables autoload. If a block is given, the current autoload setting will be restored after yielding.



1651
1652
1653
1654
1655
1656
1657
1658
1659
# File 'lib/spiderfw/model/base_model.rb', line 1651

def no_autoload
    prev_autoload = autoload?
    self.autoload = false
    if block_given?
        yield
        self.autoload = prev_autoload
    end
    return prev_autoload
end

#notify_observers(element_name, new_val) ⇒ Object

Calls the observers for element_name



1978
1979
1980
1981
1982
1983
1984
1985
1986
# File 'lib/spiderfw/model/base_model.rb', line 1978

def notify_observers(element_name, new_val) #:nodoc:
    if @value_observers && @value_observers[element_name]
        @value_observers[element_name].each{ |proc| proc.call(self, element_name, new_val) }
    end
    if @all_values_observers
        @all_values_observers.each{ |proc| proc.call(self, element_name, new_val) }
    end
    self.class.notify_observers(element_name, new_val, self)
end

#obj_pks(obj, klass) ⇒ Object



2426
2427
2428
2429
2430
2431
2432
2433
# File 'lib/spiderfw/model/base_model.rb', line 2426

def obj_pks(obj, klass)
    unless obj
        return klass.primary_keys.length > 1 ? [] : nil
    end
    pks = obj.primary_keys
    return pks[0] if pks.length == 1
    return pks
end

#observe_all_values(&proc) ⇒ Object

The given block will be called whenever a value is modified. The block will be passed three arguments: the object, the element name, and the previous value Example:

obj.observe_all_values do |instance, element_name, old_val|
  puts "#{element_name} for object #{instance} has changed from #{old_val} to #{instance.get(element_name) }"


1948
1949
1950
1951
# File 'lib/spiderfw/model/base_model.rb', line 1948

def observe_all_values(&proc)
    @all_values_observers ||= []
    @all_values_observers << proc
end

#observe_element(element_name, &proc) ⇒ Object



1953
1954
1955
1956
1957
# File 'lib/spiderfw/model/base_model.rb', line 1953

def observe_element(element_name, &proc)
    @value_observers ||= {}
    @value_observers[element_name] ||= []
    @value_observers[element_name] << proc
end

#polymorphic_become(model) ⇒ Object

Converts the object to the instance of a subclass for which this model is polymorphic.



1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
# File 'lib/spiderfw/model/base_model.rb', line 1584

def polymorphic_become(model)
    return self if self.is_a?(model)
    unless self.class.polymorphic_models && self.class.polymorphic_models[model]
        sup = model.superclass
        while sup < Spider::Model::BaseModel && !self.class.polymorphic_models[sup]
            sup = sup.superclass
        end
        raise ModelException, "#{self.class} is not polymorphic for #{model}" unless self.class.polymorphic_models[sup]
        sup_poly = polymorphic_become(sup)
        return sup_poly.polymorphic_become(model)
    end
    el = self.class.polymorphic_models[model][:through]
    obj = model.new(el => self)
    obj = Spider::Model.identity_mapper.get(obj) if Spider::Model.identity_mapper
    obj.element_loaded(el)
    return obj
end

#prepare_child(name, obj) ⇒ Object

Prepares an object that is being set as a child.



1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
# File 'lib/spiderfw/model/base_model.rb', line 1303

def prepare_child(name, obj)
    element = self.class.elements[name]
    if obj.nil?
        obj = QuerySet.static(element.model) if element.multiple?
        return obj 
    end
    if (element.model?)
        # convert between junction and real type if needed
        if element.attributes[:junction]
            if obj.is_a?(QuerySet) 
                obj.no_autoload do
                    if (element.attributes[:keep_junction] && obj.model == element.type)
                        qs = QuerySet.new(element.model)
                        obj.each{ |el_obj| 
                            qs << {element.reverse => self, element.attributes[:junction_their_element] => el_obj}
                        }
                        obj = qs
                    elsif (!element.attributes[:keep_junction] && obj.model == element.model)
                        instance_variable_set("@#{element.name}_junction", obj)
                        qs = QuerySet.new(element.type, obj.map{ |el_obj| el_obj.get(element.attributes[:junction_their_element])})

                        obj = qs
                    end 
                end
            else
                if (!element.attributes[:keep_junction] && obj.class == element.model)
                    obj = obj.get(element.attributes[:junction_their_element])
                end
            end
        end
        self.class.elements_array.select{ |el| el.attributes[:fixed] }.each do |el|
            if el.integrated_from == element
                obj.set(el.integrated_from_element, el.attributes[:fixed])
            end
        end
        obj.identity_mapper = self.identity_mapper if obj.respond_to?(:identity_mapper)
        if (element.multiple? && element.attributes[:junction] && element.attributes[:keep_junction])
            obj.append_element = element.attributes[:junction_their_element]
        end
        if (element.attributes[:set] && element.attributes[:set].is_a?(Hash))
            element.attributes[:set].each{ |k, v| obj.set(k, v) }
            obj.reset_modified_elements(*element.attributes[:set].keys)
            # FIXME: is it always ok to not set the element as modified? But otherwise sub objects
            # are always saved (and that's definitely no good)
        end
        if element.type == self.class.superclass && self.class.extended_models[element.type] && self.class.extended_models[element.type] == element.name
            obj._subclass_object = self
        end
    else
        obj = prepare_value(element, obj)
    end
    return obj
end

#prepare_value(element, value) ⇒ Object



1523
1524
1525
# File 'lib/spiderfw/model/base_model.rb', line 1523

def prepare_value(element, value)
    self.class.prepare_value(element, value)
end

#primary_keysObject

Returns an array of current primary key values



1715
1716
1717
1718
1719
1720
# File 'lib/spiderfw/model/base_model.rb', line 1715

def primary_keys
    self.class.primary_keys.map{ |k|
        val = get(k)
        k.model? && val ? val.primary_keys : val
    }
end

#primary_keys_hashObject

Returns an hash of primary keys names and values



1723
1724
1725
1726
1727
# File 'lib/spiderfw/model/base_model.rb', line 1723

def primary_keys_hash
    h = {}
    self.class.primary_keys.each{ |k| h[k.name] = get(k) }
    h
end

#primary_keys_set?Boolean

Returns true if all primary keys have a value; false if some primary key is not set or the model has no primary key

Returns:

  • (Boolean)


1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
# File 'lib/spiderfw/model/base_model.rb', line 1848

def primary_keys_set?
    primary_keys = self.class.primary_keys
    return false unless primary_keys.length > 0
    primary_keys.each do |el|
        if (el.integrated?)
            return false unless (int_obj = instance_variable_get(:"@#{el.integrated_from.name}"))
            #return false unless int_obj.instance_variable_get(:"@#{el.integrated_from_element}")
            return false unless int_obj.element_has_value?(el.integrated_from_element)
        else
            return false unless self.instance_variable_get(:"@#{el.name}")
        end
    end
    return true
end

#remove_association(element, object) ⇒ Object



2177
2178
2179
# File 'lib/spiderfw/model/base_model.rb', line 2177

def remove_association(element, object)
    mapper.delete_element_associations(self, element, object)
end

#reset_modified_elements(*elements) ⇒ Object

Resets modified elements



1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
# File 'lib/spiderfw/model/base_model.rb', line 1828

def reset_modified_elements(*elements) #:nodoc:
    if (elements.length > 0)
        elements.each{ |el_name| @_modified_elements.delete(el_name) }
    else
        @_modified_elements = {}
    end
    elements = self.class.elements_array.map{ |el| el.name } if elements.empty?
    elements.each do |el_name|
        val = instance_variable_get("@#{el_name}")
        val.modified = false if val.is_a?(QuerySet)
    end
    self.class.elements_array.select{ |el| el.attributes[:integrated_model] && self.element_has_value?(el) }.each do |el|
        self.get(el).reset_modified_elements(elements)
    end

    nil
end

#respond_to?(symbol, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
# File 'lib/spiderfw/model/base_model.rb', line 2220

def respond_to?(symbol, include_private=false)
    return true if super
    if (self.class.attributes[:integrated_models])
        self.class.attributes[:integrated_models].each do |model, name|
            if (model.method_defined?(symbol))
                return true
            end
        end
    end
    return false
end

#saveObject

Saves the object to the storage (see Mapper#save)



2041
2042
2043
2044
2045
2046
2047
# File 'lib/spiderfw/model/base_model.rb', line 2041

def save
    saving(:save) do
        before_save
        save!
    end
    self
end

#save!Object



2049
2050
2051
2052
2053
# File 'lib/spiderfw/model/base_model.rb', line 2049

def save!
    mapper.save(self)
    after_save
    self
end

#save_allObject

Saves the object and all child objects to the storage (see Mapper#save_all)



2057
2058
2059
2060
# File 'lib/spiderfw/model/base_model.rb', line 2057

def save_all
    mapper.save_all(self)
    self
end

#save_modeObject

Sets autoload to :save_mode; elements will be autoloaded only one by one, so that any already set data will not be overwritten If a block is given, the current autoload setting will be restored after yielding.



1664
1665
1666
1667
1668
1669
1670
1671
1672
# File 'lib/spiderfw/model/base_model.rb', line 1664

def save_mode
    prev_autoload = autoload?
    self.autoload = :save_mode unless prev_autoload == false
    if (block_given?)
        yield
        self.autoload = prev_autoload
    end
    return prev_autoload
end

#saving(mode, &proc) ⇒ Object



2128
2129
2130
2131
2132
2133
2134
2135
# File 'lib/spiderfw/model/base_model.rb', line 2128

def saving(mode, &proc)
    @_saving ||= { :prev_autoload => save_mode }
    if unit_of_work_available?
        Spider.current[:unit_of_work].add(self, mode)
    else
        yield
    end
end

#set(element, value, options = {}) ⇒ Object

Sets an element. The element can be a symbol, or a dotted path String. Will call the associated setter.

cat.set('favorite_food.name', 'Salmon')


1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
# File 'lib/spiderfw/model/base_model.rb', line 1427

def set(element, value, options={})
    element = element.name if element.is_a?(Element)
    first, rest = element.to_s.split('.', 2)
    if (rest)
        first_val = send(first)
        unless first_val
            if (options[:instantiate])
                first_val = instantiate_element(first.to_sym)
                set(first, first_val)
            else
                raise "Element #{first} is nil, can't set #{element}" 
            end
        end
        return first_val.set(rest, value, options)
    end
    return send("#{element}=", value)
end

#set!(element, value, options = {}) ⇒ Object

Sets an element, instantiating intermediate objects if needed



1446
1447
1448
1449
# File 'lib/spiderfw/model/base_model.rb', line 1446

def set!(element, value, options={})
    options[:instantiate] = true
    set(element, value, options)
end

#set_hash(hash) ⇒ Object

Sets each value of a Hash.



1474
1475
1476
# File 'lib/spiderfw/model/base_model.rb', line 1474

def set_hash(hash)
    hash.each { |key, val| set(key, val) }
end

#set_loaded_value(element, value, mark_loaded = true) ⇒ Object

Sets a value without calling the associated setter; used by the mapper.



1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
# File 'lib/spiderfw/model/base_model.rb', line 1528

def set_loaded_value(element, value, mark_loaded=true)
    element_name = element.is_a?(Element) ? element.name : element
    element = self.class.elements[element_name]
    if element.integrated?
        integrated = get(element.integrated_from)
        integrated.set_loaded_value(element.integrated_from_element, value) if integrated
    else
        value = prepare_child(element.name, value)
        current = instance_variable_get("@#{element_name}")
        current.set_parent(nil, nil) if current && current.is_a?(BaseModel)
        value.set_parent(self, element.name) if value.is_a?(BaseModel)
        instance_variable_set("@#{element_name}", value)
    end
    value.loaded = true if (value.is_a?(QuerySet))
    element_loaded(element_name) if mark_loaded
    set_reverse(element, value) if element.model? && value
    @_modified_elements[element_name] = false
end

#set_modified(request) ⇒ Object

Given elements are set as modified



1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
# File 'lib/spiderfw/model/base_model.rb', line 1813

def set_modified(request) #:nodoc:
    request = {request => true} unless request.is_a?(Hash)
    request.each do |key, val| # FIXME: go deep
        key = key.name if key.is_a?(Element)
        @_modified_elements[key] = true
        el = self.class.elements[key.to_sym]
        next unless el
        if el.integrated? && sub = self.get(el.integrated_from)
            sub.set_modified(el.integrated_from_element)
        end
        
    end
end

#set_parent(obj, element) ⇒ Object

 Sets the object currently containing this one (BaseModel or QuerySet)



1373
1374
1375
1376
# File 'lib/spiderfw/model/base_model.rb', line 1373

def set_parent(obj, element)
    @_parent = obj
    @_parent_element = element
end

#set_reverse(element, obj) ⇒ Object



1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
# File 'lib/spiderfw/model/base_model.rb', line 1378

def set_reverse(element, obj)
    return unless element.model? && obj
    if element.has_single_reverse? && (!element.attributes[:junction] || element.attributes[:keep_junction])
        unless element.multiple?
            val = obj.get_no_load(element.reverse)
            return if val && val.object_id == self.object_id
        end
        obj.set(element.reverse, self)
    end
end

#set_values(values) ⇒ Object



1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
# File 'lib/spiderfw/model/base_model.rb', line 1246

def set_values(values)
    if (values.is_a? Hash)
        values.keys.select{ |k| 
            k = k.name if k.is_a?(Element)
            self.class.elements[k.to_sym] && self.class.elements[k.to_sym].primary_key? 
        }.each do |k|
            set!(k, values[k])
        end
        values.each do |key, val|
            set!(key, val)
        end
    elsif (values.is_a? BaseModel)
        values.each_val do |name, val|
            set(name, val) if self.class.has_element?(name)
        end
    elsif (values.is_a? Array)
        self.class.primary_keys.each_index do |i|
            set(self.class.primary_keys[i], values[i])
        end
     # Single unset key, single value
    elsif ((empty_keys = self.class.primary_keys.select{ |key| !element_has_value?(key) }).length == 1)
        set(empty_keys[0], values)
    else
        raise ArgumentError, "Don't know how to construct a #{self.class} from #{values.inspect}"
    end
end

#storageObject

Returns the current @storage, or instantiates the default calling Spider::BaseModel.storage



2005
2006
2007
# File 'lib/spiderfw/model/base_model.rb', line 2005

def storage
    return @storage || self.class.storage
end

#subclass(model) ⇒ Object

Converts the object to the instance of a subclass. This will instantiate the model passed as an argument, and set each value of the current object on the new one. No checks are made that this makes sense, so the method will fail if the “subclass” does not contain all of the current model’s elements.



1612
1613
1614
1615
1616
1617
1618
# File 'lib/spiderfw/model/base_model.rb', line 1612

def subclass(model)
    obj = model.new
    self.class.elements_array.each do |el|
        obj.set(el, self.get(el)) if element_has_value?(el) && model.elements[el.name]
    end
    return obj
end

#to_hashObject

Returns a element_name => value Hash



2409
2410
2411
2412
2413
2414
2415
# File 'lib/spiderfw/model/base_model.rb', line 2409

def to_hash()
    h = {}
    self.class.elements.select{ |name, el| element_loaded? el }.each do |name, el|
        h[name] = get(name)
    end
    return h
end

#to_json(state = nil, &proc) ⇒ Object

Returns a JSON representation of the object.

The tree will be traversed outputting all encountered objects; when an already seen object is met, the primary keys will be output (as a single value if one, as an array if many) and traversing will stop.

For more fine-grained control of the output, it is better to use the #cut method and call to_json on it.



2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
# File 'lib/spiderfw/model/base_model.rb', line 2281

def to_json(state=nil, &proc)
require 'json'
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
if (@tmp_json_seen && !block_given?)
    pks = self.class.primary_keys.map{ |k| get(k).to_json }
    pks = pks[0] if pks.length == 1
    return pks.to_json
end
@tmp_json_seen = true
json = ""
#Spider::Model.with_identity_mapper do |im|
self.class.elements_array.select{ |el| el.attributes[:integrated_model] }.each do |el|
    (int = get(el)) && int.instance_variable_set("@tmp_json_seen", true)
end
if (block_given?)
    select_elements = Proc.new{ true }
else
    select_elements = Proc.new{ |name, el|
        !el.hidden?
        #  &&
        # #!el.attributes[:integrated_model]  && 
        # (element_has_value?(el) || (el.integrated? && element_has_value?(el.integrated_from)))
    }
end


json = "{" +
self.class.elements.select(&select_elements).map{ |name, el|
    if (block_given?)
        val = yield(self, el)
        val ? "#{name.to_json}: #{val.to_json}" : nil
    else
        val = get(name)
        if (el.type == 'text' || el.type == 'longText')
            val = ic.iconv(val + ' ')[0..-2]
        end
        val = val.to_json
        "#{name.to_json}: #{val}"
    end
    }.select{ |pair| pair}.join(',') + "}"
    @tmp_json_seen = false
    self.class.elements_array.select{ |el| el.attributes[:integrated_model] }.each do |el|
        (int = get(el)) && int.instance_variable_set("@tmp_json_seen", false)
    end
    #end
    return json
end

#to_sObject

Returns a descriptive string for the object. By default this method returns the value of the first String element, if any; otherwise, the string representation of the first element of any type. Descendant classes may well provide a better representation.



2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
# File 'lib/spiderfw/model/base_model.rb', line 2236

def to_s
    desc_elements = self.class.elements_array.select{ |el| el.attributes[:desc] }
    unless desc_elements.empty?
        return desc_elements.sort{ |a, b| 
            ad = a.attributes[:desc]; bd = b.attributes[:desc]
            if ad == true && bd == true
                0
            elsif bd == true
                -1
            elsif ad == true
                1
            else
                ad <=> bd
            end
        }.map{ |el| self.get(el).to_s }.join(' ')
    end
    self.class.each_element do |el|
        if ((el.type == String || el.type == Text) && !el.primary_key?)
            v = get(el)
            return v ? v.to_s : ''
        end
    end
    el = self.class.elements_array[0]
    if element_has_value?(el)
        v = get(el)
        return v  ? v.to_s : ''
    end
    return ''
end

#to_yaml(params = {}) ⇒ Object

Returns a yaml representation of the object. Will try to autoload all elements, unless autoload is false; foreign keys will be expressed as an array if multiple, as a single primary key value otherwise



2419
2420
2421
2422
# File 'lib/spiderfw/model/base_model.rb', line 2419

def to_yaml(params={})
    require 'yaml'
    return YAML::dump(to_yaml_h(params))
end

#to_yaml_h(params = {}) ⇒ Object



2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
# File 'lib/spiderfw/model/base_model.rb', line 2424

def to_yaml_h(params={})
    h = {}
    def obj_pks(obj, klass)
        unless obj
            return klass.primary_keys.length > 1 ? [] : nil
        end
        pks = obj.primary_keys
        return pks[0] if pks.length == 1
        return pks
    end 

    self.class.elements_array.each do |el|
        next if params[:except] && params[:except].include?(el.name)
        if (el.model?)
            obj = get(el)
            if !obj
               h[el.name] = nil 
            elsif (el.multiple?)
                h[el.name] = obj.map{ |o| obj_pks(o, el.model) }
            else
                h[el.name] = obj_pks(obj, el.model)
            end
        else
            h[el.name] = get(el)
        end
    end
    h
end

#unit_of_work_available?Boolean

Returns:

  • (Boolean)


2094
2095
2096
# File 'lib/spiderfw/model/base_model.rb', line 2094

def unit_of_work_available?
    Spider.current[:unit_of_work] && !Spider.current[:unit_of_work].running?
end

#updateObject

Updates the object in the storage Note: the update will silently fail if the object is not present in the storage (see Mapper#update).



2082
2083
2084
2085
2086
2087
# File 'lib/spiderfw/model/base_model.rb', line 2082

def update
    saving(:update) do
        before_save
        update!
    end
end

#update!Object



2089
2090
2091
2092
# File 'lib/spiderfw/model/base_model.rb', line 2089

def update!
    mapper.update(self)
    after_save
end

#use_storage(storage) ⇒ Object

Instantiates the storage for the instance. Accepts a string (url or named storage) which will be passed to Spider::BaseModel.get_storage Example:

obj.use_storage('my_named_db')
obj.use_storage('db:oracle://username:password@XE')


2014
2015
2016
2017
# File 'lib/spiderfw/model/base_model.rb', line 2014

def use_storage(storage)
    @storage = self.class.get_storage(storage)
    @mapper = self.class.get_mapper(@storage)
end