Class: Spider::Model::BaseModel

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

Overview

The main class for interacting with data. When not dealing with legacy storages, subclasses should use Managed instead, which provides an id and other conveniences.

Each BaseModel subclass defines a model; instances can be used as “data objects”: they will interact with the Mapper loading and saving the values associated with the BaseModel’s defined elements.

Each element defines an instance variable, a getter and a setter. If the instance is set to #autoload, when a getter is first called the mapper will fetch the value from the Storage .

Elements can be of one of the base types (Spider::Model.base_types), of a DataType, or other models. In the last case, they define a relationship between models.

Basic usage:

model Food < BaseModel
  element :name, String
end
model Animal < BaseModel
  element :name, String
  many :friends, Animal
  choice :favorite_food, Food
end

salmon = Food.new(:name => 'Salmon')
salmon.save
cat = Animal.new(:name => 'Cat', :favorite_food = salmon)
weasel = Animal.new(:name => 'Weasel', :friends => [cat])
weasel.save
cat.friends << weasel
cat.save

wizzy = Animal.load(:name => 'Weasel')
p wizzy.friends 
  => 'Cat'
p wizzy.friends[0].favorite_food
  => 'Salmon'

bear = Animal.new(:name => 'Bear', :favorite_food = salmon)
bear.save
salmon_lovers = Animal.where{ favorite_food == salmon }
p salmon_lovers.length
  => 2
p salmon_lovers[0].name
  => 'Cat'

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, check_request_level, 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?, #log, log, method_missing, open, reopen, request_level, send_to_loggers, set_request_level, 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.



1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
# File 'lib/spiderfw/model/base_model.rb', line 1299

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)


2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
# File 'lib/spiderfw/model/base_model.rb', line 2313

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.



97
98
99
# File 'lib/spiderfw/model/base_model.rb', line 97

def attributes
  @attributes
end

.elements_orderArray (readonly)

An array of element names, in definition order.

Returns:

  • (Array)


100
101
102
# File 'lib/spiderfw/model/base_model.rb', line 100

def elements_order
  @elements_order
end

.integrated_modelsHash (readonly)

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

Returns:

  • (Hash)


103
104
105
# File 'lib/spiderfw/model/base_model.rb', line 103

def integrated_models
  @integrated_models
end

.polymorphic_modelsHash (readonly)

An Hash of polymorphic models => polymorphic params

Returns:

  • (Hash)


106
107
108
# File 'lib/spiderfw/model/base_model.rb', line 106

def polymorphic_models
  @polymorphic_models
end

.sequencesObject (readonly)

Model sequences.



109
110
111
# File 'lib/spiderfw/model/base_model.rb', line 109

def sequences
  @sequences
end

Instance Attribute Details

#_check_if_savedbool

If true, forces the mapper to check for existance of the object in the storage

Returns:

  • (bool)


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

def _check_if_saved
  @_check_if_saved
end

#_modified_elementsArray (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)

Returns:

  • (Array)


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

def _modified_elements
  @_modified_elements
end

#_no_identity_mapperbool

This object won’t be put into the identity mapper

Returns:

  • (bool)


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

def _no_identity_mapper
  @_no_identity_mapper
end

#_parentBaseModel|QuerySet

Model instance or QuerySet containing the object

Returns:



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

def _parent
  @_parent
end

#_parent_elementSymbol

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

Returns:



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

def _parent_element
  @_parent_element
end

#_subclass_objectClass<BaseModel]

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

Returns:



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

def _subclass_object
  @_subclass_object
end

#loaded_elementsHash (readonly)

An Hash of loaded elements

Returns:

  • (Hash)


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

def loaded_elements
  @loaded_elements
end

#modelClass<BaseModel] (readonly)

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

Returns:



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.



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

def self._added_elements(&proc)
end

.add_element(el) ⇒ void

This method returns an undefined value.

Helper method that registers an element with the model

Parameters:



566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
# File 'lib/spiderfw/model/base_model.rb', line 566

def self.add_element(el)
    @elements ||= {}
    @elements[el.name] = el
    el.definer_model ||= self
    @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



1231
1232
1233
# File 'lib/spiderfw/model/base_model.rb', line 1231

def self.all
    return self.where
end

.all_values_observersObject



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

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

.appSpider::App

Returns the parent Spider::App of the module

Returns:



143
144
145
146
147
148
149
150
# File 'lib/spiderfw/model/base_model.rb', line 143

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

.attribute(name, value) ⇒ Object

