Class: Spider::Model::BaseModel

Inherits:
Object
  • Object
show all
Includes:
App::AppClass, 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?

Methods included from App::AppClass

#app, included

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.



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

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)


2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
# File 'lib/spiderfw/model/base_model.rb', line 2318

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.



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

def attributes
  @attributes
end

.elements_orderArray (readonly)

An array of element names, in definition order.

Returns:

  • (Array)


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

def elements_order
  @elements_order
end

.integrated_modelsHash (readonly)

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

Returns:

  • (Hash)


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

def integrated_models
  @integrated_models
end

.polymorphic_modelsHash (readonly)

An Hash of polymorphic models => polymorphic params

Returns:

  • (Hash)


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

def polymorphic_models
  @polymorphic_models
end

.sequencesObject (readonly)

Model sequences.



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

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)


81
82
83
# File 'lib/spiderfw/model/base_model.rb', line 81

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)


95
96
97
# File 'lib/spiderfw/model/base_model.rb', line 95

def _modified_elements
  @_modified_elements
end

#_no_identity_mapperbool

This object won’t be put into the identity mapper

Returns:

  • (bool)


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

def _no_identity_mapper
  @_no_identity_mapper
end

#_parentBaseModel|QuerySet

Model instance or QuerySet containing the object

Returns:



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

def _parent
  @_parent
end

#_parent_elementSymbol

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

Returns:



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

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:



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

def _subclass_object
  @_subclass_object
end

#loaded_elementsHash (readonly)

An Hash of loaded elements

Returns:

  • (Hash)


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

def loaded_elements
  @loaded_elements
end

#modelClass<BaseModel] (readonly)

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

Returns:



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

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.



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

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:



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

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



1229
1230
1231
# File 'lib/spiderfw/model/base_model.rb', line 1229

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

.attribute(name, value) ⇒ Object

Sets a model attribute. See #self.attributes



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

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

.auto_primary_keys?Boolean

Returns:

  • (Boolean)


994
995
996
# File 'lib/spiderfw/model/base_model.rb', line 994

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:



746
747
748
749
# File 'lib/spiderfw/model/base_model.rb', line 746

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



866
867
868
# File 'lib/spiderfw/model/base_model.rb', line 866

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

.condition(condition) ⇒ Object

Sets a fixed condition.



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

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

.containing_moduleObject



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

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



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

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

.create(values) ⇒ Object



1330
1331
1332
1333
1334
# File 'lib/spiderfw/model/base_model.rb', line 1330

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



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

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



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

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



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
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
# File 'lib/spiderfw/model/base_model.rb', line 2733

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.



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

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:



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
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
# File 'lib/spiderfw/model/base_model.rb', line 220

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
    attributes[:reverse] ||= attributes[:reverse_of]
    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.constant_defined?(assoc_type_name) )
            assoc_type = first_model.const_get(assoc_type_name)
            if (assoc_type.respond_to?(:attributes) && !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
                createP_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)
            #evita il warning 'already initialized constant News' e 'already initialized constant Contents'
            #if first_model.constant_defined?(assoc_type_name) 
            #    return nil
            #else
                assoc_type = first_model.const_set(assoc_type_name, Class.new(BaseModel))
            #end
            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)


1090
1091
1092
# File 'lib/spiderfw/model/base_model.rb', line 1090

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



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

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



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

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.



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

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.



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

def self.elements
    @elements
end

.elements_arrayObject

An array of the model’s Elements.



1048
1049
1050
# File 'lib/spiderfw/model/base_model.rb', line 1048

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

.embeddable?Boolean

Returns:

  • (Boolean)


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

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

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

This method returns an undefined value.



825
826
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
# File 'lib/spiderfw/model/base_model.rb', line 825

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.



1286
1287
# File 'lib/spiderfw/model/base_model.rb', line 1286

def self.extend_queryset(qs)
end

.extended_modelsObject

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



1095
1096
1097
# File 'lib/spiderfw/model/base_model.rb', line 1095

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



1215
1216
1217
1218
# File 'lib/spiderfw/model/base_model.rb', line 1215

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.



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

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)


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

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



2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
# File 'lib/spiderfw/model/base_model.rb', line 2673

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



2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
# File 'lib/spiderfw/model/base_model.rb', line 2613

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



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

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



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

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



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

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



1358
1359
1360
# File 'lib/spiderfw/model/base_model.rb', line 1358

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!



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

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



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

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)


1061
1062
1063
# File 'lib/spiderfw/model/base_model.rb', line 1061

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

.in_transactionObject



2727
2728
2729
2730
2731
# File 'lib/spiderfw/model/base_model.rb', line 2727

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

.inherit_storageObject

Makes the class use the superclass storage



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

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:



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

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



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

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



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

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)


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

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



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

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



