spiderfw 0.6.21 → 0.6.22

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 (149) hide show
  1. data/CHANGELOG +33 -0
  2. data/Rakefile +0 -1
  3. data/VERSION +1 -1
  4. data/apps/core/admin/_init.rb +4 -0
  5. data/apps/core/admin/admin.rb +20 -4
  6. data/apps/core/admin/controllers/admin_controller.rb +63 -4
  7. data/apps/core/admin/controllers/app_admin_controller.rb +15 -0
  8. data/apps/core/admin/data/locale/it/LC_MESSAGES/admin.mo +0 -0
  9. data/apps/core/admin/po/admin.pot +33 -0
  10. data/apps/core/admin/po/it/admin.po +34 -0
  11. data/apps/core/admin/public/css/admin.css +13 -0
  12. data/apps/core/admin/public/css/login.css +51 -0
  13. data/apps/core/admin/public/css/sass/admin.css +198 -0
  14. data/apps/core/admin/public/css/sass/bootstrap/bootstrap.css +3107 -0
  15. data/apps/core/admin/public/img/css/header_bg.png +0 -0
  16. data/apps/core/admin/public/img/css/noise.png +0 -0
  17. data/apps/core/admin/public/img/css/side_bg.png +0 -0
  18. data/apps/core/admin/public/img/icons/logout.png +0 -0
  19. data/apps/core/admin/public/img/icons-s845a69dd9f.png +0 -0
  20. data/apps/core/admin/public/js/bootstrap-alerts.js +113 -0
  21. data/apps/core/admin/public/js/bootstrap-buttons.js +62 -0
  22. data/apps/core/admin/public/js/bootstrap-dropdown.js +55 -0
  23. data/apps/core/admin/public/js/bootstrap-modal.js +260 -0
  24. data/apps/core/admin/public/js/bootstrap-popover.js +90 -0
  25. data/apps/core/admin/public/js/bootstrap-scrollspy.js +107 -0
  26. data/apps/core/admin/public/js/bootstrap-tabs.js +80 -0
  27. data/apps/core/admin/public/js/bootstrap-twipsy.js +321 -0
  28. data/apps/core/admin/public/sass/admin.scss +167 -0
  29. data/apps/core/admin/public/sass/bootstrap/bootstrap.scss +29 -0
  30. data/apps/core/admin/public/sass/bootstrap/forms.scss +478 -0
  31. data/apps/core/admin/public/sass/bootstrap/mixins.scss +220 -0
  32. data/apps/core/admin/public/sass/bootstrap/patterns.scss +1062 -0
  33. data/apps/core/admin/public/sass/bootstrap/reset.scss +141 -0
  34. data/apps/core/admin/public/sass/bootstrap/scaffolding.scss +136 -0
  35. data/apps/core/admin/public/sass/bootstrap/tables.scss +224 -0
  36. data/apps/core/admin/public/sass/bootstrap/type.scss +187 -0
  37. data/apps/core/admin/public/sass/bootstrap/variables.scss +60 -0
  38. data/apps/core/admin/public/sass/grid.scss +54 -0
  39. data/apps/core/admin/views/_app_info.shtml +5 -0
  40. data/apps/core/admin/views/admin.layout.shtml +35 -0
  41. data/apps/core/admin/views/index.shtml +1 -1
  42. data/apps/core/admin/views/login.layout.shtml +13 -0
  43. data/apps/core/auth/controllers/mixins/auth_helper.rb +1 -1
  44. data/apps/core/auth/models/super_user.rb +6 -0
  45. data/apps/core/auth/models/user.rb +9 -0
  46. data/apps/core/components/assets.rb +5 -1
  47. data/apps/core/components/data/locale/it/LC_MESSAGES/spider_components.mo +0 -0
  48. data/apps/core/components/po/it/spider_components.po +23 -9
  49. data/apps/core/components/po/spider_components.pot +16 -8
  50. data/apps/core/components/public/css/admin.css +0 -12
  51. data/apps/core/components/public/css/crud.css +16 -19
  52. data/apps/core/components/public/css/table.css +11 -5
  53. data/apps/core/components/public/js/less-1.1.3.min.js +16 -0
  54. data/apps/core/components/public/widgets/table.js +1 -1
  55. data/apps/core/components/widgets/admin/admin.rb +10 -0
  56. data/apps/core/components/widgets/admin/admin.shtml +24 -4
  57. data/apps/core/components/widgets/confirm/confirm.rb +2 -2
  58. data/apps/core/components/widgets/confirm/confirm.shtml +5 -2
  59. data/apps/core/components/widgets/crud/crud.rb +10 -1
  60. data/apps/core/components/widgets/crud/crud.shtml +18 -21
  61. data/apps/core/components/widgets/menu/menu.shtml +1 -2
  62. data/apps/core/components/widgets/switcher/switcher.rb +6 -3
  63. data/apps/core/components/widgets/switcher/templates/default.shtml +3 -1
  64. data/apps/core/components/widgets/table/table.rb +3 -2
  65. data/apps/core/components/widgets/table/table.shtml +44 -25
  66. data/apps/core/forms/data/locale/it/LC_MESSAGES/spider_forms.mo +0 -0
  67. data/apps/core/forms/po/it/spider_forms.po +7 -2
  68. data/apps/core/forms/po/spider_forms.pot +5 -1
  69. data/apps/core/forms/public/css/form.css +3 -3
  70. data/apps/core/forms/public/css/html_area.css +0 -1
  71. data/apps/core/forms/public/date_time.js +4 -3
  72. data/apps/core/forms/public/select.js +5 -4
  73. data/apps/core/forms/tags/element_label.erb +1 -1
  74. data/apps/core/forms/tags/row.erb +1 -1
  75. data/apps/core/forms/widgets/form/form.rb +23 -1
  76. data/apps/core/forms/widgets/form/form.shtml +7 -8
  77. data/apps/core/forms/widgets/inputs/checkbox/checkbox.shtml +3 -3
  78. data/apps/core/forms/widgets/inputs/date_time/date_time.shtml +3 -3
  79. data/apps/core/forms/widgets/inputs/file_input/file_input.rb +4 -2
  80. data/apps/core/forms/widgets/inputs/file_input/file_input.shtml +1 -1
  81. data/apps/core/forms/widgets/inputs/hidden/hidden.shtml +1 -1
  82. data/apps/core/forms/widgets/inputs/html_area/html_area.shtml +2 -2
  83. data/apps/core/forms/widgets/inputs/password/password.shtml +3 -3
  84. data/apps/core/forms/widgets/inputs/search_select/search_select.shtml +2 -2
  85. data/apps/core/forms/widgets/inputs/select/select.shtml +16 -13
  86. data/apps/core/forms/widgets/inputs/text/text.shtml +3 -3
  87. data/apps/core/forms/widgets/inputs/text_area/text_area.shtml +5 -2
  88. data/apps/core/forms/widgets/inputs/time_span/time_span.shtml +3 -3
  89. data/apps/messenger/_init.rb +10 -2
  90. data/apps/messenger/controllers/messenger_admin_controller.rb +53 -0
  91. data/apps/messenger/controllers/messenger_controller.rb +2 -0
  92. data/apps/messenger/controllers/mixins/messenger_helper.rb +2 -2
  93. data/apps/messenger/models/message.rb +1 -1
  94. data/apps/messenger/public/app_icon.png +0 -0
  95. data/apps/messenger/views/admin/_admin.layout.shtml +26 -0
  96. data/apps/messenger/views/admin/index.shtml +13 -0
  97. data/apps/messenger/views/admin/queue.shtml +28 -0
  98. data/apps/messenger/views/index.shtml +3 -3
  99. data/data/locale/it/LC_MESSAGES/spider.mo +0 -0
  100. data/lib/spiderfw/app.rb +10 -1
  101. data/lib/spiderfw/cache/template_cache.rb +21 -22
  102. data/lib/spiderfw/cmd/commands/app.rb +3 -3
  103. data/lib/spiderfw/cmd/commands/setup.rb +1 -1
  104. data/lib/spiderfw/config/options/spider.rb +18 -2
  105. data/lib/spiderfw/controller/controller.rb +9 -3
  106. data/lib/spiderfw/controller/dispatcher.rb +25 -12
  107. data/lib/spiderfw/controller/home_controller.rb +3 -3
  108. data/lib/spiderfw/controller/http_controller.rb +11 -0
  109. data/lib/spiderfw/controller/mixins/static_content.rb +3 -12
  110. data/lib/spiderfw/controller/mixins/visual.rb +21 -20
  111. data/lib/spiderfw/controller/request.rb +1 -3
  112. data/lib/spiderfw/http/adapters/mongrel.rb +1 -1
  113. data/lib/spiderfw/i18n/gettext.rb +14 -0
  114. data/lib/spiderfw/i18n/shtml_parser.rb +2 -2
  115. data/lib/spiderfw/model/base_model.rb +4 -3
  116. data/lib/spiderfw/model/mappers/db_mapper.rb +137 -79
  117. data/lib/spiderfw/model/mappers/mapper.rb +6 -2
  118. data/lib/spiderfw/model/migrations/drop_element.rb +1 -1
  119. data/lib/spiderfw/model/migrations/previous_model.rb +73 -0
  120. data/lib/spiderfw/model/migrations/rename_element.rb +42 -0
  121. data/lib/spiderfw/model/migrations.rb +14 -1
  122. data/lib/spiderfw/model/mixins/tree.rb +65 -19
  123. data/lib/spiderfw/model/model_hash.rb +9 -5
  124. data/lib/spiderfw/model/query.rb +8 -0
  125. data/lib/spiderfw/model/query_funcs.rb +23 -0
  126. data/lib/spiderfw/model/query_set.rb +1 -1
  127. data/lib/spiderfw/model/request.rb +11 -3
  128. data/lib/spiderfw/model/storage/db/adapters/mysql.rb +28 -1
  129. data/lib/spiderfw/model/storage/db/adapters/oracle.rb +10 -10
  130. data/lib/spiderfw/model/storage/db/db_schema.rb +20 -3
  131. data/lib/spiderfw/model/storage/db/db_storage.rb +39 -17
  132. data/lib/spiderfw/setup/app_manager.rb +69 -31
  133. data/lib/spiderfw/setup/setup_task.rb +76 -8
  134. data/lib/spiderfw/spider.rb +21 -1
  135. data/lib/spiderfw/templates/blocks/text.rb +4 -4
  136. data/lib/spiderfw/templates/blocks/text_domain.rb +25 -0
  137. data/lib/spiderfw/templates/blocks/widget.rb +1 -1
  138. data/lib/spiderfw/templates/layout.rb +160 -92
  139. data/lib/spiderfw/templates/resources/less.rb +10 -2
  140. data/lib/spiderfw/templates/resources/sass.rb +66 -9
  141. data/lib/spiderfw/templates/template.rb +35 -10
  142. data/lib/spiderfw/templates/template_blocks.rb +6 -3
  143. data/lib/spiderfw/utils/logger.rb +20 -0
  144. data/lib/spiderfw/utils/memory.rb +7 -3
  145. data/lib/spiderfw/widget/widget.rb +13 -7
  146. data/lib/spiderfw/widget/widget_attributes.rb +2 -2
  147. data/spider.gemspec +1 -0
  148. metadata +68 -11
  149. data/apps/core/admin/views/spider_admin.layout.shtml +0 -23