Sets a model attribute. See #self.attributes



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

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

.auto_primary_keys?Boolean

Returns:

  • (Boolean)


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

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

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

Defines an element with choice association. Shorthand for

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

Parameters:

  • name (Symbol)
  • type (Class)
  • attributes (Hash) (defaults to: {})
  • proc (Proc)

Returns:



748
749
750
751
# File 'lib/spiderfw/model/base_model.rb', line 748

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

.class_table_inheritance(params = {}) ⇒ void

This method returns an undefined value.

Externalizes the superclass elements making the superclass an external integrated element.

Parameters:

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

    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



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

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

.condition(condition) ⇒ Object

Sets a fixed condition.



883
884
885
# File 'lib/spiderfw/model/base_model.rb', line 883

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

.containing_moduleObject



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

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



1283
1284
1285
# File 'lib/spiderfw/model/base_model.rb', line 1283

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

.create(values) ⇒ Object



1332
1333
1334
1335
1336
# File 'lib/spiderfw/model/base_model.rb', line 1332

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

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

Creates an element with an InlineModel as type.

Parameters:

  • name (Symbol)

    Element name

  • hash (Hash)

    The hash to create the inline model from

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

    Attributes for the new element



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

def self.create_inline_model(name, hash, attributes={})
    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) ⇒ void

This method returns an undefined value.

Helper method that defines getter and setter for an element

Parameters:

  • name (Symbol)

    Element name



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
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
# File 'lib/spiderfw/model/base_model.rb', line 439

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

.dump_element(el) ⇒ Object



2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
# File 'lib/spiderfw/model/base_model.rb', line 2687

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.



1055
1056
1057
1058
1059
1060
# File 'lib/spiderfw/model/base_model.rb', line 1055

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

.element(name, type, attributes = {}) { ... } ⇒ Element

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.

Parameters:

  • name (Symbol)
  • type (Class)
  • attributes (Hash) (defaults to: {})

Yields:

  • If a block is given, will be called in the context of the junction model

Returns:



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
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/spiderfw/model/base_model.rb', line 228

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)


1092
1093
1094
# File 'lib/spiderfw/model/base_model.rb', line 1092

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

.element_attributes(element_name, attributes) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • element_name (Symbol)
  • attributes (Hash)

    Attributes to set



718
719
720
721
722
723
724
725
# File 'lib/spiderfw/model/base_model.rb', line 718

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) ⇒ void

This method returns an undefined value.

Called when an element is defined



614
615
616
617
618
619
620
# File 'lib/spiderfw/model/base_model.rb', line 614

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

Defines an element which returns a query on another one.

Parameters:

  • name (Symbol)

    Name of the new element

  • element_name (Sybmol)

    Name of the element to run the query on

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

    Attributes for the new elements. Must have a :condition attribute.



764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
# File 'lib/spiderfw/model/base_model.rb', line 764

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.



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

def self.elements
    @elements
end

.elements_arrayObject

An array of the model’s Elements.



1050
1051
1052
# File 'lib/spiderfw/model/base_model.rb', line 1050

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

.embeddable?Boolean

Returns:

  • (Boolean)


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

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

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

This method returns an undefined value.



827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
# File 'lib/spiderfw/model/base_model.rb', line 827

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.



1288
1289
# File 'lib/spiderfw/model/base_model.rb', line 1288

def self.extend_queryset(qs)
end

.extended_modelsObject

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



1097
1098
1099
# File 'lib/spiderfw/model/base_model.rb', line 1097

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



1217
1218
1219
1220
# File 'lib/spiderfw/model/base_model.rb', line 1217

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.



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

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)


1272
1273
1274
1275
1276
1277
1278
1279
1280
# File 'lib/spiderfw/model/base_model.rb', line 1272

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



2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
# File 'lib/spiderfw/model/base_model.rb', line 2627

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



2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
# File 'lib/spiderfw/model/base_model.rb', line 2573

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, static = false) ⇒ Object



1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
# File 'lib/spiderfw/model/base_model.rb', line 1342

def self.get(values, static=false)
    return static ? self.static(values) : 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)
    curr.autoload = false if static
    return curr if curr
    obj = static ? self.static(values) : self.new(values)
    Spider::Model.identity_mapper.put(obj)
    obj
end

.get_element(el) ⇒ Object



1008
1009
1010
1011
1012
1013
# File 'lib/spiderfw/model/base_model.rb', line 1008

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



1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
# File 'lib/spiderfw/model/base_model.rb', line 1187

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_static(values) ⇒ Object