986
987
988
# File 'lib/spiderfw/model/base_model.rb', line 986

def self.label_
    @label
end

.label_plural(val = nil) ⇒ Object

Sets/retrieves the plural form for the label



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

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



990
991
992
# File 'lib/spiderfw/model/base_model.rb', line 990

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



1235
1236
1237
# File 'lib/spiderfw/model/base_model.rb', line 1235

def self.list
    return self.all
end

.load(*params, &proc) ⇒ Object

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



1222
1223
1224
1225
1226
# File 'lib/spiderfw/model/base_model.rb', line 1222

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

.load_or_create(values) ⇒ Object



1336
1337
1338
# File 'lib/spiderfw/model/base_model.rb', line 1336

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)


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

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:



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

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.



1180
1181
1182
# File 'lib/spiderfw/model/base_model.rb', line 1180

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.



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

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

.mapper_include_for(params, mod) ⇒ Object



1109
1110
1111
1112
1113
# File 'lib/spiderfw/model/base_model.rb', line 1109

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


753
754
755
756
# File 'lib/spiderfw/model/base_model.rb', line 753

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

.not_embeddableObject



1017
1018
1019
# File 'lib/spiderfw/model/base_model.rb', line 1017

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



625
626
627
628
629
# File 'lib/spiderfw/model/base_model.rb', line 625

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



1025
1026
1027
# File 'lib/spiderfw/model/base_model.rb', line 1025

def self.only_embedded
    @only_embedded = true
end

.only_embedded?Boolean

Returns:

  • (Boolean)


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

def self.only_embedded?
    @only_embedded
end

.own_elementsObject

An array of non-integrated elements



1043
1044
1045
# File 'lib/spiderfw/model/base_model.rb', line 1043

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

.polymorphic(model, options) ⇒ Object

Add a subclass, allowing polymorphic queries on it.



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

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.



2309
2310
2311
# File 'lib/spiderfw/model/base_model.rb', line 2309

def self.prepare_query(query)
    query
end

.prepare_to_codeObject



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
2798
2799
2800
2801
2802
2803
# File 'lib/spiderfw/model/base_model.rb', line 2773

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.



1596
1597
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
# File 'lib/spiderfw/model/base_model.rb', line 1596

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', 'Spider::DataTypes::PK'
            value = value.to_i
        end
    end
    value
end

.primary_keysObject

An array of elements with primary_key attribute set.



1066
1067
1068
# File 'lib/spiderfw/model/base_model.rb', line 1066

def self.primary_keys
    @primary_keys
end

.referenced_by_junctionsObject



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

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

.remove_element(el) ⇒ Object

Removes a defined element

Parameters:

Returns:

  • nil



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

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:



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

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.



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

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

.short_nameObject

Underscored local name (without namespaces)



950
951
952
# File 'lib/spiderfw/model/base_model.rb', line 950

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



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

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



1145
1146
1147
1148
1149
# File 'lib/spiderfw/model/base_model.rb', line 1145

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

.storage=(val) ⇒ Object



1151
1152
1153
# File 'lib/spiderfw/model/base_model.rb', line 1151

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

.subclassesArray

All known subclasses of this model

Returns:

  • (Array)


140
141
142
# File 'lib/spiderfw/model/base_model.rb', line 140

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.



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

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.



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

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



2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
# File 'lib/spiderfw/model/base_model.rb', line 2805

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



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

def self.to_s
    self.name
end

.use_storage(name = nil) ⇒ Object

Sets the url or the name of the storage to use



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

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



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

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.



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

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



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

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



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

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



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

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



2246
2247
# File 'lib/spiderfw/model/base_model.rb', line 2246

def after_delete
end

#after_saveObject



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

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.



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

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



2234
2235
# File 'lib/spiderfw/model/base_model.rb', line 2234

def before_delete
end

#before_saveObject



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

def before_save
end

#clear_valuesObject

Sets all values to nil



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

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"


2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
# File 'lib/spiderfw/model/base_model.rb', line 2522

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



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

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

#delete!Object



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

def delete!
    mapper.delete(self)
    after_delete
    nil
end

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



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 2700

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



2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
# File 'lib/spiderfw/model/base_model.rb', line 2631

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 
    ic = Iconv.new('UTF-8//IGNORE', 'UTF-8') if RUBY_VERSION =~ /1.8/
    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
                when :String
                    if RUBY_VERSION =~ /1.8/
                        val = ic.iconv(val) if val
                    else
                        val = val = val.force_encoding(Encoding::UTF_8).encode(Encoding::UTF_8) if val && val.encoding == Encoding::ASCII_8BIT
                    end
                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



1506
1507
1508
# File 'lib/spiderfw/model/base_model.rb', line 1506

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


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

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.



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

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

