spiderfw 0.6.23 → 0.6.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. data/CHANGELOG +10 -1
  2. data/README.rdoc +1 -1
  3. data/VERSION +1 -1
  4. data/apps/config_editor/_init.rb +1 -2
  5. data/apps/config_editor/controllers/config_editor_controller.rb +1 -7
  6. data/apps/core/admin/controllers/admin_controller.rb +1 -1
  7. data/apps/core/admin/public/css/sass/admin.css +35 -31
  8. data/apps/core/admin/public/sass/admin.scss +6 -1
  9. data/apps/core/components/widgets/crud/crud.shtml +2 -2
  10. data/apps/core/components/widgets/table/table.rb +5 -5
  11. data/apps/core/forms/tags/element_row.erb +15 -10
  12. data/apps/core/forms/widgets/form/form.rb +35 -22
  13. data/apps/core/forms/widgets/inputs/checkbox/checkbox.shtml +2 -2
  14. data/apps/core/forms/widgets/inputs/date_time/date_time.shtml +2 -2
  15. data/apps/core/forms/widgets/inputs/file_input/file_input.shtml +2 -2
  16. data/apps/core/forms/widgets/inputs/html_area/html_area.shtml +2 -2
  17. data/apps/core/forms/widgets/inputs/input/input.shtml +2 -2
  18. data/apps/core/forms/widgets/inputs/password/password.shtml +2 -2
  19. data/apps/core/forms/widgets/inputs/search_select/search_select.shtml +1 -1
  20. data/apps/core/forms/widgets/inputs/select/select.shtml +2 -2
  21. data/apps/core/forms/widgets/inputs/text/text.shtml +2 -2
  22. data/apps/core/forms/widgets/inputs/text_area/text_area.shtml +2 -2
  23. data/apps/core/forms/widgets/inputs/time_span/time_span.shtml +1 -1
  24. data/blueprints/home/config.ru +8 -0
  25. data/lib/spiderfw/app.rb +416 -224
  26. data/lib/spiderfw/cmd/commands/app.rb +243 -239
  27. data/lib/spiderfw/cmd/commands/cert.rb +421 -417
  28. data/lib/spiderfw/cmd/commands/config.rb +85 -82
  29. data/lib/spiderfw/cmd/commands/console.rb +64 -40
  30. data/lib/spiderfw/cmd/commands/content.rb +29 -25
  31. data/lib/spiderfw/cmd/commands/create.rb +58 -54
  32. data/lib/spiderfw/cmd/commands/model.rb +118 -114
  33. data/lib/spiderfw/cmd/commands/setup.rb +55 -51
  34. data/lib/spiderfw/cmd/commands/test.rb +63 -59
  35. data/lib/spiderfw/cmd/commands/webserver.rb +56 -51
  36. data/lib/spiderfw/config/options/spider.rb +4 -3
  37. data/lib/spiderfw/controller/controller.rb +2 -0
  38. data/lib/spiderfw/controller/http_controller.rb +1 -2
  39. data/lib/spiderfw/controller/mixins/static_content.rb +3 -3
  40. data/lib/spiderfw/controller/mixins/visual.rb +30 -15
  41. data/lib/spiderfw/controller/response.rb +84 -0
  42. data/lib/spiderfw/controller/session/file_session.rb +2 -2
  43. data/lib/spiderfw/http/adapters/rack.rb +12 -13
  44. data/lib/spiderfw/http/server.rb +80 -46
  45. data/lib/spiderfw/i18n/cldr.rb +6 -9
  46. data/lib/spiderfw/model/base_model.rb +103 -23
  47. data/lib/spiderfw/model/condition.rb +110 -25
  48. data/lib/spiderfw/model/mappers/db_mapper.rb +14 -6
  49. data/lib/spiderfw/model/mappers/mapper.rb +440 -197
  50. data/lib/spiderfw/model/model.rb +105 -21
  51. data/lib/spiderfw/model/model_hash.rb +9 -1
  52. data/lib/spiderfw/model/query.rb +50 -9
  53. data/lib/spiderfw/model/query_set.rb +211 -44
  54. data/lib/spiderfw/model/request.rb +28 -21
  55. data/lib/spiderfw/model/storage/base_storage.rb +125 -10
  56. data/lib/spiderfw/model/storage/db/db_storage.rb +7 -4
  57. data/lib/spiderfw/model/storage.rb +8 -1
  58. data/lib/spiderfw/setup/spider_setup_wizard.rb +9 -7
  59. data/lib/spiderfw/spider.rb +270 -43
  60. data/lib/spiderfw/templates/layout.rb +9 -4
  61. data/lib/spiderfw/templates/resources/sass.rb +3 -2
  62. data/lib/spiderfw/templates/template.rb +1 -0
  63. data/lib/spiderfw/utils/annotations.rb +3 -1
  64. data/lib/spiderfw/utils/logger.rb +1 -1
  65. data/lib/spiderfw/utils/monkey/symbol.rb +4 -2
  66. data/lib/spiderfw/utils/shared_store/file_shared_store.rb +2 -2
  67. data/lib/spiderfw/utils/thread_out.rb +3 -1
  68. data/public/css/error_page.css +83 -0
  69. data/public/js/error_page.js +5 -0
  70. data/spider.gemspec +4 -1
  71. data/templates/email/error.erb +9 -0
  72. metadata +28 -12
  73. data/apps/config_editor/widgets/edit_bool/edit_bool.rb +0 -8
  74. data/apps/config_editor/widgets/edit_bool/edit_bool.shtml +0 -5
@@ -1,22 +1,35 @@
1
1
  module Spider; module Model
2
2
 
3
+ # @abstract
3
4
  # The Mapper connects a BaseModel to a Storage; it fetches data from the Storage and converts it to objects,
4
5
  # and vice versa.