1360
1361
1362
# File 'lib/spiderfw/model/base_model.rb', line 1360

def self.get_static(values)
    self.get(values, true)
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!



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

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



890
891
892
893
894
895
896
897
898
899
900
901
# File 'lib/spiderfw/model/base_model.rb', line 890

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)


1063
1064
1065
# File 'lib/spiderfw/model/base_model.rb', line 1063

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

.in_transactionObject



2681
2682
2683
2684
2685
# File 'lib/spiderfw/model/base_model.rb', line 2681

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

.inherit_storageObject

Makes the class use the superclass storage



873
874
875
876
877
878
879
880
# File 'lib/spiderfw/model/base_model.rb', line 873

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) ⇒ void

This method returns an undefined value.

Copies this class’ elements to the subclass.

Parameters:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/spiderfw/model/base_model.rb', line 117

def self.inherited(subclass) #:nodoc:
    # FIXME: might need to clone every element
    @subclasses ||= []
    @subclasses << subclass
    each_element do |el|
        unless el.attributes[:local_pk]
            cl_el = el.clone
            cl_el.definer_model = el.definer_model
            subclass.add_element(cl_el) 
        end
    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 = {}) ⇒ void

This method returns an undefined value.

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

Parameters:

  • element_name (Symbol)
  • params (Hash) (defaults to: {})

    Params can be:

    • :except Excludes some elements of the integrated model

    • :overwrite Allows overwriting this model’s elements with the integrated model’s ones.

    • :keep_pks Integrate the model’s primary keys as well

    • :hidden Set the :hidden attribute on integrated elements

    • :mapping An Hash of new names to assign to integrated elements



655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
# File 'lib/spiderfw/model/base_model.rb', line 655

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 = {}) ⇒ void

This method returns an undefined value.

Integrates a single element from a model. See integrate

Parameters:

  • element_name (Symbol)
  • element_element (Symbol)

    The element inside the element’s model to integrate

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

    See integrate



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

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] # deprecated
    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)


1015
1016
1017
# File 'lib/spiderfw/model/base_model.rb', line 1015

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

.keys_string(keys) ⇒ Object



1853
1854
1855
# File 'lib/spiderfw/model/base_model.rb', line 1853

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



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

def self.label(sing=nil, plur=nil)
    @label = sing if sing
    @label_plural = plur if plur
    unless sing
        Spider::GetText.in_domain(self.app.short_name){
            _(@label || self.name || '')
        }
    end
end

.label_Object



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

def self.label_
    @label
end

.label_plural(val = nil) ⇒ Object

Sets/retrieves the plural form for the label



979
980
981
982
983
984
985
986
# File 'lib/spiderfw/model/base_model.rb', line 979

def self.label_plural(val=nil)
    @label_plural = val if (val)
    unless val
        Spider::GetText.in_domain(self.app.short_name){
            _(@label_plural || self.name || '')
        }
    end
end

.label_plural_Object



992
993
994
# File 'lib/spiderfw/model/base_model.rb', line 992

def self.label_plural_
    @label_plural
end

.listObject

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



1237
1238
1239
# File 'lib/spiderfw/model/base_model.rb', line 1237

def self.list
    return self.all
end

.load(*params, &proc) ⇒ Object

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



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

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

.load_or_create(values) ⇒ Object



1338
1339
1340
# File 'lib/spiderfw/model/base_model.rb', line 1338

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)


957
958
959
# File 'lib/spiderfw/model/base_model.rb', line 957

def self.managed?
    return false
end

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

Defines a multiple element. Equivalent to calling

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

See also element.

Parameters:

  • name (Symbol)
  • type (Class)
  • attributes (Hash) (defaults to: {})
  • proc (Proc)

Returns:



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

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.



1182
1183
1184
# File 'lib/spiderfw/model/base_model.rb', line 1182

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.



1106
1107
1108
1109
# File 'lib/spiderfw/model/base_model.rb', line 1106

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

.mapper_include_for(params, mod) ⇒ Object



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

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, ...)


755
756
757
758
# File 'lib/spiderfw/model/base_model.rb', line 755

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

.not_embeddableObject



1019
1020
1021
# File 'lib/spiderfw/model/base_model.rb', line 1019

def self.not_embeddable
    @embeddable = false
end

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

Calls the observers for element_name



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

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



2079
2080
2081
2082
# File 'lib/spiderfw/model/base_model.rb', line 2079

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

.observer_all_values(&proc) ⇒ Object



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

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

