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.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/Rakefile +138 -0
- data/conf/message.yaml +93 -0
- data/conf/message_pl.yaml +93 -0
- data/engine2.gemspec +34 -0
- data/lib/engine2.rb +34 -0
- data/lib/engine2/action.rb +217 -0
- data/lib/engine2/core.rb +572 -0
- data/lib/engine2/handler.rb +134 -0
- data/lib/engine2/meta.rb +969 -0
- data/lib/engine2/meta/decode_meta.rb +110 -0
- data/lib/engine2/meta/delete_meta.rb +73 -0
- data/lib/engine2/meta/form_meta.rb +144 -0
- data/lib/engine2/meta/infra_meta.rb +292 -0
- data/lib/engine2/meta/link_meta.rb +133 -0
- data/lib/engine2/meta/list_meta.rb +284 -0
- data/lib/engine2/meta/save_meta.rb +63 -0
- data/lib/engine2/meta/view_meta.rb +22 -0
- data/lib/engine2/model.rb +390 -0
- data/lib/engine2/models/Files.rb +38 -0
- data/lib/engine2/models/UserInfo.rb +24 -0
- data/lib/engine2/post_bootstrap.rb +83 -0
- data/lib/engine2/pre_bootstrap.rb +27 -0
- data/lib/engine2/scheme.rb +202 -0
- data/lib/engine2/templates.rb +229 -0
- data/lib/engine2/type_info.rb +342 -0
- data/lib/engine2/version.rb +9 -0
- data/public/assets/javascripts.js +13 -0
- data/public/assets/styles.css +4 -0
- data/public/css/angular-motion.css +1022 -0
- data/public/css/angular-ui-tree.min.css +1 -0
- data/public/css/app.css +196 -0
- data/public/css/bootstrap-additions.css +1560 -0
- data/public/css/bootstrap.min.css +11 -0
- data/public/css/font-awesome.min.css +4 -0
- data/public/favicon.ico +0 -0
- data/public/fonts/FontAwesome.otf +0 -0
- data/public/fonts/fontawesome-webfont.eot +0 -0
- data/public/fonts/fontawesome-webfont.svg +655 -0
- data/public/fonts/fontawesome-webfont.ttf +0 -0
- data/public/fonts/fontawesome-webfont.woff +0 -0
- data/public/fonts/fontawesome-webfont.woff2 +0 -0
- data/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/public/fonts/glyphicons-halflings-regular.svg +288 -0
- data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/public/images/file.png +0 -0
- data/public/images/folder-closed.png +0 -0
- data/public/images/folder.png +0 -0
- data/public/images/node-closed-2.png +0 -0
- data/public/images/node-closed-light.png +0 -0
- data/public/images/node-closed.png +0 -0
- data/public/images/node-opened-2.png +0 -0
- data/public/images/node-opened-light.png +0 -0
- data/public/images/node-opened.png +0 -0
- data/public/img/ajax-loader-dark.gif +0 -0
- data/public/img/ajax-loader-light.gif +0 -0
- data/public/img/ajax-loader.gif +0 -0
- data/public/js/angular-animate.js +4115 -0
- data/public/js/angular-cookies.js +322 -0
- data/public/js/angular-local-storage.js +455 -0
- data/public/js/angular-route.js +1022 -0
- data/public/js/angular-sanitize.js +717 -0
- data/public/js/angular-strap.js +4339 -0
- data/public/js/angular-strap.tpl.js +43 -0
- data/public/js/angular-ui-tree.js +1569 -0
- data/public/js/angular.js +30714 -0
- data/public/js/i18n/angular-locale_pl.js +115 -0
- data/public/js/lodash.custom.min.js +97 -0
- data/public/js/ng-file-upload-shim.min.js +2 -0
- data/public/js/ng-file-upload.min.js +3 -0
- data/views/app.coffee +3 -0
- data/views/engine2.coffee +557 -0
- data/views/engine2actions.coffee +849 -0
- data/views/engine2templates.coffee +0 -0
- data/views/fields/blob.slim +22 -0
- data/views/fields/bs_select.slim +10 -0
- data/views/fields/bsselect_picker.slim +18 -0
- data/views/fields/bsselect_picker_opt.slim +22 -0
- data/views/fields/checkbox.slim +11 -0
- data/views/fields/checkbox_buttons.slim +6 -0
- data/views/fields/checkbox_buttons_opt.slim +8 -0
- data/views/fields/currency.slim +10 -0
- data/views/fields/date.slim +21 -0
- data/views/fields/date_range.slim +44 -0
- data/views/fields/date_time.slim +42 -0
- data/views/fields/datetime.slim +42 -0
- data/views/fields/decimal.slim +11 -0
- data/views/fields/decimal_date.slim +22 -0
- data/views/fields/decimal_time.slim +26 -0
- data/views/fields/email.slim +13 -0
- data/views/fields/file_store.slim +61 -0
- data/views/fields/input_text.slim +14 -0
- data/views/fields/integer.slim +11 -0
- data/views/fields/list_bsselect.slim +18 -0
- data/views/fields/list_bsselect_opt.slim +21 -0
- data/views/fields/list_buttons.slim +3 -0
- data/views/fields/list_buttons_opt.slim +5 -0
- data/views/fields/list_select.slim +11 -0
- data/views/fields/list_select_opt.slim +15 -0
- data/views/fields/password.slim +14 -0
- data/views/fields/radio_checkbox.slim +10 -0
- data/views/fields/scaffold.slim +2 -0
- data/views/fields/scaffold_picker.slim +20 -0
- data/views/fields/select_picker.slim +12 -0
- data/views/fields/select_picker_opt.slim +16 -0
- data/views/fields/text_area.slim +10 -0
- data/views/fields/time.slim +22 -0
- data/views/fields/typeahead_picker.slim +25 -0
- data/views/index.slim +44 -0
- data/views/infra/index.slim +5 -0
- data/views/infra/inspect.slim +81 -0
- data/views/modals/close_m.slim +15 -0
- data/views/modals/confirm_m.slim +19 -0
- data/views/modals/empty_m.slim +12 -0
- data/views/modals/menu_m.slim +13 -0
- data/views/modals/yes_no_m.slim +19 -0
- data/views/panels/menu_m.slim +9 -0
- data/views/scaffold/confirm.slim +3 -0
- data/views/scaffold/fields.slim +10 -0
- data/views/scaffold/form.slim +11 -0
- data/views/scaffold/list.slim +42 -0
- data/views/scaffold/message.slim +3 -0
- data/views/scaffold/search.slim +20 -0
- data/views/scaffold/view.slim +18 -0
- data/views/search_fields/bsmselect_picker.slim +25 -0
- data/views/search_fields/bsselect_picker.slim +24 -0
- data/views/search_fields/checkbox.slim +11 -0
- data/views/search_fields/checkbox2.slim +14 -0
- data/views/search_fields/checkbox_buttons.slim +10 -0
- data/views/search_fields/date_range.slim +46 -0
- data/views/search_fields/decimal_date_range.slim +47 -0
- data/views/search_fields/input_text.slim +18 -0
- data/views/search_fields/integer.slim +18 -0
- data/views/search_fields/integer_range.slim +27 -0
- data/views/search_fields/list_bsmselect.slim +24 -0
- data/views/search_fields/list_bsselect.slim +22 -0
- data/views/search_fields/list_buttons.slim +8 -0
- data/views/search_fields/list_select.slim +17 -0
- data/views/search_fields/scaffold_picker.slim +19 -0
- data/views/search_fields/select_picker.slim +17 -0
- data/views/search_fields/typeahead_picker.slim +25 -0
- metadata +327 -0
data/lib/engine2/core.rb
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
#coding: utf-8
|
|
2
|
+
|
|
3
|
+
module PrettyJSON
|
|
4
|
+
def to_json_pretty
|
|
5
|
+
JSON.pretty_generate(self)
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class BigDecimal
|
|
10
|
+
def to_json(*)
|
|
11
|
+
# super
|
|
12
|
+
to_s('f')
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Object
|
|
17
|
+
def instance_variables_hash
|
|
18
|
+
instance_variables.inject({}) do |h, i|
|
|
19
|
+
h[i] = instance_variable_get(i)
|
|
20
|
+
h
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Proc
|
|
26
|
+
def to_json(*)
|
|
27
|
+
loc = source_location
|
|
28
|
+
"\"#<Proc:#{loc.first[/\w+.rb/]}:#{loc.last}>\""
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Hash
|
|
33
|
+
include PrettyJSON
|
|
34
|
+
|
|
35
|
+
def rmerge!(other_hash)
|
|
36
|
+
merge!(other_hash) do |key, oldval, newval|
|
|
37
|
+
oldval.class == self.class ? oldval.rmerge!(newval) : newval
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def rmerge(other_hash)
|
|
42
|
+
r = {}
|
|
43
|
+
merge(other_hash) do |key, oldval, newval|
|
|
44
|
+
r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def rmerge2(other_hash)
|
|
49
|
+
r = {}
|
|
50
|
+
merge(other_hash) do |key, oldval, newval|
|
|
51
|
+
r[key] = oldval.class == self.class ? oldval.rmerge2(newval) : (oldval == nil ? newval : oldval)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def rmerge2!(other_hash)
|
|
56
|
+
r = {}
|
|
57
|
+
merge!(other_hash) do |key, oldval, newval|
|
|
58
|
+
r[key] = oldval.class == self.class ? oldval.rmerge2!(newval) : (oldval == nil ? newval : oldval)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def rdup
|
|
63
|
+
duplicate = self.dup
|
|
64
|
+
duplicate.each_pair do |k,v|
|
|
65
|
+
tv = duplicate[k]
|
|
66
|
+
duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.rdup : v
|
|
67
|
+
end
|
|
68
|
+
duplicate
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def path *a
|
|
72
|
+
h = self
|
|
73
|
+
i = 0
|
|
74
|
+
while h && i != a.length
|
|
75
|
+
h = h[a[i]]
|
|
76
|
+
i += 1
|
|
77
|
+
end
|
|
78
|
+
h
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def path! *a, v
|
|
82
|
+
h = self
|
|
83
|
+
i = 0
|
|
84
|
+
while i < a.length - 1
|
|
85
|
+
h = h[a[i]] ||= {}
|
|
86
|
+
i += 1
|
|
87
|
+
end
|
|
88
|
+
h[a[i]] = v
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class String
|
|
94
|
+
def limit_length num
|
|
95
|
+
s = self.strip
|
|
96
|
+
if s.length > num
|
|
97
|
+
s[0..num] + "..."
|
|
98
|
+
else
|
|
99
|
+
s
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class Symbol
|
|
105
|
+
def icon
|
|
106
|
+
"<span class='glyphicon glyphicon-#{self}'></span>"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def aicon
|
|
110
|
+
"<i class='fa fa-#{self}'></i>"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class << Sequel
|
|
115
|
+
attr_accessor :alias_tables_in_joins
|
|
116
|
+
|
|
117
|
+
def split_keys id
|
|
118
|
+
id.split('|')
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class Sequel::Database
|
|
123
|
+
attr_accessor :models, :default_schema
|
|
124
|
+
|
|
125
|
+
def cache_file
|
|
126
|
+
"#{APP_LOCATION}/#{opts[:orig_opts][:name]}.dump"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def load_schema_cache_from_file
|
|
130
|
+
self.models = {}
|
|
131
|
+
load_schema_cache? cache_file if adapter_scheme
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def dump_schema_cache_to_file
|
|
135
|
+
dump_schema_cache? cache_file if adapter_scheme
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
Sequel.quote_identifiers = false
|
|
140
|
+
Sequel.extension :core_extensions
|
|
141
|
+
Sequel::Inflections.clear
|
|
142
|
+
Sequel.alias_tables_in_joins = true
|
|
143
|
+
# Sequel::Model.plugin :json_serializer, :naked => true
|
|
144
|
+
# Sequel::Model.plugin :timestamps
|
|
145
|
+
# Sequel::Model.plugin :validation_class_methods
|
|
146
|
+
# Sequel::Model.raise_on_typecast_failure = false
|
|
147
|
+
# Sequel::Model.raise_on_save_failure = false
|
|
148
|
+
# Sequel::Model.unrestrict_primary_key
|
|
149
|
+
# Sequel::Model.plugin :validation_helpers
|
|
150
|
+
Sequel::Database::extension :schema_caching
|
|
151
|
+
|
|
152
|
+
module E2Model
|
|
153
|
+
module InstanceMethods
|
|
154
|
+
attr_accessor :skip_save_refresh, :validate_fields
|
|
155
|
+
|
|
156
|
+
def has_primary_key?
|
|
157
|
+
pk = self.pk
|
|
158
|
+
pk.is_a?(Array) ? !pk.all?{|k|k.nil?} : !pk.nil?
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def primary_key_values
|
|
162
|
+
model.primary_keys.map{|k|@values[k]}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def _save_refresh
|
|
166
|
+
super unless skip_save_refresh
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def validation
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def before_save
|
|
173
|
+
super
|
|
174
|
+
model.before_save_processors.each_pair do |name, proc|
|
|
175
|
+
proc.(self, name, model.type_info.fetch(name))
|
|
176
|
+
end if model.before_save_processors
|
|
177
|
+
|
|
178
|
+
unless model.dummies.empty?
|
|
179
|
+
dummies = {}
|
|
180
|
+
model.dummies.each do |d|
|
|
181
|
+
dummies[d] = values.delete(d)
|
|
182
|
+
end
|
|
183
|
+
@dummy_fields = dummies
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
unless self.pk
|
|
187
|
+
sequence = model.type_info[model.primary_key][:sequence]
|
|
188
|
+
self[model.primary_key] = sequence.lit if sequence
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def after_save
|
|
193
|
+
unless model.dummies.empty?
|
|
194
|
+
@values.merge!(@dummy_fields)
|
|
195
|
+
@dummy_fields = nil
|
|
196
|
+
end
|
|
197
|
+
model.after_save_processors.each_pair do |name, proc|
|
|
198
|
+
proc.(self, name, model.type_info.fetch(name))
|
|
199
|
+
end if model.after_save_processors
|
|
200
|
+
|
|
201
|
+
super
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def before_destroy
|
|
205
|
+
model.before_destroy_processors.each_pair do |name, proc|
|
|
206
|
+
proc.(self, name, model.type_info.fetch(name))
|
|
207
|
+
end if model.before_destroy_processors
|
|
208
|
+
super
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def after_destroy
|
|
212
|
+
model.after_destroy_processors.each_pair do |name, proc|
|
|
213
|
+
proc.(self, name, model.type_info.fetch(name))
|
|
214
|
+
end if model.after_destroy_processors
|
|
215
|
+
super
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def validate
|
|
219
|
+
super
|
|
220
|
+
auto_validate
|
|
221
|
+
validation
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def auto_validate
|
|
225
|
+
type_info = model.type_info
|
|
226
|
+
@validate_fields.each do |name| # || type_info.keys
|
|
227
|
+
info = type_info[name]
|
|
228
|
+
next if info[:primary_key] && !model.natural_key
|
|
229
|
+
|
|
230
|
+
value = values[name].to_s
|
|
231
|
+
value.strip! unless info[:dont_strip]
|
|
232
|
+
if value.empty?
|
|
233
|
+
if req = info[:required]
|
|
234
|
+
errors.add(name, req[:message]) if !req[:if] || req[:if].(self)
|
|
235
|
+
end
|
|
236
|
+
else
|
|
237
|
+
info[:validations].each_pair do |validation, args|
|
|
238
|
+
validation_proc = Engine2::Validations[validation] || args[:lambda] # swap ?
|
|
239
|
+
raise "Validation not found for field '#{name}' of type #{validation}" unless validation_proc
|
|
240
|
+
if result = validation_proc.(self, name, info)
|
|
241
|
+
errors.add(name, result)
|
|
242
|
+
break
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# if errors.empty? && model.natural_key && new?
|
|
249
|
+
# unless model.dataset.where(model.primary_keys_hash(primary_key_values)).empty? # optimize the keys part
|
|
250
|
+
# model.primary_keys.each{|pk| errors.add(pk, "must be unique")}
|
|
251
|
+
# end
|
|
252
|
+
# end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
module ClassMethods
|
|
257
|
+
attr_reader :natural_key
|
|
258
|
+
|
|
259
|
+
def set_natural_key key
|
|
260
|
+
set_primary_key key
|
|
261
|
+
@natural_key = true
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def primary_keys
|
|
265
|
+
# cache it ?
|
|
266
|
+
key = primary_key
|
|
267
|
+
key.is_a?(Array) ? key : [key]
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def primary_keys_qualified
|
|
271
|
+
# cache it ?
|
|
272
|
+
primary_keys.map{|k|k.qualify(table_name)}
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def primary_keys_hash id
|
|
276
|
+
Hash[primary_keys.zip(id)]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def primary_keys_hash_qualified id
|
|
280
|
+
Hash[primary_keys_qualified.zip(id)]
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
module DatasetMethods
|
|
285
|
+
|
|
286
|
+
def ensure_primary_key
|
|
287
|
+
pk = @model.primary_keys
|
|
288
|
+
|
|
289
|
+
if opts_select = @opts[:select]
|
|
290
|
+
sel_pk = []
|
|
291
|
+
opts_select.each do |sel|
|
|
292
|
+
name = case sel
|
|
293
|
+
when Symbol
|
|
294
|
+
sel.to_s =~ /\w+__(\w+)/ ? $1.to_sym : sel
|
|
295
|
+
when Sequel::SQL::QualifiedIdentifier
|
|
296
|
+
sel.column
|
|
297
|
+
when Sequel::SQL::AliasedExpression
|
|
298
|
+
sel
|
|
299
|
+
# nil #sel.aliaz # ?
|
|
300
|
+
# sel.expression
|
|
301
|
+
end
|
|
302
|
+
sel_pk << name if name && pk.include?(name)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
if pk.length == sel_pk.length
|
|
306
|
+
self
|
|
307
|
+
else
|
|
308
|
+
sels = (pk - sel_pk).map{|k| k.qualify(@model.table_name)}
|
|
309
|
+
select_more(*sels)
|
|
310
|
+
end
|
|
311
|
+
else
|
|
312
|
+
select(*pk.map{|k| k.qualify(@model.table_name)})
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def setup! fields
|
|
318
|
+
joins = {}
|
|
319
|
+
type_info = model.type_info
|
|
320
|
+
model_table_name = model.table_name
|
|
321
|
+
|
|
322
|
+
@opts[:select].map! do |sel|
|
|
323
|
+
extract_select sel do |table, name, aliaz|
|
|
324
|
+
if table
|
|
325
|
+
if table == model_table_name
|
|
326
|
+
m = model
|
|
327
|
+
else
|
|
328
|
+
a = model.many_to_one_associations[table] # || model.one_to_one_associations[table]
|
|
329
|
+
raise Engine2::E2Error.new("Association #{table} not found for model #{model}") unless a
|
|
330
|
+
m = Object.const_get(a[:class_name])
|
|
331
|
+
end
|
|
332
|
+
# raise Engine2::E2Error.new("Model not found for table #{table} in model #{model}") unless m
|
|
333
|
+
info = m.type_info
|
|
334
|
+
else
|
|
335
|
+
info = type_info
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
f_info = info[name]
|
|
339
|
+
raise Engine2::E2Error.new("Column #{name} not found for table #{table || model_table_name}") unless f_info
|
|
340
|
+
|
|
341
|
+
table ||= model_table_name
|
|
342
|
+
|
|
343
|
+
if table == model_table_name
|
|
344
|
+
fields << name
|
|
345
|
+
else
|
|
346
|
+
fields << :"#{table}__#{name}"
|
|
347
|
+
assoc = model.many_to_one_associations[table]
|
|
348
|
+
unless assoc
|
|
349
|
+
# fail
|
|
350
|
+
end
|
|
351
|
+
joins[table] = assoc
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
if f_info[:dummy]
|
|
355
|
+
nil
|
|
356
|
+
# elsif f_info[:type] == :blob_store
|
|
357
|
+
# # (~{name => nil}).as :name
|
|
358
|
+
# # Sequel.char_length(name).as name
|
|
359
|
+
# nil
|
|
360
|
+
else
|
|
361
|
+
if table != model_table_name
|
|
362
|
+
if Sequel.alias_tables_in_joins
|
|
363
|
+
name.qualify(table).as(:"#{table}__#{name}")
|
|
364
|
+
else
|
|
365
|
+
name.qualify(table)
|
|
366
|
+
end
|
|
367
|
+
else
|
|
368
|
+
name.qualify(table)
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
@opts[:select].compact!
|
|
375
|
+
|
|
376
|
+
joins.reduce(self) do |joined, (table, assoc)|
|
|
377
|
+
assoc = model.many_to_one_associations[table] # || model.one_to_one_associations[table]
|
|
378
|
+
m = Object.const_get(assoc[:class_name])
|
|
379
|
+
keys = assoc[:qualified_key]
|
|
380
|
+
keys = [keys] unless keys.is_a?(Array)
|
|
381
|
+
joined.left_join(table, m.primary_keys.zip(keys))
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def extract_select sel, al = nil, &blk
|
|
386
|
+
case sel
|
|
387
|
+
when Symbol
|
|
388
|
+
if sel.to_s =~ /^(\w+)__(\w+?)(?:___(\w+))?$/
|
|
389
|
+
yield $1.to_sym, $2.to_sym, $3 ? $3.to_sym : nil
|
|
390
|
+
else
|
|
391
|
+
yield nil, sel, al
|
|
392
|
+
end
|
|
393
|
+
when Sequel::SQL::QualifiedIdentifier
|
|
394
|
+
yield sel.table, sel.column, al
|
|
395
|
+
when Sequel::SQL::AliasedExpression
|
|
396
|
+
sel
|
|
397
|
+
# extract_select sel.expression, sel.aliaz, &blk
|
|
398
|
+
# expr = sel.expression
|
|
399
|
+
# yield expr.table, expr.column
|
|
400
|
+
else
|
|
401
|
+
raise Engine2::E2Error.new("Unknown selection #{sel}")
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def get_opts
|
|
406
|
+
@opts
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def with_proc &blk
|
|
410
|
+
ds = clone
|
|
411
|
+
ds.row_proc = blk
|
|
412
|
+
ds
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
Sequel::Model.plugin E2Model
|
|
418
|
+
|
|
419
|
+
module Sequel
|
|
420
|
+
class DestroyFailed < Error
|
|
421
|
+
attr_reader :error
|
|
422
|
+
|
|
423
|
+
def initialize error
|
|
424
|
+
@error = error
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
module Engine2
|
|
431
|
+
LOCS ||= Hash.new{|h, k| ":#{k}:"}
|
|
432
|
+
PATH ||= File.expand_path('../..', File.dirname(__FILE__))
|
|
433
|
+
|
|
434
|
+
class << self
|
|
435
|
+
attr_accessor :core_loading
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
self.core_loading = true
|
|
439
|
+
|
|
440
|
+
def self.database name
|
|
441
|
+
Object.const_set(name, yield) unless Object.const_defined?(name)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def self.connect *args
|
|
445
|
+
db = Sequel.connect *args
|
|
446
|
+
db.models = {}
|
|
447
|
+
db
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
e2_db_file = (defined? JRUBY_VERSION) ? "jdbc:sqlite:#{APP_LOCATION}/engine2.db" : "sqlite://#{APP_LOCATION}/engine2.db"
|
|
451
|
+
E2DB ||= connect e2_db_file, loggers: [Logger.new($stdout)], convert_types: false, name: :engine2
|
|
452
|
+
DUMMYDB ||= Sequel::Database.new uri: 'dummy'
|
|
453
|
+
def DUMMYDB.synchronize *args;end
|
|
454
|
+
|
|
455
|
+
def self.boot &blk
|
|
456
|
+
@boot_blk = blk
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def self.bootstrap app = APP_LOCATION
|
|
460
|
+
require 'engine2/pre_bootstrap'
|
|
461
|
+
t = Time.now
|
|
462
|
+
Action.count = 0
|
|
463
|
+
SCHEMES.clear
|
|
464
|
+
|
|
465
|
+
load "#{app}/boot.rb"
|
|
466
|
+
|
|
467
|
+
Sequel::DATABASES.each &:load_schema_cache_from_file
|
|
468
|
+
load 'engine2/models/Files.rb'
|
|
469
|
+
load 'engine2/models/UserInfo.rb'
|
|
470
|
+
Dir["#{app}/models/*"].each{|m| load m}
|
|
471
|
+
puts "MODELS, Time: #{Time.now - t}"
|
|
472
|
+
Sequel::DATABASES.each &:dump_schema_cache_to_file
|
|
473
|
+
|
|
474
|
+
SCHEMES.merge!
|
|
475
|
+
Engine2.send(:remove_const, :ROOT) if defined? ROOT
|
|
476
|
+
Engine2.const_set(:ROOT, Action.new(nil, :api, DummyMeta, {}))
|
|
477
|
+
|
|
478
|
+
@boot_blk.(ROOT)
|
|
479
|
+
ROOT.setup_action_tree
|
|
480
|
+
puts "BOOTSTRAP #{app}, Time: #{Time.new - t}"
|
|
481
|
+
self.core_loading = false
|
|
482
|
+
require 'engine2/post_bootstrap'
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
class E2Error < RuntimeError
|
|
486
|
+
def initialize msg
|
|
487
|
+
super
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
class MenuBuilder
|
|
492
|
+
attr_accessor :name
|
|
493
|
+
attr_reader :entries
|
|
494
|
+
|
|
495
|
+
def initialize name, properties = {}
|
|
496
|
+
@name = name
|
|
497
|
+
@properties = properties
|
|
498
|
+
@entries = []
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def properties props = nil
|
|
502
|
+
props ? @properties.merge!(props) : @properties
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def option name, properties = {}, index = @entries.size, &blk
|
|
506
|
+
if blk
|
|
507
|
+
entries = MenuBuilder.new(name, properties)
|
|
508
|
+
entries.instance_eval(&blk)
|
|
509
|
+
@entries.insert index, entries
|
|
510
|
+
else
|
|
511
|
+
@entries.insert index, {name: name}.merge(properties)
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def option_before iname, name, properties = {}, &blk
|
|
516
|
+
option name, properties, option_index(iname), &blk
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def option_after iname, name, properties = {}, &blk
|
|
520
|
+
option name, properties, option_index(iname) + 1, &blk
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def option_at index, name, properties = {}, &blk
|
|
524
|
+
option name, properties, index, &blk
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def option_index iname
|
|
528
|
+
index = @entries.index{|e| (e.is_a?(MenuBuilder) ? e.name : e[:name]) == iname}
|
|
529
|
+
raise E2Error.new("No menu option #{iname} found") unless index
|
|
530
|
+
index
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def modify_option name, properties
|
|
534
|
+
index = option_index(name)
|
|
535
|
+
entry = @entries[index]
|
|
536
|
+
props = entry.is_a?(MenuBuilder) ? entry.properties : entry
|
|
537
|
+
props.merge!(properties)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def divider
|
|
541
|
+
@entries << {divider: true}
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def to_a
|
|
545
|
+
@entries.map do |m|
|
|
546
|
+
if m.is_a? MenuBuilder
|
|
547
|
+
h = {entries: m.to_a}.merge(m.properties)
|
|
548
|
+
h[:loc] ||= LOCS[m.name]
|
|
549
|
+
{menu: h}
|
|
550
|
+
else
|
|
551
|
+
m[:loc] ? m : m.merge(loc: LOCS[m[:name]])
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def each &blk
|
|
557
|
+
@entries.each do |m|
|
|
558
|
+
if m.is_a? MenuBuilder
|
|
559
|
+
m.each &blk
|
|
560
|
+
else
|
|
561
|
+
yield m
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
class ActionMenuBuilder < MenuBuilder
|
|
568
|
+
def option name, properties = {}, index = @entries.size, &blk
|
|
569
|
+
super
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
end
|