engine2 1.0.4 → 1.0.9

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