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.



1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
# File 'lib/spiderfw/model/base_model.rb', line 1290

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)


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

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.



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

def attributes
  @attributes
end

.elements_orderArray (readonly)

An array of element names, in definition order.

Returns:

  • (Array)


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

def elements_order
  @elements_order
end

.integrated_modelsHash (readonly)

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

Returns:

  • (Hash)


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

def integrated_models
  @integrated_models
end

.polymorphic_modelsHash (readonly)

An Hash of polymorphic models => polymorphic params

Returns:

  • (Hash)


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

def polymorphic_models
  @polymorphic_models
end

.sequencesObject (readonly)

Model sequences.



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

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)


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

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)


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

def _modified_elements
  @_modified_elements
end

#_no_identity_mapperbool

This object won’t be put into the identity mapper

Returns:

  • (bool)


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

def _no_identity_mapper
  @_no_identity_mapper
end

#_parentBaseModel|QuerySet

Model instance or QuerySet containing the object

Returns:



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

def _parent
  @_parent
end

#_parent_elementSymbol

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

Returns:



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

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:



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

def _subclass_object
  @_subclass_object
end

#loaded_elementsHash (readonly)

An Hash of loaded elements

Returns:

  • (Hash)


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

def loaded_elements
  @loaded_elements
end

#modelClass<BaseModel] (readonly)

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

Returns:



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

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.



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

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:



557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
# File 'lib/spiderfw/model/base_model.rb', line 557

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



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

def self.all
    return self.where
end

.all_values_observersObject



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

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

.attribute(name, value) ⇒ Object

Sets a model attribute. See #self.attributes



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

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

.auto_primary_keys?Boolean

Returns:

  • (Boolean)


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

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:



739
740
741
742
# File 'lib/spiderfw/model/base_model.rb', line 739

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



859
860
861
# File 'lib/spiderfw/model/base_model.rb', line 859

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

.condition(condition) ⇒ Object

Sets a fixed condition.



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

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

.containing_moduleObject



991
992
993
994
995
996
997
# File 'lib/spiderfw/model/base_model.rb', line 991

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



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

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

.create(values) ⇒ Object



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

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



786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
# File 'lib/spiderfw/model/base_model.rb', line 786

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



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

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



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

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.



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

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:



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

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

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

end

.element_association?(element_name, association) ⇒ Boolean

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

Returns:

  • (Boolean)


1083
1084
1085
# File 'lib/spiderfw/model/base_model.rb', line 1083

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



709
710
711
712
713
714
715
716
# File 'lib/spiderfw/model/base_model.rb', line 709

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



605
606
607
608
609
610
611
# File 'lib/spiderfw/model/base_model.rb', line 605

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.



755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
# File 'lib/spiderfw/model/base_model.rb', line 755

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.



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

def self.elements
    @elements
end

.elements_arrayObject

An array of the model’s Elements.



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

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

.embeddable?Boolean

Returns:

  • (Boolean)


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

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

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

This method returns an undefined value.



818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
# File 'lib/spiderfw/model/base_model.rb', line 818

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.



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

def self.extend_queryset(qs)
end

.extended_modelsObject

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



1088
1089
1090
# File 'lib/spiderfw/model/base_model.rb', line 1088

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



1208
1209
1210
1211
# File 'lib/spiderfw/model/base_model.rb', line 1208

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.



1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
# File 'lib/spiderfw/model/base_model.rb', line 1065

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)


1263
1264
1265
1266
1267
1268
1269
1270
1271
# File 'lib/spiderfw/model/base_model.rb', line 1263

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



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

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



2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
# File 'lib/spiderfw/model/base_model.rb', line 2569

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



1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
# File 'lib/spiderfw/model/base_model.rb', line 1333

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



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

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



1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
# File 'lib/spiderfw/model/base_model.rb', line 1178

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



1351
1352
1353
# File 'lib/spiderfw/model/base_model.rb', line 1351

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!



1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
# File 'lib/spiderfw/model/base_model.rb', line 1154

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



881
882
883
884
885
886
887
888
889
890
891
892
# File 'lib/spiderfw/model/base_model.rb', line 881

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)


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

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

.in_transactionObject



2677
2678
2679
2680
2681
# File 'lib/spiderfw/model/base_model.rb', line 2677

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

.inherit_storageObject

Makes the class use the superclass storage



864
865
866
867
868
869
870
871
# File 'lib/spiderfw/model/base_model.rb', line 864

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:



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

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



646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
# File 'lib/spiderfw/model/base_model.rb', line 646

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



670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
# File 'lib/spiderfw/model/base_model.rb', line 670

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)


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

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

.keys_string(keys) ⇒ Object



1844
1845
1846
# File 'lib/spiderfw/model/base_model.rb', line 1844

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



959
960
961
962
963
964
965
966
967
# File 'lib/spiderfw/model/base_model.rb', line 959

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



979
980
981
# File 'lib/spiderfw/model/base_model.rb', line 979