.on_element_defined(el_name, &proc) ⇒ Object

Registers a block that will be called whenever an element is defined.

The block will be called with the created element as an argument.

Parameters:

  • element_name (Symbol)
  • proc (Proc)

    A block to execute



627
628
629
630
631
# File 'lib/spiderfw/model/base_model.rb', line 627

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



1027
1028
1029
# File 'lib/spiderfw/model/base_model.rb', line 1027

def self.only_embedded
    @only_embedded = true
end

.only_embedded?Boolean

Returns:

  • (Boolean)


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

def self.only_embedded?
    @only_embedded
end

.own_elementsObject

An array of non-integrated elements



1045
1046
1047
# File 'lib/spiderfw/model/base_model.rb', line 1045

def self.own_elements
    elements_array.reject{ |el| el.integrated? }
end

.polymorphic(model, options) ⇒ Object

Add a subclass, allowing polymorphic queries on it.



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

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.



2304
2305
2306
# File 'lib/spiderfw/model/base_model.rb', line 2304

def self.prepare_query(query)
    query
end

.prepare_to_codeObject



2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
# File 'lib/spiderfw/model/base_model.rb', line 2727

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.



1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
# File 'lib/spiderfw/model/base_model.rb', line 1598

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.



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

def self.primary_keys
    @primary_keys
end

.referenced_by_junctionsObject



943
944
945
# File 'lib/spiderfw/model/base_model.rb', line 943

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

.remove_element(el) ⇒ Object

Removes a defined element

Parameters:

Returns:

  • nil



586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
# File 'lib/spiderfw/model/base_model.rb', line 586

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) ⇒ void

This method returns an undefined value.

Undoes an integration See integrate

Parameters:



701
702
703
704
705
706
707
708
# File 'lib/spiderfw/model/base_model.rb', line 701

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.



929
930
931
932
# File 'lib/spiderfw/model/base_model.rb', line 929

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

.short_nameObject

Underscored local name (without namespaces)



952
953
954
# File 'lib/spiderfw/model/base_model.rb', line 952

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

.split_keys_string(string) ⇒ Object



1857
1858
1859
# File 'lib/spiderfw/model/base_model.rb', line 1857

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

.static(values = nil) ⇒ Object

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



1325
1326
1327
1328
1329
1330
# File 'lib/spiderfw/model/base_model.rb', line 1325

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



1147
1148
1149
1150
1151
# File 'lib/spiderfw/model/base_model.rb', line 1147

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

.storage=(val) ⇒ Object



1153
1154
1155
# File 'lib/spiderfw/model/base_model.rb', line 1153

def self.storage=(val)
    @storage = val
end

.subclassesArray

All known subclasses of this model

Returns:

  • (Array)


137
138
139
# File 'lib/spiderfw/model/base_model.rb', line 137

def self.subclasses
    @subclasses || []
end

.submodelsArray

An array of other models this class points to.

Returns:

  • (Array)

    An array of models which are referenced by this one’s elements.



821
822
823
# File 'lib/spiderfw/model/base_model.rb', line 821

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.



1204
1205
1206
1207
1208
1209
1210
1211
1212
# File 'lib/spiderfw/model/base_model.rb', line 1204

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



2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
# File 'lib/spiderfw/model/base_model.rb', line 2759

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



962
963
964
# File 'lib/spiderfw/model/base_model.rb', line 962

def self.to_s
    self.name
end

.use_storage(name = nil) ⇒ Object

Sets the url or the name of the storage to use



1138
1139
1140
1141
1142
1143
# File 'lib/spiderfw/model/base_model.rb', line 1138

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



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

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



1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
# File 'lib/spiderfw/model/base_model.rb', line 1251

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 = params[0].is_a?(Condition) ? params[0] : Condition.and(params[0])
        request = params[1].is_a?(Request) ? params[1] : 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.



1120
1121
1122
1123
1124
1125
# File 'lib/spiderfw/model/base_model.rb', line 1120

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



1130
1131
1132
1133
1134
1135
# File 'lib/spiderfw/model/base_model.rb', line 1130

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.



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

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 #[]=



1571
1572
1573
1574
1575
1576
1577
1578
# File 'lib/spiderfw/model/base_model.rb', line 1571

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 #[]



1582
1583
1584
1585
1586
1587
1588
1589
# File 'lib/spiderfw/model/base_model.rb', line 1582

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.



1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
# File 'lib/spiderfw/model/base_model.rb', line 1684

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") => 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



