engine2 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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