def self.label_
    @label
end

.label_plural(val = nil) ⇒ Object

Sets/retrieves the plural form for the label



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

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



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

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



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

def self.list
    return self.all
end

.load(*params, &proc) ⇒ Object

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



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

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

.load_or_create(values) ⇒ Object



1329
1330
1331
# File 'lib/spiderfw/model/base_model.rb', line 1329

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)


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

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:



726
727
728
729
730
# File 'lib/spiderfw/model/base_model.rb', line 726

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.



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

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.



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

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

.mapper_include_for(params, mod) ⇒ Object



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

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


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

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

.not_embeddableObject



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

def self.not_embeddable
    @embeddable = false
end

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

Calls the observers for element_name



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

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



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

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

.observer_all_values(&proc) ⇒ Object



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

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



618
619
620
621
622
# File 'lib/spiderfw/model/base_model.rb', line 618

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



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

def self.only_embedded
    @only_embedded = true
end

.only_embedded?Boolean

Returns:

  • (Boolean)


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

def self.only_embedded?
    @only_embedded
end

.own_elementsObject

An array of non-integrated elements



1036
1037
1038
# File 'lib/spiderfw/model/base_model.rb', line 1036

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

.polymorphic(model, options) ⇒ Object

Add a subclass, allowing polymorphic queries on it.



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

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.



2300
2301
2302
# File 'lib/spiderfw/model/base_model.rb', line 2300

def self.prepare_query(query)
    query
end

.prepare_to_codeObject



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

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.



1589
1590
1591
1592
1593
1594
1595
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
# File 'lib/spiderfw/model/base_model.rb', line 1589

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.



1059
1060
1061
# File 'lib/spiderfw/model/base_model.rb', line 1059

def self.primary_keys
    @primary_keys
end

.referenced_by_junctionsObject



934
935
936
# File 'lib/spiderfw/model/base_model.rb', line 934

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

.remove_element(el) ⇒ Object

Removes a defined element

Parameters:

Returns:

  • nil



577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/spiderfw/model/base_model.rb', line 577

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:



692
693
694
695
696
697
698
699
# File 'lib/spiderfw/model/base_model.rb', line 692

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.



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

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

.short_nameObject

Underscored local name (without namespaces)



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

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

.split_keys_string(string) ⇒ Object



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

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

.static(values = nil) ⇒ Object

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



1316
1317
1318
1319
1320
1321
# File 'lib/spiderfw/model/base_model.rb', line 1316

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



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

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

.storage=(val) ⇒ Object



1144
1145
1146
# File 'lib/spiderfw/model/base_model.rb', line 1144

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

.subclassesArray

All known subclasses of this model

Returns:

  • (Array)


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

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.



812
813
814
# File 'lib/spiderfw/model/base_model.rb', line 812

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.



1195
1196
1197
1198
1199
1200
1201
1202
1203
# File 'lib/spiderfw/model/base_model.rb', line 1195

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



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

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



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

def self.to_s
    self.name
end

.use_storage(name = nil) ⇒ Object

Sets the url or the name of the storage to use



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

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



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

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



1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
# File 'lib/spiderfw/model/base_model.rb', line 1242

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.



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

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



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

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.



1789
1790
1791
1792
1793
1794
1795
1796
# File 'lib/spiderfw/model/base_model.rb', line 1789

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



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

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



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

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.



1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
# File 'lib/spiderfw/model/base_model.rb', line 1675

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



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

def after_delete
end

#after_saveObject



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

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.



1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
# File 'lib/spiderfw/model/base_model.rb', line 1468

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.



1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
# File 'lib/spiderfw/model/base_model.rb', line 1744

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)



1737
1738
1739
# File 'lib/spiderfw/model/base_model.rb', line 1737

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

#autoload?Boolean

Returns the current autoload status

Returns:

  • (Boolean)


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

def autoload?
    @_autoload
end

#become(model) ⇒ Object



1711
1712
1713
1714
1715
# File 'lib/spiderfw/model/base_model.rb', line 1711

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

#before_deleteObject



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

def before_delete
end

#before_saveObject



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

def before_save
end

#clear_valuesObject

Sets all values to nil



2287
2288
2289
2290
2291
# File 'lib/spiderfw/model/base_model.rb', line 2287

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



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

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"


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

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



2211
2212
2213
2214
2215
2216
2217
# File 'lib/spiderfw/model/base_model.rb', line 2211

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

#delete!Object



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

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

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



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

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



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
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
# File 'lib/spiderfw/model/base_model.rb', line 2587

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

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

#eachObject

Iterates over elements and yields name-value pairs



1810
1811
1812
1813
1814
# File 'lib/spiderfw/model/base_model.rb', line 1810

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



1817
1818
1819
1820
1821
# File 'lib/spiderfw/model/base_model.rb', line 1817

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)


1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
# File 'lib/spiderfw/model/base_model.rb', line 1858

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.



1657
1658
1659
1660
# File 'lib/spiderfw/model/base_model.rb', line 1657

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)


