engine2 1.0.0

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 (145) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -0
  3. data/Rakefile +138 -0
  4. data/conf/message.yaml +93 -0
  5. data/conf/message_pl.yaml +93 -0
  6. data/engine2.gemspec +34 -0
  7. data/lib/engine2.rb +34 -0
  8. data/lib/engine2/action.rb +217 -0
  9. data/lib/engine2/core.rb +572 -0
  10. data/lib/engine2/handler.rb +134 -0
  11. data/lib/engine2/meta.rb +969 -0
  12. data/lib/engine2/meta/decode_meta.rb +110 -0
  13. data/lib/engine2/meta/delete_meta.rb +73 -0
  14. data/lib/engine2/meta/form_meta.rb +144 -0
  15. data/lib/engine2/meta/infra_meta.rb +292 -0
  16. data/lib/engine2/meta/link_meta.rb +133 -0
  17. data/lib/engine2/meta/list_meta.rb +284 -0
  18. data/lib/engine2/meta/save_meta.rb +63 -0
  19. data/lib/engine2/meta/view_meta.rb +22 -0
  20. data/lib/engine2/model.rb +390 -0
  21. data/lib/engine2/models/Files.rb +38 -0
  22. data/lib/engine2/models/UserInfo.rb +24 -0
  23. data/lib/engine2/post_bootstrap.rb +83 -0
  24. data/lib/engine2/pre_bootstrap.rb +27 -0
  25. data/lib/engine2/scheme.rb +202 -0
  26. data/lib/engine2/templates.rb +229 -0
  27. data/lib/engine2/type_info.rb +342 -0
  28. data/lib/engine2/version.rb +9 -0
  29. data/public/assets/javascripts.js +13 -0
  30. data/public/assets/styles.css +4 -0
  31. data/public/css/angular-motion.css +1022 -0
  32. data/public/css/angular-ui-tree.min.css +1 -0
  33. data/public/css/app.css +196 -0
  34. data/public/css/bootstrap-additions.css +1560 -0
  35. data/public/css/bootstrap.min.css +11 -0
  36. data/public/css/font-awesome.min.css +4 -0
  37. data/public/favicon.ico +0 -0
  38. data/public/fonts/FontAwesome.otf +0 -0
  39. data/public/fonts/fontawesome-webfont.eot +0 -0
  40. data/public/fonts/fontawesome-webfont.svg +655 -0
  41. data/public/fonts/fontawesome-webfont.ttf +0 -0
  42. data/public/fonts/fontawesome-webfont.woff +0 -0
  43. data/public/fonts/fontawesome-webfont.woff2 +0 -0
  44. data/public/fonts/glyphicons-halflings-regular.eot +0 -0
  45. data/public/fonts/glyphicons-halflings-regular.svg +288 -0
  46. data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  47. data/public/fonts/glyphicons-halflings-regular.woff +0 -0
  48. data/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
  49. data/public/images/file.png +0 -0
  50. data/public/images/folder-closed.png +0 -0
  51. data/public/images/folder.png +0 -0
  52. data/public/images/node-closed-2.png +0 -0
  53. data/public/images/node-closed-light.png +0 -0
  54. data/public/images/node-closed.png +0 -0
  55. data/public/images/node-opened-2.png +0 -0
  56. data/public/images/node-opened-light.png +0 -0
  57. data/public/images/node-opened.png +0 -0
  58. data/public/img/ajax-loader-dark.gif +0 -0
  59. data/public/img/ajax-loader-light.gif +0 -0
  60. data/public/img/ajax-loader.gif +0 -0
  61. data/public/js/angular-animate.js +4115 -0
  62. data/public/js/angular-cookies.js +322 -0
  63. data/public/js/angular-local-storage.js +455 -0
  64. data/public/js/angular-route.js +1022 -0
  65. data/public/js/angular-sanitize.js +717 -0
  66. data/public/js/angular-strap.js +4339 -0
  67. data/public/js/angular-strap.tpl.js +43 -0
  68. data/public/js/angular-ui-tree.js +1569 -0
  69. data/public/js/angular.js +30714 -0
  70. data/public/js/i18n/angular-locale_pl.js +115 -0
  71. data/public/js/lodash.custom.min.js +97 -0
  72. data/public/js/ng-file-upload-shim.min.js +2 -0
  73. data/public/js/ng-file-upload.min.js +3 -0
  74. data/views/app.coffee +3 -0
  75. data/views/engine2.coffee +557 -0
  76. data/views/engine2actions.coffee +849 -0
  77. data/views/engine2templates.coffee +0 -0
  78. data/views/fields/blob.slim +22 -0
  79. data/views/fields/bs_select.slim +10 -0
  80. data/views/fields/bsselect_picker.slim +18 -0
  81. data/views/fields/bsselect_picker_opt.slim +22 -0
  82. data/views/fields/checkbox.slim +11 -0
  83. data/views/fields/checkbox_buttons.slim +6 -0
  84. data/views/fields/checkbox_buttons_opt.slim +8 -0
  85. data/views/fields/currency.slim +10 -0
  86. data/views/fields/date.slim +21 -0
  87. data/views/fields/date_range.slim +44 -0
  88. data/views/fields/date_time.slim +42 -0
  89. data/views/fields/datetime.slim +42 -0
  90. data/views/fields/decimal.slim +11 -0
  91. data/views/fields/decimal_date.slim +22 -0
  92. data/views/fields/decimal_time.slim +26 -0
  93. data/views/fields/email.slim +13 -0
  94. data/views/fields/file_store.slim +61 -0
  95. data/views/fields/input_text.slim +14 -0
  96. data/views/fields/integer.slim +11 -0
  97. data/views/fields/list_bsselect.slim +18 -0
  98. data/views/fields/list_bsselect_opt.slim +21 -0
  99. data/views/fields/list_buttons.slim +3 -0
  100. data/views/fields/list_buttons_opt.slim +5 -0
  101. data/views/fields/list_select.slim +11 -0
  102. data/views/fields/list_select_opt.slim +15 -0
  103. data/views/fields/password.slim +14 -0
  104. data/views/fields/radio_checkbox.slim +10 -0
  105. data/views/fields/scaffold.slim +2 -0
  106. data/views/fields/scaffold_picker.slim +20 -0
  107. data/views/fields/select_picker.slim +12 -0
  108. data/views/fields/select_picker_opt.slim +16 -0
  109. data/views/fields/text_area.slim +10 -0
  110. data/views/fields/time.slim +22 -0
  111. data/views/fields/typeahead_picker.slim +25 -0
  112. data/views/index.slim +44 -0
  113. data/views/infra/index.slim +5 -0
  114. data/views/infra/inspect.slim +81 -0
  115. data/views/modals/close_m.slim +15 -0
  116. data/views/modals/confirm_m.slim +19 -0
  117. data/views/modals/empty_m.slim +12 -0
  118. data/views/modals/menu_m.slim +13 -0
  119. data/views/modals/yes_no_m.slim +19 -0
  120. data/views/panels/menu_m.slim +9 -0
  121. data/views/scaffold/confirm.slim +3 -0
  122. data/views/scaffold/fields.slim +10 -0
  123. data/views/scaffold/form.slim +11 -0
  124. data/views/scaffold/list.slim +42 -0
  125. data/views/scaffold/message.slim +3 -0
  126. data/views/scaffold/search.slim +20 -0
  127. data/views/scaffold/view.slim +18 -0
  128. data/views/search_fields/bsmselect_picker.slim +25 -0
  129. data/views/search_fields/bsselect_picker.slim +24 -0
  130. data/views/search_fields/checkbox.slim +11 -0
  131. data/views/search_fields/checkbox2.slim +14 -0
  132. data/views/search_fields/checkbox_buttons.slim +10 -0
  133. data/views/search_fields/date_range.slim +46 -0
  134. data/views/search_fields/decimal_date_range.slim +47 -0
  135. data/views/search_fields/input_text.slim +18 -0
  136. data/views/search_fields/integer.slim +18 -0
  137. data/views/search_fields/integer_range.slim +27 -0
  138. data/views/search_fields/list_bsmselect.slim +24 -0
  139. data/views/search_fields/list_bsselect.slim +22 -0
  140. data/views/search_fields/list_buttons.slim +8 -0
  141. data/views/search_fields/list_select.slim +17 -0
  142. data/views/search_fields/scaffold_picker.slim +19 -0
  143. data/views/search_fields/select_picker.slim +17 -0
  144. data/views/search_fields/typeahead_picker.slim +25 -0
  145. metadata +327 -0
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+
3
+ module Engine2
4
+ class ViewMeta < Meta
5
+ meta_type :view
6
+ include MetaViewSupport, MetaQuerySupport
7
+
8
+ def record handler, record
9
+ end
10
+
11
+ def invoke handler
12
+ handler.permit id = handler.params[:id]
13
+ record = get_query[assets[:model].primary_keys_hash_qualified(split_keys(id))]
14
+ if record
15
+ static.record(handler, record)
16
+ {record: record}
17
+ else
18
+ handler.halt_not_found LOCS[:no_entry]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,390 @@
1
+ # coding: utf-8
2
+
3
+ module Engine2
4
+ module Model
5
+ attr_reader :dummies
6
+ attr_reader :many_to_one_associations, :one_to_many_associations, :many_to_many_associations #, :one_to_one_associations
7
+ attr_reader :before_save_processors, :after_save_processors, :before_destroy_processors, :after_destroy_processors
8
+ attr_reader :validation_in_transaction
9
+
10
+ class << self
11
+ def extended cls
12
+ # cls.dataset.row_proc = nil
13
+ models = cls.db.models ||= {}
14
+ raise E2Error.new("Model '#{cls.name}' already defined") if models[cls.name.to_sym]
15
+ models[cls.name.to_sym] = cls
16
+
17
+ cls.instance_eval do
18
+ @many_to_one_associations = association_reflections.select{|n, a| a[:type] == :many_to_one}
19
+ @one_to_many_associations = association_reflections.select{|n, a| a[:type] == :one_to_many}
20
+ @many_to_many_associations = association_reflections.select{|n, a| a[:type] == :many_to_many}
21
+ # @one_to_one_associations = association_reflections.select{|n, a| a[:type] == :one_to_one}
22
+ @validation_in_transaction = nil
23
+ @before_save_processors = nil
24
+ @after_save_processors = nil
25
+ @around_save_processors = nil
26
+ @before_destroy_processors = nil
27
+ @after_destroy_processors = nil
28
+ @type_info_synchronized = nil
29
+ end
30
+ cls.setup_schema
31
+ end
32
+ end
33
+
34
+ def install_processors processors
35
+ hash = {}
36
+ type_info.each_pair do |name, info|
37
+ proc = processors[info[:type]]
38
+ hash[name] = proc if proc
39
+ end
40
+ hash.empty? ? nil : hash
41
+ end
42
+
43
+ def setup_schema
44
+ @type_info = {}
45
+ @dummies = []
46
+
47
+ type_info do
48
+ schema = @model.db_schema
49
+ schema.each_pair do |name, db_info|
50
+ @info[name] = {otype: db_info[:type]}
51
+
52
+ case db_info[:type]
53
+ when :integer
54
+ integer_field name
55
+ when :string
56
+ if db_info[:db_type] == 'text'
57
+ string_field name, 10
58
+ else
59
+ string_field name, Integer(db_info[:column_size] || db_info[:db_type][/\((\d+)\)/, 1])
60
+ end
61
+
62
+ when :time
63
+ time_field name, LOCS[:default_time_format], LOCS[:default_time_model_format]
64
+ when :date
65
+ date_field name, LOCS[:default_date_format], LOCS[:default_date_model_format]
66
+ when :datetime
67
+ datetime_field name, LOCS[:default_date_format], LOCS[:default_time_format], LOCS[:default_date_model_format], LOCS[:default_time_model_format]
68
+ when :decimal
69
+ size, scale = db_info[:column_size], db_info[:scale]
70
+ unless size && scale
71
+ db_info[:db_type] =~ /decimal\((\d+),(\d+)\)/i
72
+ size, scale = $1.to_i, $2.to_i
73
+ raise E2Error.new("Cannot parse decimal type for #{db_info}") unless size || scale
74
+ end
75
+ decimal_field name, size, scale
76
+ when :blob
77
+ blob_field name, 100000
78
+ else
79
+ p db_info
80
+ raise E2Error.new("Unknown column type: #{db_info[:type].inspect} for #{name}")
81
+ end
82
+
83
+ required name if !db_info[:allow_null]
84
+ primary_key name if db_info[:primary_key]
85
+ sequence name, "SEQ_#{@model.table_name}.nextVal" if db_info[:primary_key] && !db_info[:allow_null] && !db_info[:auto_increment] && !@model.natural_key
86
+ default name, db_info[:ruby_default] if db_info[:ruby_default]
87
+ end
88
+
89
+ unique *@model.primary_keys if @model.natural_key && @model.db.adapter_scheme # uri ?
90
+
91
+ @model.many_to_one_associations.each do |aname, assoc|
92
+ many_to_one_field aname
93
+ decode assoc[:keys].first
94
+ end
95
+ end
96
+ end
97
+
98
+ def type_info &blk
99
+ if blk
100
+ raise E2Error.new("type_info already called for model #{self}") if @type_info_synchronized
101
+ TypeInfo.new(self).instance_eval(&blk)
102
+ nil
103
+ else
104
+ @type_info
105
+ end
106
+ end
107
+
108
+ def synchronize_type_info
109
+ resolve_dependencies
110
+ @before_save_processors = install_processors(BeforeSaveProcessors)
111
+ @after_save_processors = install_processors(AfterSaveProcessors)
112
+ @around_save_processors = {}
113
+ @before_destroy_processors = install_processors(BeforeDestroyProcessors)
114
+ @after_destroy_processors = install_processors(AfterDestroyProcessors)
115
+ @type_info_synchronized = true
116
+ end
117
+
118
+ def resolve_dependencies
119
+ resolved = {}
120
+ @type_info.each_pair do |name, info|
121
+ @validation_in_transaction ||= info[:transaction]
122
+ resolve_dependency(name, resolved)
123
+ end
124
+ @type_info = resolved
125
+ end
126
+
127
+ def resolve_dependency name, resolved, seen = []
128
+ seen << name
129
+ deps = @type_info[name][:depends]
130
+ deps.each do |e|
131
+ if !resolved[e]
132
+ raise "Circular dependency for field '#{name}' in model '#{self}'" if seen.include?(e)
133
+ resolve_dependency(e, resolved, seen)
134
+ end
135
+ end if deps
136
+ resolved[name] = @type_info[name]
137
+ end
138
+
139
+ attr_reader :scheme_name, :scheme_args
140
+
141
+ def scheme s_name = :default, opts = nil, &blk
142
+ @scheme_name = s_name
143
+ @scheme_args = [name.to_sym, self, opts]
144
+ SCHEMES::define_scheme name.to_sym, &blk
145
+ end
146
+
147
+ end
148
+
149
+ # def define_dummy_model
150
+ # end
151
+
152
+ module MemoryModel
153
+ def self.extended cls
154
+ cls.extend Engine2::Model
155
+ cls.class_eval do
156
+ def save
157
+ end
158
+ end
159
+
160
+ def cls.type_info &blk
161
+ if blk
162
+ super(&blk)
163
+ @columns = @type_info.keys
164
+ nil
165
+ else
166
+ @type_info
167
+ end
168
+ end
169
+
170
+ end
171
+ end
172
+
173
+ (Validations ||= {}).merge!(
174
+ boolean: lambda{|record, field, info|
175
+ value = record.values[field]
176
+ LOCS[:wrong_boolean_value] if value != info[:true_value] && value != info[:false_value]
177
+ },
178
+ string_length: lambda{|record, field, info|
179
+ value = record.values[field]
180
+ LOCS[:value_exceeds_maximum_length] if value.to_s.length > info[:length]
181
+ },
182
+ date: lambda{|record, field, info|
183
+ value = record.values[field]
184
+ begin
185
+ Sequel.string_to_date(value.to_s)
186
+ nil
187
+ end rescue LOCS[:invalid_date_format]
188
+ },
189
+ time: lambda{|record, field, info|
190
+ value = record.values[field]
191
+ begin
192
+ Sequel.string_to_time(value)
193
+ nil
194
+ end rescue LOCS[:invalid_time_format] unless value.is_a? Integer
195
+ },
196
+ decimal_date: lambda{|record, field, info|
197
+ value = record.values[field].to_s
198
+ if value == '0' && info[:required]
199
+ info[:required][:message]
200
+ else
201
+ Validations[:date].(record, field, info)
202
+ end
203
+ },
204
+ decimal_time: lambda{|record, field, info|
205
+ value = record.values[field].to_s
206
+
207
+ if value == '0' && info[:required]
208
+ info[:required][:message]
209
+ else
210
+ LOCS[:invalid_time_format] unless value.rjust(6, '0') =~ info[:model_regexp]
211
+ end
212
+
213
+ # value = record.values[field]
214
+ # begin
215
+ # Sequel.string_to_time("010101 #{value}")
216
+ # nil
217
+ # end rescue LOCS[:invalid_time_format]
218
+ },
219
+
220
+ datetime: lambda{|record, field, info|
221
+ begin
222
+ Sequel.string_to_datetime(record.values[field])
223
+ nil
224
+ end rescue LOCS[:invalid_datetime_format]
225
+ },
226
+ date_range: lambda{|record, field, info|
227
+ to_errors = record.errors[info[:other_date]]
228
+ if to_errors
229
+ record.errors.add(field, *to_errors)
230
+ nil
231
+ else
232
+ from = record.values[field].to_s
233
+ to = record.values[info[:other_date]].to_s
234
+ LOCS[:value_from_gt_to] if Sequel.string_to_date(from) > Sequel.string_to_date(to)
235
+ end
236
+ },
237
+ date_time: lambda{|record, field, info|
238
+ to_errors = record.errors[info[:other_time]]
239
+ if to_errors
240
+ record.errors.add(field, *to_errors)
241
+ nil
242
+ end
243
+ },
244
+ format: lambda{|record, field, info|
245
+ value = record.values[field]
246
+ args = info[:validations][:format]
247
+ args[:message] if value !~ args[:pattern]
248
+ },
249
+ integer: lambda{|record, field, info|
250
+ value = record.values[field]
251
+ LOCS[:invalid_number_value] unless value.is_a?(Integer) || value.to_s =~ /^\-?\d+$/
252
+ },
253
+ positive_integer: lambda{|record, field, info|
254
+ LOCS[:number_negative] if record.values[field] < 0
255
+ },
256
+ list_select: lambda{|record, field, info|
257
+ value = record.values[field]
258
+ LOCS[:invalid_list_value] unless info[:list].any?{|a|a.first == value}
259
+ },
260
+ decimal: lambda{|record, field, info|
261
+ value = record.values[field]
262
+ LOCS[:invalid_decimal_value] unless value.to_s =~ info[:validations][:decimal][:regexp]
263
+ },
264
+ currency: lambda{|record, field, info|
265
+ value = record.values[field]
266
+ LOCS[:invalid_currency_value] unless value.to_s =~ /^\d+(?:\.\d{,2})?$/
267
+ },
268
+ unique: lambda{|record, field, info|
269
+ with = info[:validations][:unique][:with]
270
+ with_errors = with.map{|w|record.errors[w]}
271
+ if with_errors.compact.empty?
272
+ all_fields = [field] + with
273
+ query = record.model.dataset.where(*all_fields.map{|f|{f => record[f]}})
274
+ query = query.exclude(record.model.primary_keys_hash(record.primary_key_values)) unless record.new?
275
+ unless query.empty?
276
+ msg = LOCS[:required_unique_value]
277
+ with.each{|w| record.errors.add(w, msg)}
278
+ msg
279
+ end
280
+ else
281
+ nil
282
+ end
283
+ }
284
+ )
285
+
286
+ (BeforeSaveProcessors ||= {}).merge!(
287
+ blob_store: lambda{|record, field, info|
288
+ if value = record.values[field] # attachment info
289
+ record.values[info[:name_field]] = value[:name]
290
+ record.values[info[:mime_field]] = value[:mime]
291
+ end
292
+ },
293
+ foreign_blob_store: lambda{|record, field, info|
294
+ if value = record.values[field] # attachment info
295
+ assoc = record.model.association_reflections[info[:assoc_name]]
296
+ blob_model = Object.const_get(assoc[:class_name])
297
+ file_fields = {info[:bytes_field] => :$data, info[:name_field] => :$name_field, info[:mime_field] => :$mime_field}
298
+ upload = info[:store][:upload]
299
+ file_data = {data: Sequel.blob(open("#{upload}/#{value[:rackname]}", "rb"){|f|f.read}), name_field: value[:name], mime_field: value[:mime]}
300
+
301
+ if record.new?
302
+ statement = blob_model.dataset.prepare(:insert, :insert_blob, file_fields)
303
+ id = statement.call(file_data)
304
+ record.values[assoc[:key]] = id
305
+ else
306
+ key = record.model.naked.select(assoc[:key]).where(record.model.primary_keys_hash(record.primary_key_values)).first
307
+ statement = blob_model.dataset.where(blob_model.primary_key => :$id_field).prepare(:update, :update_blob, file_fields)
308
+ statement.call(file_data.merge(id_field: key[assoc[:key]]))
309
+ end
310
+ File.delete("#{upload}/#{value[:rackname]}")
311
+ end
312
+ }
313
+ )
314
+
315
+ (AfterSaveProcessors ||= {}).merge!(
316
+ star_to_many_field: lambda{|record, field, info|
317
+ value = record.values[field]
318
+ if value && value.is_a?(Hash)
319
+ assoc = record.model.association_reflections[info[:assoc_name]]
320
+ other_model = Object.const_get(assoc[:class_name])
321
+ unlinked = value[:unlinked]
322
+ linked = value[:linked]
323
+ parent_key = record.primary_key_values
324
+ case assoc[:type]
325
+ when :one_to_many
326
+ StarToManyUnlinkMetaBase.one_to_many_unlink_db(other_model, assoc, unlinked) if unlinked
327
+ StarToManyLinkMeta.one_to_many_link_db(other_model, assoc, parent_key, linked) if linked
328
+ when :many_to_many
329
+ StarToManyUnlinkMetaBase.many_to_many_unlink_db(other_model, assoc, parent_key, unlinked) if unlinked
330
+ StarToManyLinkMeta.many_to_many_link_db(other_model, assoc, parent_key, linked) if linked
331
+ else unsupported_association
332
+ end
333
+ end
334
+ },
335
+ file_store: lambda{|m, v, info|
336
+ value = m.values[v]
337
+ files = E2Files.db[:files]
338
+ owner = m.primary_key_values.join('|')
339
+ upload = info[:store][:upload]
340
+ files_dir = info[:store][:files]
341
+ value.each do |entry|
342
+ name = entry[:name]
343
+ if (rackname = entry[:rackname])
344
+ unless entry[:deleted]
345
+ file_id = files.insert(name: name, mime: entry[:mime], owner: owner, model: m.model.name, field: v.to_s, uploaded: Sequel.datetime_class.now)
346
+ File.rename("#{upload}/#{rackname}", "#{files_dir}/#{name}_#{file_id}")
347
+ end
348
+ elsif entry[:deleted]
349
+ File.delete("#{files_dir}/#{name}_#{entry[:id]}")
350
+ files.where(id: entry[:id]).delete #, model: m.model.table_name.to_s, field: v.to_s
351
+ end
352
+ end if value # .is_a?(Array)
353
+ },
354
+ blob_store: lambda{|record, field, info|
355
+ if value = record.values[field] # attachment info
356
+ upload = info[:store][:upload]
357
+ id = record.model.primary_keys_hash(record.primary_key_values)
358
+ id_n = Hash[record.model.primary_keys.map{|k| [k, :"$#{k}"]}]
359
+ statement = record.model.dataset.where(id_n).prepare(:update, :update_blob, info[:bytes_field] => :$data)
360
+ statement.call(id.merge(data: Sequel.blob(open("#{upload}/#{value[:rackname]}", "rb"){|f|f.read})))
361
+ # record.model.where(id).update(info[:field] => Sequel.blob(open("#{upload}/#{value[:rackname]}", "rb"){|f|f.read}))
362
+ File.delete("#{upload}/#{value[:rackname]}")
363
+ end
364
+ }
365
+ )
366
+
367
+ (BeforeDestroyProcessors ||= {}).merge!(
368
+ foreign_blob_store: lambda{|record, field, info|
369
+ assoc = record.model.association_reflections[info[:assoc_name]]
370
+ key = record.model.naked.select(assoc[:key]).where(record.model.primary_keys_hash(record.primary_key_values)).first
371
+ if key
372
+ blob_model = Object.const_get(assoc[:class_name])
373
+ blob_model.where(blob_model.primary_key => key[assoc[:key]]).delete
374
+ end
375
+ }
376
+ )
377
+
378
+ (AfterDestroyProcessors ||= {}).merge!(
379
+ file_store: lambda{|m, v, info|
380
+ files = E2Files.db[:files]
381
+ files_dir = info[:store][:files]
382
+ owner = m.primary_key_values.join('|')
383
+ files.select(:id, :name).where(owner: owner, model: m.model.name, field: v.to_s).all.each do |entry|
384
+ File.delete("#{files_dir}/#{entry[:name]}_#{entry[:id]}")
385
+ end
386
+ files.where(owner: owner, model: m.model.name, field: v.to_s).delete
387
+ }
388
+ )
389
+
390
+ end
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+
3
+ module Engine2
4
+ E2DB.create_table :files do
5
+ primary_key :id
6
+ String :name, size: 100, null: false
7
+ String :mime, fixed: true, size: 40, null: false
8
+ String :owner, fixed: true, size: 20, null: false
9
+ String :model, fixed: true, size: 20, null: false
10
+ String :field, fixed: true, size: 20, null: false
11
+ DateTime :uploaded, null: false
12
+ end unless E2DB.table_exists?(:files)
13
+
14
+ class E2Files < Sequel::Model(E2DB[:files])
15
+ extend Engine2::Model
16
+
17
+ type_info do
18
+ # list_select :model, Hash[@model.db.models.keys.map{|m| [m, m]}]
19
+ # list_select :field, {}
20
+ end
21
+
22
+ scheme :default, Schemes::CRUD.merge(create: false, bulk_delete: true) do
23
+ self.* do
24
+ hide_pk
25
+ query select(:name, :mime, :owner, :model, :field, :uploaded)
26
+ sortable
27
+ searchable :name, :owner, :model, :field
28
+ search_live
29
+
30
+ on_change :model do |req, value|
31
+ # action.parent.*.assets[:model].select(:field).where(model: value).all.map{|rec|f = rec.values[:field]; [f, f]}
32
+ # render :field, list: {a: 1, b: 2}.to_a
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end