#identity_mapperObject

Returns the instance’s IdentityMapper



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

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.



1396
1397
1398
# File 'lib/spiderfw/model/base_model.rb', line 1396

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
2194
# File 'lib/spiderfw/model/base_model.rb', line 2190

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

#inspectObject

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



2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
# File 'lib/spiderfw/model/base_model.rb', line 2393

def inspect
    ic = Iconv.new('UTF-8//IGNORE', 'UTF-8') if RUBY_VERSION =~ /1.8/
    enc = Spider.conf.get('storages')['default']['encoding']
    enc ||= 'UTF-8'
    
    self.class.name+': {' +
    self.class.elements_array.select{ |el| (element_loaded?(el) || element_has_value?(el)) && !el.hidden? } \
        .map{ |el|
            val = get(el.name).to_s
            if RUBY_VERSION =~ /1.8/
                val = ic.iconv(val + ' ')[0..-2] if val
            else
                begin
                    val = ((val+' ').encode('UTF-8', enc, :invalid => :replace, :undef => :replace))[0..-2] if val
                rescue EncodingError  
                    val = ''  
                end 
            end
         ":#{el.name} => #{val}"}.join(',') + '}'
end

#instantiate_element(name) ⇒ Object

Returns a new instance for given element name.



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

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.



2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
# File 'lib/spiderfw/model/base_model.rb', line 2269

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



2586
2587
2588
2589
2590
2591
2592
2593
# File 'lib/spiderfw/model/base_model.rb', line 2586

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.



1419
1420
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
# File 'lib/spiderfw/model/base_model.rb', line 1419

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



1639
1640
1641
# File 'lib/spiderfw/model/base_model.rb', line 1639

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



2302
2303
2304
# File 'lib/spiderfw/model/base_model.rb', line 2302

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)


2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
# File 'lib/spiderfw/model/base_model.rb', line 2345

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



2249
2250
2251
2252
2253
2254
2255
2256
# File 'lib/spiderfw/model/base_model.rb', line 2249

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)


2258
2259
2260
# File 'lib/spiderfw/model/base_model.rb', line 2258

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


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

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



1562
1563
1564
1565
# File 'lib/spiderfw/model/base_model.rb', line 1562

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

#set_hash(hash) ⇒ Object

Sets each value of a Hash.



1590
1591
1592
# File 'lib/spiderfw/model/base_model.rb', line 1590

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.



1644
1645
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 1644

def set_loaded_value(element, value, mark_loaded=true)
    element_name = element.is_a?(Element) ? element.name : element
    element = self.class.elements[element_name]
    unless element.blank?
        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
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)



1489
1490
1491
1492
# File 'lib/spiderfw/model/base_model.rb', line 1489

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

#set_reverse(element, obj) ⇒ Object



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

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



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

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



2569
2570
2571
2572
2573
2574
2575
# File 'lib/spiderfw/model/base_model.rb', line 2569

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.



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
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
# File 'lib/spiderfw/model/base_model.rb', line 2421

def to_json(state=nil, &proc)
require 'json'
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8') if RUBY_VERSION =~ /1.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)
        enc = Spider.conf.get('storages')['default']['encoding']
        enc ||= 'UTF-8'
        if (el.type == String || el.type == Text)
            if RUBY_VERSION =~ /1.8/
                val = ic.iconv(val + ' ')[0..-2] if val
            elsif val.encoding == "ASCII-8BIT" && val.is_a?(String)
                    begin
                        val = ((val+' ').force_encoding("UTF-8").encode('UTF-8', :invalid => :replace, :undef => :replace))[0..-2] if val
                    rescue
                        Spider.logger.error("Errore di encoding relativamente al seguente valore inserito in json #{val}")
                    ensure
                        val ||= ''
                    end
            elsif val.is_a?(String)
                begin
                    val = ((val+' ').encode('UTF-8', enc))[0..-2] if val
                rescue EncodingError 
                    val = ((val+' ').encode('UTF-8', 'UTF-8', :invalid => :replace, :undef => :replace))[0..-2] if val
                ensure
                    val ||= ''
                end
            end
        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.



2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
# File 'lib/spiderfw/model/base_model.rb', line 2361

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



2579
2580
2581
2582
# File 'lib/spiderfw/model/base_model.rb', line 2579

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

#to_yaml_h(params = {}) ⇒ Object



2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
# File 'lib/spiderfw/model/base_model.rb', line 2584

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)


2213
2214
2215
# File 'lib/spiderfw/model/base_model.rb', line 2213

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



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

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

#update!Object



2207
2208
2209
2210
2211
# File 'lib/spiderfw/model/base_model.rb', line 2207

def update!
    mapper.update(self)
    after_save
    self
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