engine2 1.0.4 → 1.0.9

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 (131) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +0 -0
  3. data/Rakefile +4 -4
  4. data/app/{engine2actions.coffee → actions.coffee} +341 -215
  5. data/app/app.coffee +0 -0
  6. data/app/app.css +17 -0
  7. data/app/engine2.coffee +158 -208
  8. data/app/modal.coffee +138 -0
  9. data/bower.json +4 -2
  10. data/conf/message.yaml +5 -0
  11. data/conf/message_pl.yaml +7 -2
  12. data/config.coffee +24 -12
  13. data/engine2.gemspec +8 -8
  14. data/lib/engine2.rb +12 -10
  15. data/lib/engine2/action.rb +1338 -133
  16. data/lib/engine2/action/array.rb +189 -0
  17. data/lib/engine2/{meta/decode_meta.rb → action/decode.rb} +52 -21
  18. data/lib/engine2/action/delete.rb +64 -0
  19. data/lib/engine2/action/form.rb +16 -0
  20. data/lib/engine2/{meta/infra_meta.rb → action/infra.rb} +123 -89
  21. data/lib/engine2/action/link.rb +117 -0
  22. data/lib/engine2/action/list.rb +333 -0
  23. data/lib/engine2/action/save.rb +28 -0
  24. data/lib/engine2/action/view.rb +8 -0
  25. data/lib/engine2/action_node.rb +221 -0
  26. data/lib/engine2/core.rb +175 -87
  27. data/lib/engine2/handler.rb +14 -13
  28. data/lib/engine2/model.rb +85 -43
  29. data/lib/engine2/models/Files.rb +4 -1
  30. data/lib/engine2/models/UserInfo.rb +6 -3
  31. data/lib/engine2/post_bootstrap.rb +4 -3
  32. data/lib/engine2/pre_bootstrap.rb +10 -6
  33. data/lib/engine2/scheme.rb +107 -65
  34. data/lib/engine2/templates.rb +41 -6
  35. data/lib/engine2/type_info.rb +51 -23
  36. data/lib/engine2/version.rb +2 -1
  37. data/package.json +22 -16
  38. data/public/favicon.ico +0 -0
  39. data/public/img/ajax-loader-dark.gif +0 -0
  40. data/public/img/ajax-loader.gif +0 -0
  41. data/views/fields/blob.slim +1 -1
  42. data/views/fields/bs_select.slim +2 -2
  43. data/views/fields/bsselect_picker.slim +4 -4
  44. data/views/fields/bsselect_picker_opt.slim +5 -5
  45. data/views/fields/checkbox.slim +4 -4
  46. data/views/fields/checkbox_button.slim +6 -0
  47. data/views/fields/checkbox_buttons.slim +3 -3
  48. data/views/fields/checkbox_buttons_opt.slim +3 -3
  49. data/views/fields/currency.slim +2 -2
  50. data/views/fields/date.slim +4 -4
  51. data/views/fields/date_range.slim +9 -9
  52. data/views/fields/date_time.slim +9 -9
  53. data/views/fields/datetime.slim +8 -8
  54. data/views/fields/decimal.slim +1 -1
  55. data/views/fields/decimal_date.slim +3 -3
  56. data/views/fields/decimal_time.slim +3 -3
  57. data/views/fields/email.slim +3 -3
  58. data/views/fields/file_store.slim +11 -11
  59. data/views/fields/input_text.slim +4 -4
  60. data/views/fields/integer.slim +1 -1
  61. data/views/fields/list_bsmselect.slim +20 -0
  62. data/views/fields/list_bsselect.slim +5 -5
  63. data/views/fields/list_bsselect_opt.slim +6 -6
  64. data/views/fields/list_buttons.slim +1 -1
  65. data/views/fields/list_buttons_opt.slim +2 -2
  66. data/views/fields/list_mbuttons.slim +9 -0
  67. data/views/fields/list_mbuttons_opt.slim +11 -0
  68. data/views/fields/list_mselect.slim +12 -0
  69. data/views/fields/list_select.slim +4 -4
  70. data/views/fields/list_select_opt.slim +5 -5
  71. data/views/fields/password.slim +4 -4
  72. data/views/fields/radio_checkbox.slim +3 -3
  73. data/views/fields/scaffold.slim +2 -2
  74. data/views/fields/scaffold_picker.slim +5 -5
  75. data/views/fields/select_picker.slim +3 -3
  76. data/views/fields/select_picker_opt.slim +4 -4
  77. data/views/fields/text_area.slim +4 -3
  78. data/views/fields/time.slim +5 -4
  79. data/views/fields/typeahead_picker.slim +12 -9
  80. data/views/index.slim +3 -3
  81. data/views/infra/index.slim +0 -0
  82. data/views/infra/inspect.slim +41 -10
  83. data/views/modals/close_m.slim +0 -0
  84. data/views/modals/confirm_m.slim +0 -0
  85. data/views/modals/empty_m.slim +0 -0
  86. data/views/modals/menu_m.slim +1 -1
  87. data/views/modals/yes_no_m.slim +0 -0
  88. data/views/panels/menu_m.slim +1 -1
  89. data/views/scaffold/confirm.slim +0 -0
  90. data/views/scaffold/fields.slim +6 -4
  91. data/views/scaffold/form.slim +1 -1
  92. data/views/scaffold/form_collapse.slim +4 -3
  93. data/views/scaffold/form_tabs.slim +3 -2
  94. data/views/scaffold/list.slim +0 -0
  95. data/views/scaffold/message.slim +0 -0
  96. data/views/scaffold/search.slim +4 -4
  97. data/views/scaffold/search_collapse.slim +8 -7
  98. data/views/scaffold/search_tabs.slim +6 -5
  99. data/views/scaffold/view.slim +2 -2
  100. data/views/scaffold/view_collapse.slim +5 -4
  101. data/views/scaffold/view_tabs.slim +4 -3
  102. data/views/search_fields/bsmselect_picker.slim +4 -4
  103. data/views/search_fields/bsselect_picker.slim +4 -4
  104. data/views/search_fields/checkbox.slim +3 -3
  105. data/views/search_fields/checkbox2.slim +5 -5
  106. data/views/search_fields/checkbox_buttons.slim +3 -3
  107. data/views/search_fields/date.slim +20 -0
  108. data/views/search_fields/date_range.slim +8 -8
  109. data/views/search_fields/decimal_date_range.slim +5 -5
  110. data/views/search_fields/input_text.slim +2 -2
  111. data/views/search_fields/integer.slim +1 -1
  112. data/views/search_fields/integer_range.slim +2 -2
  113. data/views/search_fields/list_bsmselect.slim +4 -4
  114. data/views/search_fields/list_bsselect.slim +4 -4
  115. data/views/search_fields/list_buttons.slim +2 -2
  116. data/views/search_fields/list_mbuttons.slim +12 -0
  117. data/views/search_fields/list_select.slim +3 -3
  118. data/views/search_fields/scaffold_picker.slim +2 -2
  119. data/views/search_fields/select_picker.slim +3 -3
  120. data/views/search_fields/typeahead_picker.slim +8 -7
  121. metadata +53 -48
  122. data/lib/engine2/meta.rb +0 -1216
  123. data/lib/engine2/meta/array_meta.rb +0 -82
  124. data/lib/engine2/meta/delete_meta.rb +0 -60
  125. data/lib/engine2/meta/form_meta.rb +0 -15
  126. data/lib/engine2/meta/link_meta.rb +0 -134
  127. data/lib/engine2/meta/list_meta.rb +0 -281
  128. data/lib/engine2/meta/save_meta.rb +0 -50
  129. data/lib/engine2/meta/view_meta.rb +0 -7
  130. data/public/__sinatra__/404.png +0 -0
  131. data/public/__sinatra__/500.png +0 -0