@@ -30,9 +30,9 @@ module Spider
30
30
  @app_routes ||= []
31
31
  end
32
32
 
33
- def self.print_app_routes
34
- max_length = app_routes.inject(0){ |m, r| m > r[0].length ? m : r[0].length }
35
- app_routes.map{ |r| "#{r[0].ljust(max_length+3)} -> #{r[1]}"}.sort.join("\n")
33
+ def self.print_app_routes(routes=app_routes)
34
+ max_length = routes.inject(0){ |m, r| m > r[0].length ? m : r[0].length }
35
+ routes.map{ |r| "#{r[0].ljust(max_length+3)} -> #{r[1]}"}.sort.join("\n")
36
36
  end
37
37
 
38
38
  end
@@ -99,6 +99,16 @@ module Spider
99
99
  @request.format = $2.to_sym
100
100
  end
101
101
  # Spider.reload_sources if Spider.conf.get('webserver.reload_sources')
102
+ static_level = Spider.conf.get('log.static_extensions')
103
+ if @request.format && @request.get? && static_level != true
104
+ allowed = Spider.conf.get('log.non_static_extensions_list')
105
+ unless allowed.include?(@request.format.to_s)
106
+ Spider.logger.info("GET #{@request.path}")
107
+ @logger_static_prev = Spider.logger.set_thread_level(static_level)
108
+ end
109
+ end
110
+ Spider::Logger.debug("REQUEST:")
111
+ Spider::Logger.debug(@request.env)
102
112
  Spider.logger.info("Request: #{@request.http_method} #{@request.http_host} #{@request.path}")