2241
2242
# File 'lib/spiderfw/model/base_model.rb', line 2241

def after_delete
end

#after_saveObject



2235
2236
2237
2238
2239
# File 'lib/spiderfw/model/base_model.rb', line 2235

def after_save
    reset_modified_elements
    self.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.



1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
# File 'lib/spiderfw/model/base_model.rb', line 1477

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.



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

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)



1746
1747
1748
# File 'lib/spiderfw/model/base_model.rb', line 1746

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

#autoload?Boolean

Returns the current autoload status

Returns:

  • (Boolean)


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

def autoload?
    @_autoload
end

#become(model) ⇒ Object



1720
1721
1722
1723
1724
# File 'lib/spiderfw/model/base_model.rb', line 1720

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

#before_deleteObject



2229
2230
# File 'lib/spiderfw/model/base_model.rb', line 2229

def before_delete
end

#before_saveObject



2232
2233
# File 'lib/spiderfw/model/base_model.rb', line 2232

def before_save
end

#clear_valuesObject

Sets all values to nil



2291
2292
2293
2294
2295
# File 'lib/spiderfw/model/base_model.rb', line 2291

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



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

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"


2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
# File 'lib/spiderfw/model/base_model.rb', line 2482

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).



2217
2218
2219
2220
2221
2222
# File 'lib/spiderfw/model/base_model.rb', line 2217

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

#delete!Object



2224
2225
2226
2227
# File 'lib/spiderfw/model/base_model.rb', line 2224

def delete!
    mapper.delete(self)
    after_delete
end

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



2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
# File 'lib/spiderfw/model/base_model.rb', line 2654

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



2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
# File 'lib/spiderfw/model/base_model.rb', line 2591

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



1819
1820
1821
1822
1823
# File 'lib/spiderfw/model/base_model.rb', line 1819

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



1826
1827
1828
1829
1830
# File 'lib/spiderfw/model/base_model.rb', line 1826

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)


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

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.



1666
1667
1668
1669
# File 'lib/spiderfw/model/base_model.rb', line 1666

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)


1672
1673
1674
1675
# File 'lib/spiderfw/model/base_model.rb', line 1672

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)


1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
# File 'lib/spiderfw/model/base_model.rb', line 1888

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)


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

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



1508
1509
1510
# File 'lib/spiderfw/model/base_model.rb', line 1508

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)


1980
1981
1982
# File 'lib/spiderfw/model/base_model.rb', line 1980

def empty?
    return @_has_values
end

#eql?(other) ⇒ Boolean

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

Returns:

  • (Boolean)


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

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')


1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
# File 'lib/spiderfw/model/base_model.rb', line 1521

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



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

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



2030
2031
2032
2033
2034
2035
2036
2037
2038
# File 'lib/spiderfw/model/base_model.rb', line 2030

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.



1533
1534
1535
1536
1537
1538
1539
# File 'lib/spiderfw/model/base_model.rb', line 1533

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

#identity_mapperObject

Returns the instance’s IdentityMapper



1392
1393
1394
1395
# File 'lib/spiderfw/model/base_model.rb', line 1392

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.



1398
1399
1400
# File 'lib/spiderfw/model/base_model.rb', line 1398

def identity_mapper=(im)
    @identity_mapper = im
end

#in_storage?Boolean

Returns:

  • (Boolean)


1925
1926
1927
1928
# File 'lib/spiderfw/model/base_model.rb', line 1925

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

#insertObject



2178
2179
2180
2181
2182
2183
2184
# File 'lib/spiderfw/model/base_model.rb', line 2178

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).



2190
2191
2192
2193
# File 'lib/spiderfw/model/base_model.rb', line 2190

def insert!
    mapper.insert(self)
    after_save
end

#inspectObject

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



2388
2389
2390
2391
2392
# File 'lib/spiderfw/model/base_model.rb', line 2388

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.



1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
# File 'lib/spiderfw/model/base_model.rb', line 1403

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 ‘,’



1848
1849
1850
# File 'lib/spiderfw/model/base_model.rb', line 1848

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

#keys_to_conditionObject

Returns a condition based on the current primary keys



2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
# File 'lib/spiderfw/model/base_model.rb', line 2041

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.



2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
# File 'lib/spiderfw/model/base_model.rb', line 2264

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)



2136
2137
2138
2139
2140
2141
2142
2143
2144
# File 'lib/spiderfw/model/base_model.rb', line 2136

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



2147
2148
2149
# File 'lib/spiderfw/model/base_model.rb', line 2147