@@ -0,0 +1,117 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Engine2
5
+ class StarToManyLinkAction < Action
6
+ include ActionModelSupport
7
+ http_method :post
8
+ action_type :star_to_many_link
9
+
10
+ def pre_run
11
+ super
12
+ execute "action.errors || [action.parent().invoke(), action.panel_close()]"
13
+ end
14
+
15
+ def invoke handler
16
+ json = handler.post_to_json
17
+ parent = json[:parent_id]
18
+ ids = json[:ids]
19
+ handler.permit parent && ids
20
+ invoke_link_db handler, split_keys(parent), ids
21
+ end
22
+
23
+ def invoke_link_db handler, parent, ids
24
+ model = assets[:model]
25
+ assoc = assets[:assoc]
26
+
27
+ case assoc[:type]
28
+ when :one_to_many
29
+ model.db.transaction do
30
+ pk = Hash[assoc[:keys].zip(parent)]
31
+ ids.each do |id|
32
+ model.where(model.primary_keys_hash(split_keys(id))).update(pk)
33
+ end
34
+ end
35
+ when :many_to_many
36
+ p_pk = Hash[assoc[:left_keys].zip(parent)]
37
+ values = ids.map do |id|
38
+ p_pk.merge Hash[assoc[:right_keys].zip(split_keys(id))]
39
+ end
40
+ model.db[assoc[:join_table]].multi_insert values
41
+ else unsupported_association
42
+ end
43
+ {}
44
+ end
45
+ end
46
+
47
+ class StarToManyUnlinkActionBase < Action
48
+ include ActionModelSupport
49
+ http_method :delete
50
+
51
+ def pre_run
52
+ super
53
+ execute "[action.parent().invoke(), action.panel_close()]"
54
+ end
55
+
56
+ def invoke_unlink_db handler, parent, ids
57
+ model = assets[:model]
58
+ assoc = assets[:assoc]
59
+
60
+ case assoc[:type]
61
+ when :one_to_many
62
+ keys = assoc[:keys]
63
+ if keys.all?{|k|model.db_schema[k][:allow_null] == true}
64
+ model.db.transaction do
65
+ ids.each do |id|
66
+ model.where(model.primary_keys_hash(split_keys(id))).update(Hash[keys.zip([nil])])
67
+ end
68
+ end
69
+ else
70
+ handler.halt_method_not_allowed LOCS[:"non_nullable"]
71
+ end
72
+ when :many_to_many
73
+ model.db.transaction do
74
+ p_pk = Hash[assoc[:left_keys].zip(parent)]
75
+ ds = model.db[assoc[:join_table]]
76
+ ids.each do |id|
77
+ ds.where(p_pk & Hash[assoc[:right_keys].zip(split_keys(id))]).delete
78
+ end
79
+ end
80
+ else unsupported_association
81
+ end
82
+ {}
83
+ end
84
+ end
85
+
86
+ class StarToManyUnlinkAction < StarToManyUnlinkActionBase
87
+ action_type :star_to_many_unlink
88
+
89
+ def pre_run
90
+ super
91
+ node.parent.parent.*.menu(:item_menu).option :confirm_unlink, icon: "minus", show: "action.selected_size() == 0", button_loc: false
92
+ end
93
+
94
+ def invoke handler
95
+ handler.permit id = handler.params[:id]
96
+ handler.permit parent = handler.params[:parent_id]
97
+ invoke_unlink_db handler, split_keys(parent), [id]
98
+ end
99
+ end
100
+
101
+ class StarToManyBulkUnlinkAction < StarToManyUnlinkActionBase
102
+ action_type :star_to_many_bulk_unlink
103
+
104
+ def pre_run
105
+ super
106
+ node.parent.parent.*.select_toggle_menu
107
+ node.parent.parent.*.menu(:menu).option_after :default_order, :confirm_bulk_unlink, icon: "minus", show: "action.selected_size() > 0", button_loc: false
108
+ end
109
+
110
+ def invoke handler
111
+ ids = handler.param_to_json(:ids)
112
+ handler.permit parent = handler.params[:parent_id]
113
+ handler.permit ids.is_a?(Array)
114
+ invoke_unlink_db handler, split_keys(parent), ids
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,333 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Engine2
5
+ class ListAction < Action
6
+ action_type :list
7
+ include ActionListSupport, ActionQuerySupport
8
+
9
+ (DefaultFilters ||= {}).merge!(
10
+ string: lambda{|query, name, value, type_info, hash|
11
+ case type_info[:type]
12
+ when :list_select
13
+ raise E2Error.new("Filter unimplemented for string multi list_select, field: '#{name.to_sym}'") if type_info[:multiselect] # todo
14
+ query.where(name => value)
15
+ when :many_to_one
16
+ query.where(name => value)
17
+ else
18
+ query.where(name.like("%#{value}%"))
19
+ end
20
+ },
21
+ date: lambda{|query, name, value, type_info, hash|
22
+ if value.is_a? Hash
23
+ from, to = value[:from], value[:to]
24
+ if from && to
25
+ query.where(name => from .. to)
26
+ elsif from
27
+ query.where(name >= Sequel.string_to_date(from))
28
+ elsif to
29
+ query.where(name <= Sequel.string_to_date(to))
30
+ else
31
+ query # ?
32
+ end
33
+ else
34
+ query.where(name => Sequel.string_to_date(value))
35
+ end
36
+ },
37
+ datetime: lambda{|query, name, value, type_info, hash|
38
+ if value.is_a? Hash
39
+ DefaultFilters[:date].(query, name, value, type_info, hash)
40
+ else
41
+ query.where(:date.sql_function(name) => Sequel.string_to_date(value))
42
+ end
43
+ },
44
+ integer: lambda{|query, name, value, type_info, hash|
45
+ if value.is_a? Hash
46
+ from, to = value[:from], value[:to]
47
+ if from && to
48
+ query.where(name => from .. to)
49
+ else
50
+ query.where(from ? name >= from.to_i : name <= to.to_i)
51
+ end
52
+ elsif value.is_a?(Integer) || value.is_a?(String)
53
+ query.where(name => value.to_i)
54
+ elsif value.is_a? Array
55
+ if !value.empty?
56
+ case type_info[:type]
57
+ when :many_to_one
58
+ keys = type_info[:keys]
59
+ if keys.length == 1
60
+ query.where(name => value)
61
+ else
62
+ query.where(keys.map{|k| hash[k]}.transpose.map{|vals| Hash[keys.zip(vals)]}.reduce{|q, c| q | c})
63
+ end
64
+ when :list_select
65
+ if type_info[:multiselect]
66
+ query.where(~{(name.sql_number & value.reduce(0, :|)) => 0})
67
+ else
68
+ query.where(name => value) # decode in sql query ?
69
+ end
70
+ when :integer
71
+ query
72
+ else
73
+ nil
74
+ end
75
+ else
76
+ nil
77
+ end
78
+ else
79
+ nil
80
+ end
81
+ },
82
+ boolean: lambda{|query, name, value, type_info, hash|
83
+ query.where(name => value)
84
+ }
85
+ )
86
+
87
+ def post_run
88
+ query select(*assets[:model].columns.reject{|col| assets[:model].type_info[col][:length].to_i > 20}.take(10)) unless @query
89
+ super
90
+ end
91
+
92
+ def page_frame handler, entries
93
+ entries
94
+ end
95
+
96
+ def invoke handler
97
+ params = handler.params
98
+ model = assets[:model]
99
+ query = list_context(get_query, handler)
100
+
101
+ if search = params[:search]
102
+ query = list_search(query, handler, search)
103
+ elsif @filters || static.filters
104
+ static.filters.to_h.merge(@filters.to_h).each do |name, filter|
105
+ query = filter.(handler, query, {})
106
+ handler.permit query
107
+ end
108
+ end
109
+
110
+ count = query.count if lookup(:config, :use_count)
111
+
112
+ if order = params[:order]
113
+ order = order.to_sym
114
+ handler.permit lookup(:fields, order, :sort)
115
+ query = list_order(query, handler, order, params[:asc] == "true")
116
+ elsif order = @default_order_field || static.default_order_field
117
+ query = list_order(query, handler, order, true)
118
+ end
119
+
120
+ per_page = lookup(:config, :per_page)
121
+ page = params[:page].to_i
122
+ handler.permit page >= 0 && page < 1000000
123
+
124
+ query = query.limit(per_page, page)
125
+
126
+ res = {entries: page_frame(handler, query.load_all)}
127
+ res[:count] = count if count
128
+ res
129
+ end
130
+
131
+ def list_search query, handler, search
132
+ hash = JSON.parse(search, symbolize_names: true) rescue handler.halt_forbidden
133
+ model = assets[:model]
134
+ sfields = lookup(:search_field_list)
135
+ handler.permit sfields
136
+
137
+ filters = @filters || static.filters ? static.filters.to_h.merge(@filters.to_h) : {}
138
+
139
+ sfields.each do |name|
140
+ type_info = model.find_type_info(name)
141
+ name = name.to_sym
142
+ filter = filters[name]
143
+ if !(value = hash[name]).nil? # use key?
144
+ query = if filter
145
+ filter.(handler, query, hash)
146
+ elsif filter = DefaultFilters[type_info[:otype]]
147
+ name = model.type_info[name] ? model.table_name.q(name) : Sequel.expr(name)
148
+ filter.(query, name, value, type_info, hash)
149
+ else
150
+ raise E2Error.new("Filter not found for field '#{name}' in model '#{model}'") unless filter
151
+ end
152
+ handler.permit query
153
+ else
154
+ if filter
155
+ query = filter.(handler, query, hash)
156
+ handler.permit query
157
+ end
158
+ end
159
+ end
160
+
161
+ # hash.each_pair do |name, value|
162
+ # handler.permit name = sfields.find{|sf|sf.to_sym == name}
163
+ # type_info = model.find_type_info(name)
164
+ # query = if filter = (@filters && @filters[name]) || (dynamic? && (static.filters && static.filters[name]))
165
+ # filter.(handler, query, hash)
166
+ # elsif filter = DefaultFilters[type_info[:otype]]
167
+ # name = model.type_info[name] ? model.table_name.q(name) : Sequel.expr(name)
168
+ # filter.(query, name, value, type_info, hash)
169
+ # else
170
+ # raise E2Error.new("Filter not found for field '#{name}' in model '#{model}'") unless filter
171
+ # end
172
+ # handler.permit query
173
+ # end
174
+
175
+ query
176
+ end
177
+
178
+ def list_order query, handler, order, asc
179
+ model = assets[:model]
180
+ query = if order_blk = (@orders && @orders[order]) || (dynamic? && (static.orders && static.orders[order]))
181
+ order_blk.(handler, query)
182
+ else
183
+ order = model.table_name.q(order) if model.type_info[order]
184
+ query.order(order)
185
+ end
186
+
187
+ asc ? query : query.reverse
188
+ end
189
+
190
+ def list_context query, handler
191
+ query
192
+ end
193
+ end
194
+
195
+ #
196
+ # Many to One
197
+ #
198
+ class ManyToOneListAction < ListAction
199
+ action_type :many_to_one_list
200
+
201
+ def pre_run
202
+ super
203
+ end
204
+ end
205
+
206
+ #
207
+ # * to Many
208
+ #
209
+ class StarToManyListAction < ListAction
210
+ action_type :star_to_many_list
211
+ def pre_run
212
+ super
213
+ menu(:panel_menu).option_at 0, :close, icon: "remove"
214
+ panel_title "#{assets[:model].model_icon.icon} #{LOCS[assets[:assoc][:name]]}"
215
+ end
216
+
217
+ def list_context query, handler
218
+ handler.permit parent = handler.params[:parent_id]
219
+ model = assets[:model]
220
+ assoc = assets[:assoc]
221
+ parent_keys = split_keys(parent)
222
+ case assoc[:type]
223
+ when :one_to_many
224
+ keys = assoc[:keys]
225
+ condition = parent_keys.all?(&:empty?) ? false : Hash[keys.map{|k| model.table_name.q(k)}.zip(parent_keys)]
226
+ if handler.params[:negate]
227
+ query = query.exclude(condition)
228
+ query = query.or(Hash[keys.zip([nil])]) if keys.all?{|k|model.db_schema[k][:allow_null] == true} # type_info[:required] ?
229
+ query
230
+ else
231
+ query.where(condition)
232
+ end
233
+ when :many_to_many
234
+ q_pk = model.primary_keys_qualified
235
+ j_table = assoc[:join_table]
236
+ l_keys = assoc[:left_keys].map{|k| j_table.q(k)}
237
+ r_keys = assoc[:right_keys].map{|k| j_table.q(k)}
238
+ r_keys_vals = Hash[r_keys.zip(q_pk)]
239
+ l_keys_vals = parent_keys.all?(&:empty?) ? false : Hash[l_keys.zip(parent_keys)]
240
+
241
+ if handler.params[:negate]
242
+ query.exclude(model.db[j_table].select(nil).where(r_keys_vals & l_keys_vals).exists)
243
+ else
244
+ # query.qualify.join(j_table, [r_keys_vals, l_keys_vals])
245
+ if joins = query.opts[:join] and joins.find{|j|j.table == j_table}
246
+ query
247
+ else
248
+ query.qualify.left_join(j_table, r_keys_vals)
249
+ end.filter(l_keys_vals)
250
+ end
251
+ else unsupported_association
252
+ end
253
+ end
254
+
255
+ def post_run
256
+ super
257
+ nd = node.parent.nodes[:decode_entry]
258
+ request do |h|
259
+ if h.initial?
260
+ action = nd.*
261
+ rec = action.invoke_decode(h, [split_keys(h.params[:parent_id])]).first
262
+ panel_title "#{static.panel[:title]} - #{action.meta[:decode_fields].map{|f|rec[f]}.join(action.meta[:separator])}"
263
+ end
264
+ end if nd
265
+ end
266
+ end
267
+
268
+ class StarToManyLinkListAction < StarToManyListAction
269
+ action_type :star_to_many_link_list
270
+ def pre_run
271
+ super
272
+ panel_title LOCS[:link_title]
273
+ menu(:panel_menu).option_at 0, :link, icon: "ok", enabled: "action.selected_size() > 0"
274
+ node.parent.*.menu(:menu).option_at 0, :link_list, icon: "paperclip", button_loc: false
275
+ end
276
+ end
277
+
278
+ # *_to_many_field
279
+ class StarToManyFieldAction < StarToManyListAction
280
+ action_type :star_to_many_field
281
+
282
+ def pre_run
283
+ super
284
+ modal_action false
285
+ panel_panel_template false
286
+ end
287
+
288
+ def list_context query, handler
289
+ changes = handler.param_to_json(:changes)
290
+ model = assets[:model]
291
+ unlinked = changes[:unlink].to_a + changes[:delete].to_a + changes[:modify].to_a.map{|m|join_keys(model.primary_keys.map{|k|m[k]})}
292
+ linked = changes[:link]
293
+ query = super(query, handler)
294
+
295
+ pks = model.primary_keys_qualified
296
+
297
+ if handler.params[:negate]
298
+ query = unlinked.reduce(query){|q, unl|q.or pks.zip(split_keys(unl))}
299
+ query = linked.reduce(query){|q, ln|q.where(pks.zip(split_keys(ln)).sql_negate)}
300
+ else
301
+ query = unlinked.reduce(query){|q, unl|q.where(pks.zip(split_keys(unl)).sql_negate)}
302
+ query = case assets[:assoc][:type]
303
+ when :one_to_many
304
+ linked.reduce(query){|q, ln|q.or pks.zip(split_keys(ln))}
305
+ when :many_to_many
306
+ linked.reduce(query){|q, ln|q.or pks.zip(split_keys(ln))}.distinct
307
+ else unsupported_association
308
+ end unless linked.empty?
309
+ end
310
+
311
+ added = changes[:create].to_a + changes[:modify].to_a
312
+ cols = get_query.columns
313
+ query = added.reduce query do |q, a|
314
+ q.union(model.db.select(*cols.map{|c|a[c]}), all: true, alias: model.table_name)
315
+ end
316
+
317
+ query
318
+ end
319
+ end
320
+
321
+ class StarToManyFieldLinkListAction < StarToManyFieldAction
322
+ action_type :star_to_many_field_link_list
323
+
324
+ def pre_run
325
+ super
326
+ modal_action true
327
+ panel_panel_template nil
328
+ panel_title LOCS[:link_title]
329
+ menu(:panel_menu).option_at 0, :link, icon: "ok", enabled: "action.selected_size() > 0"
330
+ node.parent.*.menu(:menu).option_at 0, :link_list, icon: "paperclip", button_loc: false
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Engine2
5
+ class SaveAction < Action
6
+ include ActionSaveSupport
7
+ end
8
+
9
+ class InsertAction < SaveAction
10
+ include ActionInsertSupport
11
+ action_type :approve
12
+ end
13
+
14
+ class UpdateAction < SaveAction
15
+ include ActionUpdateSupport
16
+ action_type :approve
17
+ end
18
+
19
+ class StarToManyFieldInsertAction < InsertAction
20
+ self.validate_only = true
21
+ action_type :star_to_many_field_approve
22
+ end
23
+
24
+ class StarToManyFieldUpdateAction < UpdateAction
25
+ self.validate_only = true
26
+ action_type :star_to_many_field_approve
27
+ end
28
+ end