103
113
  super(action, *arguments)
104
114
  end
@@ -116,6 +126,7 @@ module Spider
116
126
  action = $1 if (action =~ /(.+)\.(\w+)$/) # strip extension, set format
117
127
  @request.session.persist if @request.session && @request.session.respond_to?(:persist)
118
128
  super(action, *arguments)
129
+ @Spider.logger.set_thread_level(@logger_static_prev) if @logger_static_prev
119
130
  end
120
131
 
121
132
  def ensure(action='', *arguments)
@@ -10,8 +10,8 @@ module Spider; module ControllerMixins
10
10
 
11
11
  def self.included(klass)
12
12
  super
13
- klass.route('public/', :serve_static, :do => lambda{ @serving_static = true })
14
- klass.route('w/', :serve_widget_static, :do => lambda{ @serving_static = true })
13
+ @static_content_route ||= 'public/'
14
+ klass.route(@static_content_route, :serve_static, :do => lambda{ @serving_static = true })
15
15
  if (klass < Visual)
16
16
  klass.no_layout('public')
17
17
  klass.no_layout('serve_static')
@@ -61,14 +61,6 @@ module Spider; module ControllerMixins
61
61
  output_static(full_path)
62
62
  end
63
63
 
64
- def serve_widget_static(path)
65
- path = sanitize_path(path)
66
- parts = path.split('/public/', 2)
67
- raise Spider::Controller::NotFound.new(path) unless parts[1]
68
- full_path = self.class.app.widgets_path+'/'+parts[0]+'/public/'+parts[1]
69
- output_static(full_path)
70
- end
71
-
72
64
  def output_static(full_path, file_name=nil)
73
65
  file_name ||= File.basename(full_path)
74
66
  @request.misc[:is_static] = true
@@ -89,11 +81,10 @@ module Spider; module ControllerMixins
89
81
  begin
90
82
  if_modified = Time.httpdate(@request.env['HTTP_IF_MODIFIED_SINCE'])
91
83
  rescue ArgumentError # Passenger with IE6 has this header wrong
92
- if_modified = 0
93
84
  end
94
85
  max_age = nil
95
86
  fresh = true
96
- if fresh && mtime <= if_modified
87
+ if fresh && if_modified && mtime <= if_modified
97
88
  debug("Not modified since #{if_modified}: #{full_path}")
98
89
  #@response.status = Spider::HTTP::NOT_MODIFIED
99
90
  @response.headers.delete("Content-Type")
@@ -43,6 +43,16 @@ module Spider; module ControllerMixins
43
43
  if Spider.runmode != 'devel' && File.exists?(File.join(Spider.paths[:tmp], 'maintenance.txt'))
44
44
  raise Spider::Controller::Maintenance
45
45
  end