def mapper=(mapper)
    @mapper = mapper
end

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

Sets all values of obj on the current object



1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
# File 'lib/spiderfw/model/base_model.rb', line 1985

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



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

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)


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

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.



1769
1770
1771
1772
1773
1774
1775
1776
1777
# File 'lib/spiderfw/model/base_model.rb', line 1769

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



2094
2095
2096
2097
2098
2099
2100
2101
2102
# File 'lib/spiderfw/model/base_model.rb', line 2094

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



2546
2547
2548
2549
2550
2551
2552
2553
# File 'lib/spiderfw/model/base_model.rb', line 2546

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) }"


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

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

#observe_element(element_name, &proc) ⇒ Object



2069
2070
2071
2072
2073
# File 'lib/spiderfw/model/base_model.rb', line 2069

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.



1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
# File 'lib/spiderfw/model/base_model.rb', line 1702

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.



1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
# File 'lib/spiderfw/model/base_model.rb', line 1421

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



1641
1642
1643
# File 'lib/spiderfw/model/base_model.rb', line 1641

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

#primary_keysObject

Returns an array of current primary key values



1833
1834
1835
1836
1837
1838
# File 'lib/spiderfw/model/base_model.rb', line 1833

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



1841
1842
1843
1844
1845
# File 'lib/spiderfw/model/base_model.rb', line 1841

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)


1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
# File 'lib/spiderfw/model/base_model.rb', line 1964

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



2297
2298
2299
# File 'lib/spiderfw/model/base_model.rb', line 2297

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

#reset_modified_elements(*elements) ⇒ Object

Resets modified elements



1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
# File 'lib/spiderfw/model/base_model.rb', line 1947

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

    nil
end

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

Returns:

  • (Boolean)


2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
# File 'lib/spiderfw/model/base_model.rb', line 2340

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)



2157
2158
2159
2160
2161
2162
2163
# File 'lib/spiderfw/model/base_model.rb', line 2157

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

#save!Object



2165
2166
2167
2168
2169
# File 'lib/spiderfw/model/base_model.rb', line 2165

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)



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

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.



1782
1783
1784
1785
1786
1787
1788
1789
1790
# File 'lib/spiderfw/model/base_model.rb', line 1782

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



2244
2245
2246
2247
2248
2249
2250
2251
# File 'lib/spiderfw/model/base_model.rb', line 2244

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

#saving?Boolean

Returns:

  • (Boolean)


2253
2254
2255
# File 'lib/spiderfw/model/base_model.rb', line 2253

def saving?
    !@_saving.nil?
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')


1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
# File 'lib/spiderfw/model/base_model.rb', line 1545

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



1564
1565
1566
1567
# File 'lib/spiderfw/model/base_model.rb', line 1564

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

#set_hash(hash) ⇒ Object

Sets each value of a Hash.



1592
1593
1594
# File 'lib/spiderfw/model/base_model.rb', line 1592

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.



1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
# File 'lib/spiderfw/model/base_model.rb', line 1646

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



1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
# File 'lib/spiderfw/model/base_model.rb', line 1931

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
        next if el.attributes[:unmapped]
        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)



1491
1492
1493
1494
# File 'lib/spiderfw/model/base_model.rb', line 1491

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

#set_reverse(element, obj) ⇒ Object



1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
# File 'lib/spiderfw/model/base_model.rb', line 1496

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



1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
# File 'lib/spiderfw/model/base_model.rb', line 1364

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



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

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.



1730
1731
1732
1733
1734
1735
1736
# File 'lib/spiderfw/model/base_model.rb', line 1730

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



2529
2530
2531
2532
2533
2534
2535
# File 'lib/spiderfw/model/base_model.rb', line 2529

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.



2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
# File 'lib/spiderfw/model/base_model.rb', line 2401

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.



2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
# File 'lib/spiderfw/model/base_model.rb', line 2356

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



2539
2540
2541
2542
# File 'lib/spiderfw/model/base_model.rb', line 2539

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

#to_yaml_h(params = {}) ⇒ Object



2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
# File 'lib/spiderfw/model/base_model.rb', line 2544

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)


2210
2211
2212
# File 'lib/spiderfw/model/base_model.rb', line 2210

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).



2198
2199
2200
2201
2202
2203
# File 'lib/spiderfw/model/base_model.rb', line 2198

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

#update!Object



2205
2206
2207
2208
# File 'lib/spiderfw/model/base_model.rb', line 2205

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')


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

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