5
- # It is not usually called directly; the BaseModel provides methods for interacting with the mapper.
6
- # Its methods may be overridden with BaseModel#with_mapper, though.
7
-
6
+ #
7
+ # Each model has one instance of the mapper, retrieved by {BaseModel.mapper}. The mapper has a pointer to
8
+ # its model, and one to a {Storage} instance, which is shared between all models accessing the same storage.
9
+ #
10
+ # The BaseModel provides methods for interacting with the mapper; it is not usually called directly,
11
+ # but it can be if needed (for example, to call the {#delete_all!} method, which is not exposed on the model).
12
+ #
13
+ #
14
+ # Its methods may be overridden with BaseModel.with_mapper.
8
15
  class Mapper
16
+ # @return [BaseModel] pointer to the model instance
9
17
  attr_reader :model
18
+ # @return [Storage] pointer to the Storage instance
10
19
  attr_accessor :storage
11
- # Mapper type (:db, :hash, etc.)
20
+ # A Symbolic name for the Mapper's subclass
21
+ # @return [Symbol]
12
22
  attr_reader :type
13
23
 
14
24
  # Returns whether this Mapper can write to the storage.
25
+ # return [true]
15
26
  def self.write?
16
27
  true
17
28
  end
18
29
 
19
30
  # Takes a BaseModel class and a storage instance.
31
+ # @param [BaseModel] model
32
+ # @param [Storage] storage
20
33
  def initialize(model, storage)
21
34
  @model = model
22
35
  @storage = storage
@@ -28,12 +41,15 @@ module Spider; module Model
28
41
 
29
42
  # Configuration methods
30
43
 
31
- # Sets that the given elements will not be processed.
44
+ # Tells to the mapper that the given elements should not be handled.
45
+ # @param [*Element] Elements which should not be mapped
46
+ # @return [void]
32
47
  def no_map(*els)
33
48
  els.each{ |el| @no_map_elements[el] = true }
34
49
  end
35
50
 
36
51
  # Returns whether the given element can be handled by the mapper.
52
+ # @return [bool]
37
53
  def mapped?(element)
38
54
  element = element.name if (element.is_a? Element)
39
55
  element = @model.elements[element]
@@ -43,25 +59,28 @@ module Spider; module Model
43
59
  return true
44
60
  end
45
61
 
62
+ # @param [Symbol|Element] element
63
+ # @return [bool] True if the mapper can sort by this element
46
64
  def sortable?(element)
47
65
  element = element.name if (element.is_a? Element)
48
66
  element = @model.elements[element]
49
67
  mapped?(element) || element.attributes[:sortable]
50
68
  end
51
69
 
70
+ # Returns the base type corresponding to a type; see {Model.base_type}
71
+ # @return [Class] the base type corresponding to type
52
72
  def base_type(type)
53
73
  Spider::Model.base_type(type)
54
74
  end
55
75
 
56
76
  # Utility methods
57
77
 
58
- # An array of mapped elements.
59
- def map_elements # :nodoc:
60
- @model.elements_array.select{ |el| !@no_map_elements[el.name] }
61
- end
62
-
63
- # Calls the given action. Used by UnitOfWork tasks.
64
- def execute_action(action, object, params={}) # :nodoc:
78
+ # Executes the given UnitOfWork action.
79
+ # @param [Symbol] action
80
+ # @param [BaseModel] object
81
+ # @param [Hash] params
82
+ # @return [void]
83
+ def execute_action(action, object, params={})
65
84
  case action
66
85
  when :save
67
86
  if params[:force] == :insert
@@ -80,7 +99,9 @@ module Spider; module Model
80
99
  end
81
100
  end
82
101
 
83
- # Converts hashes and arrays to QuerySets and BaseModel instances.
102
+ # Converts hashes and arrays inside an object to QuerySets and BaseModel instances.
103
+ # @param [BaseModel] obj
104
+ # @return [void]
84
105
  def normalize(obj)
85
106
  obj.no_autoload do