46
+ n_route = dispatch_next(action)
47
+ obj = n_route.obj if n_route
48
+ if obj.is_a?(Visual) && !(obj.respond_to?(:serving_static?) && obj.serving_static?(n_route.path))
49
+ set_layout = @layout || @dispatcher_layout
50
+ if set_layout
51
+ set_layout = [set_layout] unless set_layout.is_a?(Array)
52
+ set_layout.map{ |l| self.class.load_layout(l) }
53
+ obj.dispatcher_layout = set_layout
54
+ end
55
+ end
46
56
  super
47
57
  end
48
58
 
@@ -210,13 +220,12 @@ module Spider; module ControllerMixins
210
220
  end
211
221
  init_widgets(template, layout)
212
222
  return template if done?
213
- Spider::GetText.in_domain(self.class.app.short_name){
214
- if layout
215
- layout.render(scene)
216
- else
217
- template.render(scene)
218
- end
219
- }
223
+
224
+ if layout
225
+ layout.render(scene)
226
+ else
227
+ template.render(scene)
228
+ end
220
229
  return template
221
230
  end
222
231
 
@@ -236,21 +245,12 @@ module Spider; module ControllerMixins
236
245
 
237
246
 
238
247
 
239
- def dispatched_object(route)
240
- obj = super
241
- if (obj.is_a?(Visual))
242
- set_layout = @layout || @dispatcher_layout
243
- if set_layout
244
- set_layout = [set_layout] unless set_layout.is_a?(Array)
245
- set_layout.map{ |l| self.class.load_layout(l) }
246
- obj.dispatcher_layout = set_layout
247
- end
248
- end
249
- return obj
250
- end
251
248
 
252
249
  def try_rescue(exc)
253
- exc.uuid = UUIDTools::UUID.random_create.to_s if exc.respond_to?(:uuid=)
250
+ if exc.respond_to?(:uuid=)
251
+ return super if exc.uuid # Error page already outputted
252
+ exc.uuid = UUIDTools::UUID.random_create.to_s
253
+ end
254
254
  format = self.class.output_format(:error) || :html
255
255
  unless exc.is_a?(Spider::Controller::Maintenance) || exc.is_a?(Spider::Controller::NotFound)
256
256
  return super unless @executed_format == :html
@@ -555,6 +555,7 @@ module Spider; module ControllerMixins
555
555
  path = Spider::Layout.named_layouts[path]
556
556
  end
557
557
  resource = Spider::Template.find_resource(path+'.layout', layout_path, self)
558
+ raise "Layout #{path} not found" unless resource && resource.path
558
559
  layout = Spider::Layout.new(resource.path)
559
560
  layout.definer_class = resource.definer
560
561
  layout.asset_set = params[:assets] if params[:assets]
@@ -21,10 +21,8 @@ module Spider
21
21
 
22
22
 
23
23
  def initialize(env)
24
- Spider::Logger.debug("REQUEST:")
25
- Spider::Logger.debug(env)
26
24
  @env = env
27
- @locale = Locale.current[0]
25
+ @locale = Spider.locale
28
26
  @misc = {}
29
27
  @params = {}
30
28
  @action = ""
@@ -24,7 +24,7 @@ module Spider; module HTTP
24
24
 
25
25
 
26
26
  def shutdown_server
27
- @server.stop
27
+ @server.stop if @server
28
28
  end
29
29
 
30
30
 
@@ -12,6 +12,7 @@ module Spider
12
12
 
13
13
  module GetText
14
14
 
15
+ # Executes a block of code in the given text_domain
15
16
  def self.in_domain(domain, &block)
16
17
  prev_text_domain = FastGettext.text_domain
17
18
  FastGettext.text_domain = domain if FastGettext.translation_repositories.key?(domain)
@@ -19,6 +20,19 @@ module Spider
19
20
  FastGettext.text_domain = prev_text_domain
20
21
  v
21
22
  end
23
+
24
+ # Sets the current text_domain; return the previous domain
25
+ def self.set_domain(domain)
26
+ prev_text_domain = FastGettext.text_domain
27
+ FastGettext.text_domain = domain if FastGettext.translation_repositories.key?(domain)
28
+ prev_text_domain
29
+ end
30
+
31
+ # Sets the current text_domain; assumes the domain was already set before, so skips any
32
+ # check for domain validity
33
+ def self.restore_domain(domain)
34
+ FastGettext.text_domain = domain
35
+ end
22
36
 
23
37
  end
24
38
 
@@ -21,13 +21,13 @@ module Spider; module I18n
21
21
  str =~ Spider::TemplateBlocks::GettextRegexp
22
22
  found = false
23
23
  ary.each do |msg|
24
- if (msg[0] == $1)
24
+ if (msg[0] == $2)
25
25
  msg << "#{file}:#{cnt}"
26
26
  found = true
27
27
  break
28
28
  end
29
29
  end
30
- ary << [$1, "#{file}:#{cnt}"] unless found
30
+ ary << [$2, "#{file}:#{cnt}"] unless found
31
31
  end
32
32
  end
33
33
  f.close
@@ -1174,8 +1174,8 @@ module Spider; module Model
1174
1174
  qs.autoload = true
1175
1175
  qs.where(&proc)
1176
1176
  else