1663
1664
1665
1666
# File 'lib/spiderfw/model/base_model.rb', line 1663

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)


1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
# File 'lib/spiderfw/model/base_model.rb', line 1879

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)


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

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



1499
1500
1501
# File 'lib/spiderfw/model/base_model.rb', line 1499

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)


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

def empty?
    return !@_has_values
end

#eql?(other) ⇒ Boolean

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

Returns:

  • (Boolean)


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

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


1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
# File 'lib/spiderfw/model/base_model.rb', line 1512

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



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

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



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

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.



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

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

#identity_mapperObject

Returns the instance’s IdentityMapper



1383
1384
1385
1386
# File 'lib/spiderfw/model/base_model.rb', line 1383

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.



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

def identity_mapper=(im)
    @identity_mapper = im
end

#in_storage?Boolean

Returns:

  • (Boolean)


1916
1917
1918
1919
# File 'lib/spiderfw/model/base_model.rb', line 1916

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

#insertObject



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

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



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

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

#inspectObject

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



2384
2385
2386
2387
2388
# File 'lib/spiderfw/model/base_model.rb', line 2384

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

#instantiate_element(name) ⇒ Object

Returns a new instance for given element name.



1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
# File 'lib/spiderfw/model/base_model.rb', line 1394

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



1839
1840
1841
# File 'lib/spiderfw/model/base_model.rb', line 1839

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

#keys_to_conditionObject

Returns a condition based on the current primary keys



2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
# File 'lib/spiderfw/model/base_model.rb', line 2032

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.



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

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)



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

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



2138
2139
2140
# File 'lib/spiderfw/model/base_model.rb', line 2138

def mapper=(mapper)
    @mapper = mapper
end

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

Sets all values of obj on the current object



1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
# File 'lib/spiderfw/model/base_model.rb', line 1976

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



1996
1997
1998
1999
2000
# File 'lib/spiderfw/model/base_model.rb', line 1996

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)


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

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.



1760
1761
1762
1763
1764
1765
1766
1767
1768
# File 'lib/spiderfw/model/base_model.rb', line 1760

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



2085
2086
2087
2088
2089
2090
2091
2092
2093
# File 'lib/spiderfw/model/base_model.rb', line 2085

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



2542
2543
2544
2545
2546
2547
2548
2549
# File 'lib/spiderfw/model/base_model.rb', line 2542

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


2055
2056
2057
2058
# File 'lib/spiderfw/model/base_model.rb', line 2055

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

#observe_element(element_name, &proc) ⇒ Object



2060
2061
2062
2063
2064
# File 'lib/spiderfw/model/base_model.rb', line 2060

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.



1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
# File 'lib/spiderfw/model/base_model.rb', line 1693

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.



1412
1413
1414
1415
1416
1417
1418
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
# File 'lib/spiderfw/model/base_model.rb', line 1412

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



1632
1633
1634
# File 'lib/spiderfw/model/base_model.rb', line 1632

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

#primary_keysObject

Returns an array of current primary key values



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

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



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

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)


1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
# File 'lib/spiderfw/model/base_model.rb', line 1955

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



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

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

#reset_modified_elements(*elements) ⇒ Object

Resets modified elements



1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
# File 'lib/spiderfw/model/base_model.rb', line 1938

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)


2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
# File 'lib/spiderfw/model/base_model.rb', line 2336

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)



2148
2149
2150
2151
2152
2153
2154
# File 'lib/spiderfw/model/base_model.rb', line 2148

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

#save!Object



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

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)



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

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.



1773
1774
1775
1776
1777
1778
1779
1780
1781
# File 'lib/spiderfw/model/base_model.rb', line 1773

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



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

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)


2249
2250
2251
# File 'lib/spiderfw/model/base_model.rb', line 2249

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


1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
# File 'lib/spiderfw/model/base_model.rb', line 1536

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



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

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

#set_hash(hash) ⇒ Object

Sets each value of a Hash.



1583
1584
1585
# File 'lib/spiderfw/model/base_model.rb', line 1583

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.



1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
# File 'lib/spiderfw/model/base_model.rb', line 1637

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

#set_modified(request) ⇒ Object

Given elements are set as modified



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

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)



1482
1483
1484
1485
# File 'lib/spiderfw/model/base_model.rb', line 1482

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

#set_reverse(element, obj) ⇒ Object



1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
# File 'lib/spiderfw/model/base_model.rb', line 1487

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



1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
# File 'lib/spiderfw/model/base_model.rb', line 1355

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



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

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.



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

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



2525
2526
2527
2528
2529
2530
2531
# File 'lib/spiderfw/model/base_model.rb', line 2525

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.



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

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


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

#to_sObject

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



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

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



2535
2536
2537
2538
# File 'lib/spiderfw/model/base_model.rb', line 2535

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

#to_yaml_h(params = {}) ⇒ Object



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

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)


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

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



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

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

#update!Object



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

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


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

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