86
107
  @model.elements.select{ |n, el|
@@ -103,18 +124,16 @@ module Spider; module Model
103
124
  # Info #
104
125
  #############################################################
105
126
 
106
- # Returns true if information to find the given element is accessible to the mapper.
107
- # (see for example DbMapper#have_references?)
127
+ # @abstract
128
+ # Returns true if information to find the given element is accessible to the mapper
129
+ # (see {DbMapper#have_references?} for an implementation)
130
+ # @param [Symbol|Element] element
131
+ # @return [bool] True if the storage has a field to write the element or a reference to the element (primary keys),
132
+ # false otherwise
108
133
  def have_references?(element)
109
134
  raise MapperError, "Unimplemented"
110
135
  end
111
136
 
112
- # Returns true if information to find the given element is accessible to the mapper, or to an integrated model's mapper.
113
- # (see for example DbMapper#someone_have_references?)
114
- def someone_have_references?(element)
115
- raise MapperError, "Unimplemented"
116
- end
117
-
118
137
 
119
138
  ##############################################################
120
139
  # Save (insert and update) #
@@ -122,8 +141,12 @@ module Spider; module Model
122
141
 
123
142
  # This method is called before a save operation, normalizing and preparing the object.
124
143
  # 'mode' can be :insert or :update.
125
- # This method is well suited for being overridden, to add custom preprocessing of the object; just
126
- # remember to call #super, or use #before_insert and #before_update instead.
144
+ # This method is well suited for being overridden (with {BaseModel.with_mapper},
145
+ # to add custom preprocessing of the object; just
146
+ # remember to call super, or use #before_insert and #before_update instead.
147
+ # @param [BaseModel] obj
148
+ # @param [Symbol] mode :insert or :update
149
+ # @return [void]
127
150
  def before_save(obj, mode)
128
151
  obj.trigger(:before_save, mode)
129
152
  normalize(obj)
@@ -166,6 +189,10 @@ module Spider; module Model
166
189
  end
167
190
  end
168
191
 
192
+ # Saves models that obj's model extends (see {BaseModel.extend_model})
193
+ # @param [BaseModel] obj
194
+ # @param [Symbol] mode
195
+ # @return [void]
169
196
  def save_extended_models(obj, mode)
170
197
  if @model.extended_models
171
198
  @model.extended_models.each do |m, el|
@@ -179,6 +206,10 @@ module Spider; module Model
179
206
  end
180
207
  end
181
208
 
209
+ # Saves objects integrated in obj (see {BaseModel.integrate})
210
+ # @param [BaseModel] obj
211
+ # @param [Symbol] mode
212
+ # @return [void]
182
213
  def save_integrated(obj, mode)
183
214
  @model.elements_array.select{ |el| !el.integrated? && el.attributes[:integrated_model] && !el.attributes[:extended_model] }.each do |el|
184
215
  sub_obj = obj.get(el)
@@ -187,44 +218,71 @@ module Spider; module Model
187
218
  end
188
219
 
189
220
  # Hook to provide custom preprocessing. The default implementation does nothing.
221
+ #
222
+ # If needed, override using {BaseModel.with_mapper}
223
+ # @param [BaseModel] obj
224
+ # @return [void]
190
225
  def before_insert(obj)
191
226
  end
192
227
 
193
228
  # Hook to provide custom preprocessing. The default implementation does nothing.
229
+ #
230
+ # If needed, override using {BaseModel.with_mapper}
231
+ # @param [BaseModel] obj
232
+ # @return [void]
194
233
  def before_update(obj)
195
234
  end
196
235
 
197
236
  # Hook to provide custom preprocessing. Will be passed a QuerySet. The default implementation does nothing.
237
+ #
238
+ # If needed, override using {BaseModel.with_mapper}
239
+ # @param [QuerySet] objects
240
+ # @return [void]
198
241
  def before_delete(objects)
199
242
  end
200
243
 
201
244
  # Called after a succesful save. 'mode' can be :insert or :update.
245
+ #
246
+ # If needed, override using {BaseModel.with_mapper}; but make sure to call super, since this method's
247
+ # implementation is not empty.
248
+ # Otherwise, override {#save_done}
249
+ # @param [BaseModel] obj
250
+ # @param [Symbol] mode :insert or :update
251
+ # @return [void]
202
252
  def after_save(obj, mode)
203
253
  obj.reset_modified_elements
204
254
  save_associations(obj, mode)
205
255
 
206
256
  end
207
257
 
208
- # Hook called after a succesful save: the object is not in save mode.
258
+ # Hook called after a succesful save, when the object is not in save mode (see {BaseModel#save_mode}) anymore.
259
+ #
260
+ # If needed, override using {BaseModel.with_mapper}
261
+ # @param [BaseModel] obj
262
+ # @param [Symbol] mode :insert or :update
263
+ # @return [void]
209
264
  def save_done(obj, mode)
210
265
  end
211
266
 
212
267
  # Hook to provide custom preprocessing. Will be passed a QuerySet. The default implementation does nothing.
268
+ #
269
+ # If needed, override using {BaseModel.with_mapper}
270
+ # @param [QuerySet] objects
271
+ # @return [void]
213
272
  def after_delete(objects)
214
273
  end
215
274
 
216
275
  # Saves the object to the storage.
276
+ # @param [BaseModel] obj
277
+ # @param [Model::Request] request Save only elements in the fiven request.
278
+ # @return [true]
217
279
  def save(obj, request=nil)
218
280
  prev_autoload = obj.autoload?
219
281
  obj.save_mode
220
282
  storage.in_transaction
221
283
  save_mode = determine_save_mode(obj)
222
284
  before_save(obj, save_mode)
223
- # @model.elements_array.select{ |el| el.attributes[:integrated_model] }.each do |el|
224
- # obj.get(el).save if obj.element_modified?(el)
225
- # end
226
-
227
- if (save_mode == :update)
285
+ if save_mode == :update
228
286
  do_update(obj)
229
287
  else
230
288
  do_insert(obj)
@@ -241,6 +299,9 @@ module Spider; module Model
241
299
  true
242
300
  end
243
301
 
302
+ # Determines whether the object needs to be inserted or updated.
303
+ # @param [BaseModel] obj
304
+ # @return [Symbol] :insert or :update
244
305
  def determine_save_mode(obj)
245
306
  if @model.extended_models && !@model.extended_models.empty?
246
307
  is_insert = false
@@ -263,6 +324,8 @@ module Spider; module Model
263
324
 
264
325
 
265
326
  # Elements that are associated to this one externally.
327
+ # @return [Array] An Array of elements for which the storage does not hold keys (see {#have_references?}),
328
+ # and which must be associated through other ways
266
329
  def association_elements
267
330
  return [] if Spider::Model.unit_of_work_running?
268
331
  els = @model.elements_array.select{ |el|
@@ -271,7 +334,8 @@ module Spider; module Model
271
334
  els
272
335
  end
273
336
 
274
- # Saves object associations.
337
+ # Saves externally associated objects (the ones corresponding to elements returned by #association_elements)
338
+ # @return [void]
275
339
  def save_associations(obj, mode)
276
340
  association_elements.select{ |el| obj.element_has_value?(el) }.each do |el|
277
341
  save_element_associations(obj, el, mode)
@@ -279,6 +343,10 @@ module Spider; module Model
279
343
  end
280
344
 
281
345
  # Deletes all associations from the given object to the element.
346
+ # @param [BaseModel] obj
347
+ # @param [Element] element
348
+ # @param [BaseModel] associated The currently associated objects
349
+ # @return [void]
282
350
  def delete_element_associations(obj, element, associated=nil)
283
351
  if element.attributes[:junction]
284
352
  condition = {element.attributes[:reverse] => obj.primary_keys}
@@ -307,6 +375,10 @@ module Spider; module Model
307
375
  end
308
376
 
309
377
  # Saves the associations from the given object to the element.
378
+ # @param [BaseModel] obj
379
+ # @param [Element] element
380
+ # @param [Symbol] mode :insert or :update
381
+ # @return [void]
310
382
  def save_element_associations(obj, element, mode)
311
383
  our_element = element.attributes[:reverse]
312
384
  val = obj.get(element)
@@ -380,6 +452,8 @@ module Spider; module Model
380
452
  end
381
453
 
382
454
  # Saves the given object and all objects reachable from it.
455
+ # @param [BaseModel] root The root object
456
+ # @return [void]
383
457
  def save_all(root)
384
458
  UnitOfWork.new do |uow|
385
459
  uow.add(root)
@@ -388,6 +462,8 @@ module Spider; module Model
388
462
  end
389
463
 
390
464
  # Inserts the object in the storage.
465
+ # @param [BaseModel] obj
466
+ # @return [void]
391
467
  def insert(obj)
392
468
  prev_autoload = obj.save_mode()
393
469
  storage.in_transaction
@@ -399,6 +475,8 @@ module Spider; module Model
399
475
  end
400
476
 
401
477
  # Updates the object in the storage.
478
+ # @param [BaseModel] obj
479
+ # @return [void]
402
480
  def update(obj)
403
481
  prev_autoload = obj.save_mode()
404
482
  storage.in_transaction
@@ -409,15 +487,23 @@ module Spider; module Model
409
487
  obj.autoload = prev_autoload
410
488
  end
411
489
 
412
- # FIXME: remove?
413
- def bulk_update(values, conditon) # :nodoc:
490
+ # @abstract
491
+ # Executes a mass update for given condition.
492
+ # @param [Hash] values
493
+ # @param [Condition] condition
494
+ # @return [nil]
495
+ def bulk_update(values, conditon)
414
496
  end
415
497
 
416
498
  # Deletes an object, or objects according to a condition.
417
499
  # Will not delete with null condition (i.e. all objects) unless force is true
418
- # Options can be:
419
- # :keep_single_reverse: don't delete associations that have a single reverse.
420
- # Useful when an object will be re-inserted with the same keys.
500
+ #
501
+ # @param [BaseModel|Condition] obj_or_condition
502
+ # @param [bool] force
503
+ # @param [Hash] options Available options:
504
+ # * :keep_single_reverse: don't delete associations that have a single reverse.
505
+ # Useful when an object will be re-inserted with the same keys.
506
+ # @return [void]
421
507
  def delete(obj_or_condition, force=false, options={})
422
508
 
423
509
  def prepare_delete_condition(obj)
@@ -484,56 +570,42 @@ module Spider; module Model
484
570
  end
485
571
 
486
572
  # Deletes all objects from the storage.
573
+ # @return [void]
487
574
  def delete_all!
488
575
  all = @model.all
489
576
  #all.fetch_window = 100
490
577
  delete(all, true)
491
578
  end
492
579
 
493
- def truncate!
494
- raise MapperError, "Unimplemented"
495
- end
496
-
497
- # Actual interaction with the storage. May be implemented by subclasses.
498
- def do_delete(obj, force=false)
499
- raise MapperError, "Unimplemented"
500
- end
501
-
502
- # Actual interaction with the storage. May be implemented by subclasses.
503
- def do_insert(obj)
504
- raise MapperError, "Unimplemented"
505
- end
506
-
507
- # Actual interaction with the storage. May be implemented by subclasses.
508
- def do_update(obj)
509
- raise MapperError, "Unimplemented"
510
- end
511
-
512
- # Actual interaction with the storage. May be implemented by subclasses.
513
- def lock(obj=nil, mode=:exclusive)
514
- raise MapperError, "Unimplemented"
515
- end
516
-
517
- # Actual interaction with the storage. May be implemented by subclasses.
518
- def sequence_next(name)
519
- raise MapperError, "Unimplemented"
520
- end
580
+
521
581
 
522
582
  ##############################################################
523
583
  # Load (and find) #
524
584
  ##############################################################
525
585
 
526
586
  # Loads an element. Other elements may be loaded as well, according to lazy groups.
587
+ # @param [QuerySet] objects Objects for which to load given element
588
+ # @param [Element] element
589
+ # @return [QuerySet]
527
590
  def load_element(objects, element)
528
591
  load(objects, Query.new(nil, [element.name]))
529
592
  end
530
593
 
531
594
  # Loads only the given element, ignoring lazy groups.
595
+ # @param [QuerySet] objects Objects for which to load given element
596
+ # @param [Element] element
597
+ # @return [QuerySet]
532
598
  def load_element!(objects, element)
533
599
  load(objects, Query.new(nil, [element.name]), :no_expand_request => true)
534
600
  end
535
601
 
536
602
  # Loads elements of given objects according to query.request.
603
+ #
604
+ # See also {#find}
605
+ # @param [QuerySet] objects Objects to expand
606
+ # @param [Query] query
607
+ # @param [Hash] options
608
+ # @return [QuerySet]
537
609
  def load(objects, query, options={})
538
610
  objects = queryset_siblings(objects) unless objects.is_a?(QuerySet)
539
611
  request = query.request
@@ -545,6 +617,12 @@ module Spider; module Model
545
617
  end
546
618
 
547
619
  # Finds objects according to a query, merging the results into a query_set if given.
620
+ #
621
+ # @param [Query] query
622
+ # @param [QuerySet] query_set QuerySet to merge results into, if given
623
+ # @param [Hash] options Options can be:
624
+ # * :no_expand_request: don't expand request using lazy loading groups
625
+ # @return [QuerySet]
548
626
  def find(query, query_set=nil, options={})
549
627
  set = nil
550
628
  Spider::Model.with_identity_mapper do |im|
@@ -621,9 +699,166 @@ module Spider; module Model
621
699
  end
622
700
  return set
623
701
  end
702
+
703
+ # Does a count query on the storage for given condition
704
+ # @param [Condition]
705
+ # @return [Fixnum]
706
+ def count(condition)
707
+ query = Query.new(condition)
708
+ result = fetch(query)
709
+ return result.length
710
+ end
624
711
 
712
+
713
+
714
+ # Returns the siblings, if any, of the object, in its ancestor QuerySet.
715
+ #
716
+ # Siblings are objects in the same branch of the object tree.
717
+ #
718
+ # This method is used to load related data, avoiding N+1 queries
719
+ # @param [BaseModel|QuerySet] obj
720
+ # @return [QuerySet]
721
+ def queryset_siblings(obj)
722
+ return QuerySet.new(@model, obj) unless obj._parent
723
+ orig_obj = obj
724
+ path = []
725
+ seen = {obj => true}
726
+ while (obj._parent && !seen[obj._parent])
727
+ path.unshift(obj._parent_element) if (obj._parent_element) # otherwise it's a query set
728
+ obj = obj._parent
729
+ seen[obj] = true
730
+ end
731
+ res = path.empty? ? obj : obj.all_children(path)
732
+ raise RuntimeError, "Broken object path" if (obj && !path.empty? && res.length < 1)
733
+ res = QuerySet.new(@model, res) unless res.is_a?(QuerySet)
734
+ res = res.select{ |obj| obj.primary_keys_set? }
735
+ return res
736
+ end
625
737
 
626
- def merge_object(set, obj, request) # :nodoc:
738
+ # Prepares a value going to be bound to an insert or update statement
739
+ # @param [Class] type Value's type
740
+ # @param [Object] value
741
+ # @param [Symbol] save_mode :insert, :update, or generically :save
742
+ # @return [Object]
743
+ def map_save_value(type, value, save_mode=:save)
744
+ value = map_value(type, value, :save)
745
+ return @storage.value_for_save(Model.simplify_type(type), value, save_mode)
746
+ end
747
+
748
+ # Prepares a value for a condition.
749
+ # @param [Class] type Value's type
750
+ # @param [Object] value
751
+ # @param [Symbol] save_mode :insert, :update, or generically :save
752
+ # @return [Object]
753
+ def map_condition_value(type, value)
754
+ if value.is_a?(Range)
755
+ return Range.new(map_condition_value(type, value.first), map_condition_value(type, value.last))
756
+ end
757
+ return value if ( type.class == Class && type.subclass_of?(Spider::Model::BaseModel) )
758
+ value = map_value(type, value, :condition)
759
+ return @storage.value_for_condition(Model.simplify_type(type), value)
760
+ end
761
+
762
+ # Calls {Storage#value_to_mapper}. It is repeated in Mapper for easier overriding.
763
+ # @param [Class] type Value's type
764
+ # @param [Object] value
765
+ # @return [Object]
766
+ def storage_value_to_mapper(type, value)
767
+ storage.value_to_mapper(type, value)
768
+ end
769
+
770
+
771
+ # Converts a value into one that is accepted by the storage.
772
+ # @param [Class] type Value's type
773
+ # @param [Object] value
774
+ # @param [Symbol] save_mode :insert, :update, or generically :save
775
+ # @return [Object]
776
+ def map_value(type, value, mode=nil)
777
+ return value if value.nil?
778
+ if type == Spider::DataTypes::PK
779
+ value = value.obj if value.is_a?(Spider::DataTypes::PK)
780
+ elsif type < Spider::DataType
781
+ value = type.from_value(value) unless value.is_a?(type)
782
+ value = value.map(self.type)
783
+ elsif type.class == Class && type.subclass_of?(Spider::Model::BaseModel)
784
+ value = type.primary_keys.map{ |key| value.send(key.name) }
785
+ end
786
+ value
787
+ end
788
+
789
+
790
+ # Converts a storage value back to the corresponding base type or DataType.
791
+ # @param [Class] type Value's type
792
+ # @param [Object] value
793
+ # @return [Object]
794
+ def map_back_value(type, value)
795
+ value = value[0] if value.class == Array
796
+ value = storage_value_to_mapper(Model.simplify_type(type), value)
797
+
798
+ if type <= Spider::DataTypes::PK
799
+ value = value.is_a?(Spider::DataTypes::PK) ? value.obj : value
800
+ elsif type < Spider::DataType && type.maps_back_to
801
+ type = type.maps_back_to
802
+ end
803
+ case type.name
804
+ when 'Fixnum'
805
+ return value ? value.to_i : nil
806
+ when 'Float'
807
+ return value ? value.to_f : nil
808
+ end
809
+ return nil unless value
810
+ case type.name
811
+ when 'Date', 'DateTime'
812
+ return type.parse(value) unless value.is_a?(Date)
813
+ end
814
+ if type < Spider::DataType && type.force_wrap?
815
+ value = type.from_value(value)
816
+ end
817
+ return value
818
+ end
819
+
820
+ # Unit of work
821
+
822
+ # @abstract
823
+ # Returns task dependecies for the UnitOfWork. May be implemented by subclasses.
824
+ # @param [MapperTask] task
825
+ # @return [Array] Dependencies for the task
826
+ def get_dependencies(task)
827
+ return []
828
+ end
829
+
830
+ # @param [BaseModel] obj
831
+ # @param [Symbol] action UnitOfWork action
832
+ # @return [Array] Objects to be added to the UnitOfWork when obj is added
833
+ def children_for_unit_of_work(obj, action)
834
+ children = []
835
+ obj.class.elements_array.each do |el|
836
+ next unless obj.element_has_value?(el)
837
+ next unless el.model?
838
+ next unless obj.element_modified?(el)
839
+ val = obj.get(el)
840
+ next unless val.modified?
841
+ children << val
842
+ end
843
+ children
844
+ end
845
+
846
+ protected
847
+
848
+ # @return [Array] An array of all elements which are handled by the mapper
849
+ def map_elements
850
+ @model.elements_array.select{ |el| !@no_map_elements[el.name] }
851
+ end
852
+
853
+
854
+ # Given a QuerySet and a model object, searches for an object with the same keys
855
+ # in the QuerySet; if found, merges the object, otherwise, adds the object to the set
856
+ #
857
+ # @param [QuerySet] set
858
+ # @param [BaseModel] obj Object to merge
859
+ # @param [Model::Request] request Only elements in request will be merged
860
+ # @return [void]
861
+ def merge_object(set, obj, request)
627
862
  search = {}
628
863
  @model.primary_keys.each{ |k| search[k.name] = obj.get(k.name) }
629
864
  obj_res = set.find(search) # FIXME: find a better way
@@ -637,8 +872,14 @@ module Spider; module Model
637
872
  obj
638
873
  end
639
874
  end
640
-
641
- def find_with_superclass(query, set=nil, options={}) # :nodoc:
875
+
876
+ # Like #find, but also retrieves instances of the object's superclass (assuming it is a BaseModel as well)
877
+ #
878
+ # @param [Query] query
879
+ # @param [QuerySet] set
880
+ # @param [Hash] options
881
+ # @return [QuerySet]
882
+ def find_with_superclass(query, set=nil, options={})
642
883
  q = query.clone
643
884
  polym_request = Request.new
644
885
  polym_condition = Condition.new
@@ -656,26 +897,12 @@ module Spider; module Model
656
897
  end
657
898
  return set
658
899
  end
659
-
660
- # Does a count query on the storage for given condition
661
- def count(condition)
662
- query = Query.new(condition)
663
- result = fetch(query)
664
- return result.length
665
- end
666
-
667
- # Actual interaction with the storage. Should be implemented by subclasses.
668
- def fetch(query)
669
- raise MapperError, "Unimplemented"
670
- end
671
-
672
-
673
- # Transforms a Storage result into an object. Should be implemented by subclasses.
674
- def map(request, result, obj_or_model)
675
- raise MapperError, "Unimplemented"
676
- end
677
-
678
- # Loads external elements, according to query, and merges them into an object or a QuerySet
900
+
901
+
902
+ # Loads external elements, according to query, and merges them into an object or a QuerySet
903
+ # @param [QuerySet|BaseModel] objects
904
+ # @param [Query] query
905
+ # @return [QuerySet]
679
906
  def get_external(objects, query)
680
907
  objects = queryset_siblings(objects) unless objects.is_a?(QuerySet)
681
908
  return objects if objects.length < 1
@@ -711,6 +938,10 @@ module Spider; module Model
711
938
  end
712
939
 
713
940
  # Loads an external element, according to query, and merges the result into an object or QuerySet.
941
+ # @param [Element] element
942
+ # @param [Query] query
943
+ # @param [QuerySet] result
944
+ # @return [QuerySet]
714
945
  def get_external_element(element, query, objects)
715
946
  # Spider::Logger.debug("Getting external element #{element.name} for #{@model}")
716
947
  return load_element(objects, element) if have_references?(element)
@@ -733,6 +964,10 @@ module Spider; module Model
733
964
 
734
965
  # Given the results of a query for an element, and a set of objects, associates
735
966
  # the result with the corresponding objects.
967
+ # @param [Element] element
968
+ # @param [QuerySet] objects
969
+ # @param [QuerySet] result
970
+ # @return [QuerySet]
736
971
  def associate_external(element, objects, result)
737
972
  # result.reindex
738
973
  objects.element_loaded(element.name)
@@ -751,92 +986,15 @@ module Spider; module Model
751
986
  end
752
987
  return objects
753
988
  end
754
-
755
- # Returns the siblings, if any, of the object, in its ancestor QuerySet.
756
- def queryset_siblings(obj)
757
- return QuerySet.new(@model, obj) unless obj._parent
758
- orig_obj = obj
759
- path = []
760
- seen = {obj => true}
761
- while (obj._parent && !seen[obj._parent])
762
- path.unshift(obj._parent_element) if (obj._parent_element) # otherwise it's a query set
763
- obj = obj._parent
764
- seen[obj] = true
765
- end
766
- res = path.empty? ? obj : obj.all_children(path)
767
- raise RuntimeError, "Broken object path" if (obj && !path.empty? && res.length < 1)
768
- res = QuerySet.new(@model, res) unless res.is_a?(QuerySet)
769
- res = res.select{ |obj| obj.primary_keys_set? }
770
- return res
771
- end
772
-
773
- # Prepares a value going to be bound to an insert or update statement
774
- def map_save_value(type, value, save_mode=:save)
775
- value = map_value(type, value, :save)
776
- return @storage.value_for_save(Model.simplify_type(type), value, save_mode)
777
- end
778
-
779
- # Prepares a value for a condition.
780
- def map_condition_value(type, value)
781
- if value.is_a?(Range)
782
- return Range.new(map_condition_value(type, value.first), map_condition_value(type, value.last))
783
- end
784
- return value if ( type.class == Class && type.subclass_of?(Spider::Model::BaseModel) )
785
- value = map_value(type, value, :condition)
786
- return @storage.value_for_condition(Model.simplify_type(type), value)
787
- end
788
-
789
- def storage_value_to_mapper(type, value)
790
- storage.value_to_mapper(type, value)
791
- end
792
-
793
-
794
- # Converts a value in one accepted by the storage.
795
- def map_value(type, value, mode=nil)
796
- return value if value.nil?
797
- if type == Spider::DataTypes::PK
798
- value = value.obj if value.is_a?(Spider::DataTypes::PK)
799
- elsif type < Spider::DataType
800
- value = type.from_value(value) unless value.is_a?(type)
801
- value = value.map(self.type)
802
- elsif type.class == Class && type.subclass_of?(Spider::Model::BaseModel)
803
- value = type.primary_keys.map{ |key| value.send(key.name) }
804
- end
805
- value
806
- end
807
-
808
-
809
- # Converts a storage value back to the corresponding base type or DataType.
810
- def map_back_value(type, value)
811
- value = value[0] if value.class == Array
812
- value = storage_value_to_mapper(Model.simplify_type(type), value)
813
989
 
814
- if type <= Spider::DataTypes::PK
815
- value = value.is_a?(Spider::DataTypes::PK) ? value.obj : value
816
- elsif type < Spider::DataType && type.maps_back_to
817
- type = type.maps_back_to
818
- end
819
- case type.name
820
- when 'Fixnum'
821
- return value ? value.to_i : nil
822
- when 'Float'
823
- return value ? value.to_f : nil
824
- end
825
- return nil unless value
826
- case type.name
827
- when 'Date', 'DateTime'
828
- return type.parse(value) unless value.is_a?(Date)
829
- end
830
- if type < Spider::DataType && type.force_wrap?
831
- value = type.from_value(value)
832
- end
833
- return value
834
- end
835
-
836
990
  ##############################################################
837
991
  # Strategy #
838
992
  ##############################################################
839
993
 
994
+ # Ensures a Query is ready for being used by the mapper
995
+ # @param [Query] query
996
+ # @param [BaseModel] obj Optional object; if passed, will be used to ensure the Query Request corresponds to the object
997
+ # @return [Query] The prepared query
840
998
  def prepare_query(query, obj=nil)
841
999
  if (query.request.polymorphs?)
842
1000
  conds = split_condition_polymorphs(query.condition, query.request.polymorphs.keys)
@@ -852,6 +1010,11 @@ module Spider; module Model
852
1010
  return query
853
1011
  end
854
1012
 
1013
+ # Helper method to split conditions for polymorphic elements
1014
+ # into the correct classes
1015
+ # @param [Condition] condition
1016
+ # @param [Array] polymorphs Array of polymorphic model classes
1017
+ # @return [Array] An array of conditions
855
1018
  def split_condition_polymorphs(condition, polymorphs)
856
1019
  conditions = {}
857
1020
  return conditions if condition.polymorph && polymorphs.include?(condition.polymorph)
@@ -884,6 +1047,9 @@ module Spider; module Model
884
1047
 
885
1048
 
886
1049
  # Normalizes a request.
1050
+ # @param [Request] request
1051
+ # @param [BaseModel] obj
1052
+ # @return [void]
887
1053
  def prepare_query_request(request, obj=nil)
888
1054
  @model.primary_keys.each do |key|
889
1055
  request[key] = true
@@ -900,7 +1066,11 @@ module Spider; module Model
900
1066
  new_requests.each{ |r| request.request(r) }
901
1067
  end
902
1068
 
903
- # Adds lazy groups to request.
1069
+ # Adds lazy groups to request. That is, load more data than was requested, to avoid making more
1070
+ # trips to the storage.
1071
+ # @param [Request] request
1072
+ # @param [BaseModel] obj Optional model instance
1073
+ # @return [void]
904
1074
  def expand_request(request, obj=nil)
905
1075
  lazy_groups = []
906
1076
  request.each do |k, v|
@@ -924,6 +1094,8 @@ module Spider; module Model
924
1094
  end
925
1095
 
926
1096
  # Preprocessing of the condition
1097
+ # @param [Condition] condition
1098
+ # @return [Condition] The preprocessed condition
927
1099
  def preprocess_condition(condition)
928
1100
  model = condition.polymorph ? condition.polymorph : @model
929
1101
  condition.simplify
@@ -1044,25 +1216,86 @@ module Spider; module Model
1044
1216
  end
1045
1217
  return condition
1046
1218
  end
1219
+
1220
+ # @abstract
1221
+ # Returns true if information to find the given element is accessible to the mapper, or to an integrated model's mapper
1222
+ # (see also {#have_references?}, and {DbMapper#someone_have_references?} for an implementation).
1223
+ #
1224
+ # @param [Symbol|Element] element
1225
+ # @return [bool] True if this mapper, or an integrated model's mapper, has references, false otherwise.
1226
+ def someone_have_references?(element)
1227
+ raise MapperError, "Unimplemented"
1228
+ end
1229
+
1230
+ # Abstract methods
1231
+
1232
+ # @abstract
1233
+ # Deletes all data associated to the model from the storage
1234
+ # @return [void]
1235
+ def truncate!
1236
+ raise MapperError, "Unimplemented"
1237
+ end
1047
1238
 
1048
- # Returns task dependecies for the UnitOfWork. May be implemented by subclasses.
1049
- def get_dependencies(task)
1050
- return []
1239
+ # @abstract
1240
+ # Actual interaction with the storage. May be implemented by subclasses.
1241
+ # @return [void]
1242
+ def do_delete(obj, force=false)
1243
+ raise MapperError, "Unimplemented"
1051
1244
  end
1052
1245
 
1053
- def children_for_unit_of_work(obj, action)
1054
- children = []
1055
- obj.class.elements_array.each do |el|
1056
- next unless obj.element_has_value?(el)
1057
- next unless el.model?
1058
- next unless obj.element_modified?(el)
1059
- val = obj.get(el)
1060
- next unless val.modified?
1061
- children << val
1062
- end
1063
- children
1246
+ # @abstract
1247
+ # Actual interaction with the storage. May be implemented by subclasses.
1248
+ # @return [void]
1249
+ def do_insert(obj)
1250
+ raise MapperError, "Unimplemented"
1251
+ end
1252
+
1253
+ # @abstract
1254
+ # Actual interaction with the storage. May be implemented by subclasses.
1255
+ # @return [void]
1256
+ def do_update(obj)
1257
+ raise MapperError, "Unimplemented"
1064
1258
  end
1065
1259
 
1260
+ # @abstract
1261
+ # Actual interaction with the storage. May be implemented by subclasses.
1262
+ # @return [void]
1263
+ def lock(obj=nil, mode=:exclusive)
1264
+ raise MapperError, "Unimplemented"
1265
+ end
1266
+
1267
+ # @abstract
1268
+ # Actual interaction with the storage. May be implemented by subclasses.
1269
+ # @param [Symbol] name
1270
+ # @return [void]
1271
+ def sequence_next(name)
1272
+ raise MapperError, "Unimplemented"
1273
+ end
1274
+
1275
+ # @abstract
1276
+ # Actual interaction with the storage. Should be implemented by subclasses.
1277
+ # @param [Query]
1278
+ # @return [QuerySet]
1279
+ def fetch(query)
1280
+ raise MapperError, "Unimplemented"
1281
+ end
1282
+
1283
+ # @abstract
1284
+ # Transforms a Storage result into an object. Should be implemented by subclasses.
1285
+ # @return [BaseModel]
1286
+ def map(request, result, obj_or_model)
1287
+ raise MapperError, "Unimplemented"
1288
+ end
1289
+
1290
+ # @abstract
1291
+ # @param [Element|Symbol] element
1292
+ # @param [Condition]
1293
+ # @return [Fixnum] The max value for an element
1294
+ def max(element, condition=nil)
1295
+ raise "Unimplemented"
1296
+ end
1297
+
1298
+
1066
1299
 
1067
1300
  end
1068
1301
 
@@ -1070,10 +1303,21 @@ module Spider; module Model
1070
1303
  # MapperTask #
1071
1304
  ##############################################################
1072
1305
 
1073
- # The MapperTask is used by the UnitOfWork.
1306
+ # The MapperTask is used by the UnitOfWork. It represents an action that needs to be done,
1307
+ # and allows to specify dependences between tasks
1074
1308
  class MapperTask
1075
- attr_reader :dependencies, :object, :action, :params
1309
+ # @return [Array] Array of MapperTasks this one depends on
1310
+ attr_reader :dependencies
1311
+ # @return [BaseModel] The task's subject
1312
+ attr_reader :object
1313
+ # @return [Symbol] The task's action
1314
+ attr_reader :action
1315
+ # @return [Hash] Params for the task
1316
+ attr_reader :params
1076
1317
 
1318
+ # @param [BaseModel] object The task's subject
1319
+ # @param [Symbol] action
1320
+ # @param [Hash] params
1077
1321
  def initialize(object, action, params={})
1078
1322
  @object = object
1079
1323
  @action = action
@@ -1081,17 +1325,23 @@ module Spider; module Model
1081
1325
  @dependencies = []
1082
1326
  end
1083
1327
 
1328
+ # Addes a dependency to the Task
1329
+ # @param [MapperTask] task
1330
+ # @return [void]
1084
1331
  def <<(task)
1085
1332
  @dependencies << task
1086
1333
  end
1087
1334
 
1088
- def execute()
1335
+ # Makes the objects' mapper run the task
1336
+ # @return [void]
1337
+ def execute
1089
1338
  debug_str = "Executing #{@action} on #{@object.inspect}"
1090
1339
  debug_str += " (#{@params.inspect})" unless @params.empty?
1091
1340
  Spider::Logger.debug debug_str
1092
1341
  @object.mapper.execute_action(@action, @object, @params)
1093
1342
  end
1094
1343
 
1344
+ # @return [bool] True if the other task has the same object, action and params, false otherwise
1095
1345
  def eql?(task)
1096
1346
  return false unless task.class == self.class
1097
1347
  return false unless (task.object == self.object && task.action == self.action)
@@ -1101,10 +1351,12 @@ module Spider; module Model
1101
1351
  return true
1102
1352
  end
1103
1353
 
1354
+ # @return [String] Hash for keying
1104
1355
  def hash
1105
1356
  return @object.hash + @action.hash
1106
1357
  end
1107
1358
 
1359
+ # @return [bool] Same as #eql?
1108
1360
  def ===(task)
1109
1361
  return eql?(task)
1110
1362
  end
@@ -1113,6 +1365,7 @@ module Spider; module Model
1113
1365
  # "#{@action} on #{@object} (#{object.class})\n"
1114
1366
  # end
1115
1367
 
1368
+ # @return [String] A textual representation of the Task
1116
1369
  def inspect
1117
1370
  if (@action && @object)
1118
1371
  str = "#{@action} on #{@object}##{@object.object_id} (#{object.class})"
@@ -1132,25 +1385,16 @@ module Spider; module Model
1132
1385
 
1133
1386
  end
1134
1387
 
1135
- ##############################################################
1136
- # Aggregates #
1137
- ##############################################################
1138
-
1139
- def max(element, condition=nil)
1140
- raise "Unimplemented"
1141
- end
1142
-
1388
+
1143
1389
 
1144
1390
  ##############################################################
1145
1391
  # Exceptions #
1146
1392
  ##############################################################
1147
1393
 
1148
1394
  # Generic Mapper error.
1149
-
1150
1395
  class MapperError < RuntimeError; end
1151
1396
 
1152
1397
  # Generic Mapper error regarding an element.
1153
-
1154
1398
  class MapperElementError < MapperError
1155
1399
  def initialize(element)
1156
1400
  @element = element
@@ -1178,14 +1422,13 @@ module Spider; module Model
1178
1422
  end
1179
1423
 
1180
1424
  # A required element has no value
1181
-
1182
1425
  RequiredError = MapperElementError.create_subclass(_("Element %s is required"))
1183
1426
 
1184
1427
  # An uniqueness constraint has been violated.
1185
-
1186
1428
  NotUniqueError = MapperElementError.create_subclass(_("Another item with the same %s is already present"))
1187
1429
 
1188
1430
 
1431
+ # Helper module to hold methods overridden by {BaseModel.with_mapper}
1189
1432
  module MapperIncludeModule
1190
1433
 
1191
1434
  def self.included(mod)