1177
- condition = Condition.and(params[0])
1178
- request = Request.new(params[1])
1177
+ condition = params[0].is_a?(Condition) ? params[0] : Condition.and(params[0])
1178
+ request = params[1].is_a?(Request) ? params[1] : Request.new(params[1])
1179
1179
  query = Query.new(condition, request)
1180
1180
  qs = QuerySet.new(self, query)
1181
1181
  end
@@ -1852,6 +1852,7 @@ module Spider; module Model
1852
1852
  @_modified_elements[key] = true
1853
1853
  el = self.class.elements[key.to_sym]
1854
1854
  next unless el
1855
+ next if el.attributes[:unmapped]
1855
1856
  if el.integrated? && sub = self.get(el.integrated_from)
1856
1857
  sub.set_modified(el.integrated_from_element)
1857
1858
  end
@@ -1872,7 +1873,7 @@ module Spider; module Model
1872
1873
  val.modified = false if val.is_a?(QuerySet)
1873
1874
  end
1874
1875
  self.class.elements_array.select{ |el| el.attributes[:integrated_model] && self.element_has_value?(el) }.each do |el|
1875
- self.get(el).reset_modified_elements(elements)
1876
+ self.get(el).reset_modified_elements(*elements)
1876
1877
  end
1877
1878
 
1878
1879
  nil
@@ -90,43 +90,46 @@ module Spider; module Model; module Mappers
90
90
  @model.each_element do |element|
91
91
  next if !mapped?(element) || element.integrated?
92
92
  next if save_mode == :update && !obj.element_modified?(element)
93
- if (save_mode == :insert)
93
+ if save_mode == :insert
94
94
  if element.attributes[:autoincrement] && !schema.attributes(element.name)[:autoincrement]
95
95
  obj.set(element.name, @storage.sequence_next(schema.sequence(element.name)))
96
96
  end
97
97
  end
98
- if (!element.multiple?)
99
- next if (save_mode == :update && element.primary_key?)
100
- next if (element.model? && !schema.has_foreign_fields?(element.name))
101
- element_val = obj.get(element)
102
- # next if (element.model? && (!(element_val = obj.get(element)) || !))
103
- next if (element.integrated?)
104
- element_val = nil if element.model? && element_val.is_a?(BaseModel) && !element_val.primary_keys_set?
105
- if (element.model?)
106
- element.model.primary_keys.each do |key|
107
- # FIXME! only works with one primary key
108
- if (key.model?)
109
- key_type = key.model.primary_keys[0].type
110
- key_value = element_val ? element_val.get(key.name).get(key.model.primary_keys[0]) : nil
111
- else
112
- key_type = key.model? ? key.model.primary_keys[0].type : key.type
113
- key_value = element_val ? element_val.get(key.name) : nil
114
- end
115
- store_key = schema.foreign_key_field(element.name, key.name)
116
- next if store_key.is_a?(FieldExpression)
117
- values[store_key] = map_save_value(key_type, key_value, save_mode)
118
- end
119
- else
120
- store_key = schema.field(element.name)
121
- values[store_key] = map_save_value(element.type, element_val, save_mode)
122
- end
123
- end
98
+ next if element.multiple?
99
+ next if save_mode == :update && element.primary_key?
100
+ next if element.model? && !schema.has_foreign_fields?(element.name)
101
+
102
+ element_val = obj.get(element)
103
+ prepare_save_value(element, element_val, save_mode, values)
124
104
  end
125
105
  end
126
106
  return {
127
107
  :values => values
128
108
  }
129
109
  end
110
+
111
+ def prepare_save_value(element, element_val, save_mode, values={})
112
+ element_val = nil if element.model? && element_val.is_a?(BaseModel) && !element_val.primary_keys_set?
113
+ if element.model?
114
+ element.model.primary_keys.each do |key|
115
+ # FIXME! only works with one primary key
116
+ if (key.model?)
117
+ key_type = key.model.primary_keys[0].type
118
+ key_value = element_val ? element_val.get(key.name).get(key.model.primary_keys[0]) : nil
119
+ else
120
+ key_type = key.model? ? key.model.primary_keys[0].type : key.type
121
+ key_value = element_val ? element_val.get(key.name) : nil
122
+ end
123
+ store_key = schema.foreign_key_field(element.name, key.name)
124
+ next if store_key.is_a?(FieldExpression)
125
+ values[store_key] = map_save_value(key_type, key_value, save_mode)
126
+ end
127
+ else
128
+ store_key = schema.field(element.name)
129
+ values[store_key] = map_save_value(element.type, element_val, save_mode)
130
+ end
131
+ values
132
+ end
130
133
 
131
134
  # Insert preprocessing
132
135
  def prepare_insert(obj) #:nodoc:
@@ -152,27 +155,29 @@ module Spider; module Model; module Mappers
152
155
  end
153
156
 
154
157
  # Updates according to a condition, storing the values, which must passed as a Hash.
158
+ # Condition may be nil.
155
159
  def bulk_update(values, condition)
156
160
  db_values = {}
157
161
  joins = []
158
162
  integrated = {}
159
- condition = preprocess_condition(condition)
163
+ condition = preprocess_condition(condition) if condition
160
164
  values.each do |key, val|
161
165
  element = @model.elements[key]
162
- if (element.integrated?)
166
+ if element.integrated?
163
167
  integrated[element.integrated_from] ||= {}
164
168
  integrated[element.integrated_from][key] = val
165
169
  next
166
170
  end
167
171
  next if !mapped?(element)
168
- next if element.model? && val != nil
169
- store_key = schema.field(element.name)
170
- next unless store_key
171
- if (val.is_a?(Spider::QueryFuncs::Expression))
172
+ next if element.multiple?
173
+ next if element.model? && !schema.has_foreign_fields?(element.name)
174
+ if val.is_a?(Spider::QueryFuncs::Expression)
172
175
  joins += prepare_expression(val)
176
+ store_key = schema.field(element.name)
177
+ next unless store_key
173
178
  db_values[store_key] = val
174
179
  else
175
- db_values[store_key] = map_save_value(element.type, val, :update)
180
+ prepare_save_value(element, val, :update, db_values)
176
181
  end
177
182
  end
178
183
  integrated.each do |i_el, i_values|
@@ -181,11 +186,13 @@ module Spider; module Model; module Mappers
181
186
  end
182
187
  return if db_values.empty?
183
188
  save = {:table => schema.table, :values => db_values}
184
- condition, c_joins = prepare_condition(condition)
185
- joins += c_joins
186
- save[:condition] = condition
189
+ if condition
190
+ condition, c_joins = prepare_condition(condition)
191
+ joins += c_joins
192
+ save[:condition] = condition
193
+ end
187
194
  save[:joins] = prepare_joins(joins)
188
- sql, bind_vars = @storage.sql_update(save)
195
+ sql, bind_vars = @storage.sql_update(save, true)
189
196
  return @storage.execute(sql, *bind_vars)
190
197
  end
191
198
 
@@ -252,7 +259,11 @@ module Spider; module Model; module Mappers
252
259
  end
253
260
  model = obj_or_model.is_a?(Class) ? obj_or_model : obj_or_model.model
254
261
  data = {}
262
+ extra_data = {}
255
263
  request.keys.each do |element_name|
264
+ if element_name.is_a?(QueryFuncs::SelectFunction)
265
+ extra_data[element_name.as] = result[element_name.as.to_s]
266
+ end
256
267
  element = @model.elements[element_name]
257
268
  result_value = nil
258
269
  next if !element || element.integrated? || !have_references?(element)
@@ -282,6 +293,9 @@ module Spider; module Model; module Mappers
282
293
  return nil
283
294
  end
284
295
  data.keys.each{ |el| obj.element_loaded(el) }
296
+ extra_data.each do |k, v|
297
+ obj[k] = v
298
+ end
285
299
  if (request.polymorphs)
286
300
  request.polymorphs.each do |model, polym_request|
287
301
  polym_result = {}
@@ -315,7 +329,9 @@ module Spider; module Model; module Mappers
315
329
  # Generates a select hash description based on the query.
316
330
  def prepare_select(query) #:nodoc:
317
331
  condition, joins = prepare_condition(query.condition)
318
- elements = query.request.keys.select{ |k| mapped?(k) }
332
+ elements = query.request.keys.select{ |k| !k.is_a?(QueryFuncs::SelectFunction) && mapped?(k) }
333
+ select_functions = query.request.keys.select{ |k| k.is_a?(QueryFuncs::SelectFunction) }
334
+
319
335
  keys = []
320
336
  primary_keys = []
321
337
  types = {}
@@ -331,6 +347,8 @@ module Spider; module Model; module Mappers
331
347
  oj[:as] ||= "ORD#{cnt+=1}" if joins.select{ |j| j[:to] == oj[:to] }.length > 0
332
348
  end
333
349
  joins += order_joins if order_joins
350
+ group_by, group_by_joins = prepare_group_by(query)
351
+ joins += group_by_joins if group_by_joins
334
352
  seen_fields = {}
335
353
  model_pks = []
336
354
  @model.primary_keys.each do |pk|
@@ -363,15 +381,11 @@ module Spider; module Model; module Mappers
363
381
  end
364
382
  end
365
383
  sub_request = query.request[element.name]
366
- # if (can_join?(element) && sub_request.is_a?(Request) &&
367
- # sub_request.select{|k, v| !element.model.elements[k].primary_key?}.length > 0)
368
- # sub_request = element.mapper.prepare_query_request(sub_request).reject{ |name, req| element.reverse == name }
369
- # sub_select = element.mapper.prepare_select(Query.new(nil, sub_request))
370
- # keys += sub_select[:keys]
371
- # joins << get_join(element)
372
- # end
373
384
  end
374
385
  end
386
+ select_functions.each do |f|
387
+ keys << prepare_queryfunc(f)
388
+ end
375
389
  if (query.request.polymorphs? || !query.condition.polymorphs.empty?)
376
390
  only_conditions = {:conj => 'or', :values => []} if (query.request.only_polymorphs?)
377
391
  polymorphs = (query.request.polymorphs.keys + query.condition.polymorphs).uniq
@@ -427,6 +441,7 @@ module Spider; module Model; module Mappers
427
441
  :condition => condition,
428
442
  :joins => joins,
429
443
  :order => order,
444
+ :group_by => group_by,
430
445
  :offset => query.offset,
431
446
  :limit => query.limit
432
447
  }
@@ -580,9 +595,10 @@ module Spider; module Model; module Mappers
580
595
  cond[:values] << [field, comp, v]
581
596
  joins += field.joins
582
597
  if is_having
583
- cond[:group_by_fields] += k.inner_elements.map{ |el_name, owner_func|
584
- owner_func.mapper_fields[el_name.to_s]
585
- }
598
+ k.inner_elements.each do |el_name, owner_func|
599
+ next if owner_func.is_a?(QueryFuncs::AggregateFunction)
600
+ cond[:group_by_fields] <<= owner_func.mapper_fields[el_name.to_s]
601
+ end
586
602
  end
587
603
  next
588
604
  end
@@ -600,6 +616,7 @@ module Spider; module Model; module Mappers
600
616
  element_cond = {:conj => 'AND', :values => [], :is_having => is_having}
601
617
  v.each_with_comparison do |el_k, el_v, el_comp|
602
618
  field = model_schema.foreign_key_field(element.name, el_k)
619
+ next if field.is_a?(FixedExpression)
603
620
  el_comp ||= '='
604
621
  op = el_comp
605
622
  field_cond = [field, op, map_condition_value(element.model.elements[el_k.to_sym].type, el_v)]
@@ -609,50 +626,56 @@ module Spider; module Model; module Mappers
609
626
  cond[:values] << element_cond
610
627
  else
611
628
  if element.storage == model.mapper.storage
612
- join_type = join_info[element.name.to_s] ? :inner : :left
613
- sub_join = model.mapper.get_join(element, join_type)
614
- # FIXME! cleanup, and apply the check to joins acquired in other places, too (maybe pass the current joins to get_join)
615
- existent = joins.select{ |j| j[:to] == sub_join[:to] }
616
- j_cnt = nil
617
- had_join = false
618
- existent.each do |j|
619
- if sub_join[:to] == j[:to] && sub_join[:keys] == j[:keys] && sub_join[:conditions] == j[:conditions]
620
- # if any condition allows a left join, then a left join should be used here as well
621
- j[:type] = :left if sub_join[:type] == :left
622
- sub_join = j
623
- had_join = true
624
- break
625
- else
626
- j_cnt ||= 0; j_cnt += 1
627
- end
628
- end
629
- sub_join[:as] = "#{sub_join[:to]}#{j_cnt}" if j_cnt
630
- joins << sub_join unless had_join
631
629
 
630
+ needs_join = true
632
631
  if v.nil? && comp == '='
633
632
  el_model_schema = model_schema
634
633
  element_cond = {:conj => 'AND', :values => [], :is_having => is_having}
635
634
  if model.mapper.have_references?(element.name)
636
635
  el_name = element.name
637
636
  el_model = element.model
637
+ needs_join = false
638
638
  elsif element.junction?
639
639
  el_model = element.type
640
640
  el_model_schema = element.model.mapper.schema
641
641
  el_name = element.attributes[:junction_their_element]
642
642
  else
643
- el_model = element.type
644
- el_model_schema = el_model.mapper.schema
643
+ el_model = @model
644
+ el_model_schema = element.type.mapper.schema
645
645
  el_name = element.reverse
646
646
  end
647
647
  el_model.primary_keys.each do |k|
648
648
  field = el_model_schema.foreign_key_field(el_name, k.name)
649
- field_cond = [field, comp, map_condition_value(element.model.elements[k.name].type, nil)]
649
+ next if field.is_a?(FixedExpression)
650
+ field_cond = [field, comp, map_condition_value(el_model.elements[k.name].type, nil)]
650
651
  element_cond[:values] << field_cond
651
652
  element_cond[:is_having] = is_having
652
653
  cond[:group_by_fields] << field if is_having
653
654
  end
654
655
  cond[:values] << element_cond
655
- elsif v
656
+ end
657
+ if needs_join
658
+ join_type = join_info[element.name.to_s] ? :inner : :left
659
+ sub_join = model.mapper.get_join(element, join_type)
660
+ # FIXME! cleanup, and apply the check to joins acquired in other places, too (maybe pass the current joins to get_join)
661
+ existent = joins.select{ |j| j[:to] == sub_join[:to] }
662
+ j_cnt = nil
663
+ had_join = false
664
+ existent.each do |j|
665
+ if sub_join[:to] == j[:to] && sub_join[:keys] == j[:keys] && sub_join[:conditions] == j[:conditions]
666
+ # if any condition allows a left join, then a left join should be used here as well
667
+ j[:type] = :left if sub_join[:type] == :left
668
+ sub_join = j
669
+ had_join = true
670
+ break
671
+ else
672
+ j_cnt ||= 0; j_cnt += 1
673
+ end
674
+ end
675
+ sub_join[:as] = "#{sub_join[:to]}#{j_cnt}" if j_cnt
676
+ joins << sub_join unless had_join
677
+ end
678
+ if v
656
679
  sub_condition, sub_joins = element.mapper.prepare_condition(v, :table => sub_join[:as],
657
680
  :joins => joins, :join_info => el_join_info, :is_having => is_having || nil)
658
681
  sub_condition[:table] = sub_join[:as] if sub_join[:as]
@@ -667,7 +690,7 @@ module Spider; module Model; module Mappers
667
690
  end
668
691
  elsif(model_schema.field(element.name))
669
692
  field = model_schema.field(element.name)
670
- field = FieldInAliasedTable(field, options[:table]) if options[:table]
693
+ field = FieldInAliasedTable.new(field, options[:table]) if options[:table]
671
694
  op = comp ? comp : '='
672
695
  if (v.is_a?(Spider::QueryFuncs::Expression))
673
696
  v_joins = prepare_expression(v)
@@ -815,6 +838,7 @@ module Spider; module Model; module Mappers
815
838
  owner_func.mapper_fields[el_name.to_s] = el_model.mapper.schema.field(el.name)
816
839
  end
817
840
  f = FieldFunction.new(storage.function(func), schema.table, joins)
841
+ f.as = func.as if func.is_a?(QueryFuncs::SelectFunction)
818
842
  f.aggregate = true if func.has_aggregates?
819
843
  return f
820
844
  end
@@ -850,7 +874,9 @@ module Spider; module Model; module Mappers
850
874
  if el.model?
851
875
  if el_model.mapper.have_references?(el) || el.model.storage != storage
852
876
  el.model.primary_keys.each do |pk|
853
- fields << [el_model.mapper.schema.foreign_key_field(el.name, pk.name), direction]
877
+ field = el_model.mapper.schema.foreign_key_field(el.name, pk.name)
878
+ next if field.is_a?(FixedExpression)
879
+ fields << [field, direction]
854
880
  end
855
881
  else
856
882
  el.model.primary_keys.each do |pk|
@@ -868,6 +894,34 @@ module Spider; module Model; module Mappers
868
894
  return [fields, joins]
869
895
  end
870
896
 
897
+ def prepare_group_by(query)
898
+ return nil if !query.group_by_elements
899
+ joins = []
900
+ fields = []
901
+ query.group_by_elements.each do |gb|
902
+ el_model = @model
903
+ el_joins, el_model, el = get_deep_join(gb)
904
+ # FIXME: this is almost identical to prepare_order
905
+ if el.model?
906
+ if el_model.mapper.have_references?(el) || el.model.storage != storage
907
+ el.model.primary_keys.each do |pk|
908
+ fields << el_model.mapper.schema.foreign_key_field(el.name, pk.name)
909
+ end
910
+ else
911
+ el.model.primary_keys.each do |pk|
912
+ fields << el.model.mapper.schema.field(pk.name)
913
+ end
914
+ end
915
+ else
916
+ raise "Order on unmapped element #{el_model.name}.#{el.name}" unless el_model.mapper.mapped?(el)
917
+ field = el_model.mapper.schema.field(el.name)
918
+ fields << field
919
+ end
920
+ joins += el_joins
921
+ end
922
+ return [fields, joins]
923
+ end
924
+
871
925
  # Returns a type accepted by the storage for type.
872
926
  def map_type(type)
873
927
  st = type
@@ -1060,16 +1114,20 @@ module Spider; module Model; module Mappers
1060
1114
  end
1061
1115
  end
1062
1116
 
1063
- # Autogenerates schema. Returns a DbSchema.
1064
- def generate_schema(schema=nil)
1065
- had_schema = schema ? true : false
1066
- schema ||= DbSchema.new
1117
+ def schema_table_name
1067
1118
  n = @model.name.sub('::Models', '')
1068
1119
  app = @model.app
1069
1120
  app_name = app.name if app
1070
1121
  short_prefix = app.short_prefix if app
1071
1122
  n.sub!(app_name, short_prefix) if short_prefix
1072
- schema.table ||= @model.attributes[:db_table] || @storage.table_name(n)
1123
+ @storage.table_name(n)
1124
+ end
1125
+
1126
+ # Autogenerates schema. Returns a DbSchema.
1127
+ def generate_schema(schema=nil)
1128
+ had_schema = schema ? true : false
1129
+ schema ||= DbSchema.new
1130
+ schema.table ||= @model.attributes[:db_table] || schema_table_name
1073
1131
  integrated_pks = []
1074
1132
  @model.each_element do |element|
1075
1133
  if element.integrated?
@@ -604,7 +604,8 @@ module Spider; module Model
604
604
  merged[merged_obj.object_id] = true
605
605
  end
606
606
  query.request.keys.each do |k, v|
607
- set.element_loaded(k) if have_references?(k)
607
+ # k may be a SelectFunction
608
+ set.element_loaded(k) if !k.is_a?(QueryFuncs::SelectFunction) && have_references?(k)
608
609
  end
609
610
  set.each_current do |obj|
610
611
  next if merged[obj.object_id]
@@ -1054,7 +1055,10 @@ module Spider; module Model
1054
1055
  obj.class.elements_array.each do |el|
1055
1056
  next unless obj.element_has_value?(el)
1056
1057
  next unless el.model?
1057
- children << obj.get(el)
1058
+ next unless obj.element_modified?(el)
1059
+ val = obj.get(el)
1060
+ next unless val.modified?
1061
+ children << val
1058
1062
  end
1059
1063
  children
1060
1064
  end
@@ -12,7 +12,7 @@ module Spider; module Migrations
12
12
  field = @options[:field_name]
13
13
  if !field
14
14
  schema_field = @model.mapper.schema.field(@element)
15
- field = schema_field.name
15
+ field = schema_field.name if schema_field
16
16
  end
17
17
  field ||= @model.mapper.storage.column_name(@element)
18
18
  desc = @model.mapper.storage.describe_table(@model.mapper.schema.table)