engine2 1.0.5 → 1.0.6

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 (86) hide show
  1. checksums.yaml +5 -5
  2. data/app/actions.coffee +93 -58
  3. data/app/app.css +12 -0
  4. data/app/engine2.coffee +42 -24
  5. data/conf/message.yaml +1 -0
  6. data/conf/message_pl.yaml +1 -0
  7. data/config.coffee +2 -2
  8. data/engine2.gemspec +1 -1
  9. data/lib/engine2/action.rb +130 -126
  10. data/lib/engine2/action/array.rb +4 -4
  11. data/lib/engine2/action/decode.rb +13 -9
  12. data/lib/engine2/action/infra.rb +3 -3
  13. data/lib/engine2/action/list.rb +17 -10
  14. data/lib/engine2/action_node.rb +1 -2
  15. data/lib/engine2/core.rb +35 -7
  16. data/lib/engine2/model.rb +64 -15
  17. data/lib/engine2/post_bootstrap.rb +1 -1
  18. data/lib/engine2/pre_bootstrap.rb +10 -0
  19. data/lib/engine2/scheme.rb +2 -2
  20. data/lib/engine2/templates.rb +8 -0
  21. data/lib/engine2/type_info.rb +37 -15
  22. data/lib/engine2/version.rb +1 -1
  23. data/package.json +8 -5
  24. data/views/fields/blob.slim +1 -1
  25. data/views/fields/bs_select.slim +2 -2
  26. data/views/fields/bsselect_picker.slim +4 -4
  27. data/views/fields/bsselect_picker_opt.slim +5 -5
  28. data/views/fields/checkbox.slim +4 -4
  29. data/views/fields/checkbox_buttons.slim +3 -3
  30. data/views/fields/checkbox_buttons_opt.slim +3 -3
  31. data/views/fields/currency.slim +2 -2
  32. data/views/fields/date.slim +4 -4
  33. data/views/fields/date_range.slim +9 -9
  34. data/views/fields/date_time.slim +9 -9
  35. data/views/fields/datetime.slim +8 -8
  36. data/views/fields/decimal.slim +1 -1
  37. data/views/fields/decimal_date.slim +3 -3
  38. data/views/fields/decimal_time.slim +3 -3
  39. data/views/fields/email.slim +3 -3
  40. data/views/fields/file_store.slim +4 -4
  41. data/views/fields/input_text.slim +4 -4
  42. data/views/fields/integer.slim +1 -1
  43. data/views/fields/list_bsmselect.slim +20 -0
  44. data/views/fields/list_bsselect.slim +5 -5
  45. data/views/fields/list_bsselect_opt.slim +6 -6
  46. data/views/fields/list_buttons.slim +1 -1
  47. data/views/fields/list_buttons_opt.slim +2 -2
  48. data/views/fields/list_select.slim +4 -4
  49. data/views/fields/list_select_opt.slim +5 -5
  50. data/views/fields/password.slim +4 -4
  51. data/views/fields/radio_checkbox.slim +3 -3
  52. data/views/fields/scaffold.slim +1 -1
  53. data/views/fields/scaffold_picker.slim +5 -5
  54. data/views/fields/select_picker.slim +3 -3
  55. data/views/fields/select_picker_opt.slim +4 -4
  56. data/views/fields/text_area.slim +3 -3
  57. data/views/fields/time.slim +5 -4
  58. data/views/fields/typeahead_picker.slim +5 -5
  59. data/views/scaffold/fields.slim +4 -4
  60. data/views/scaffold/form.slim +1 -1
  61. data/views/scaffold/form_collapse.slim +4 -3
  62. data/views/scaffold/form_tabs.slim +3 -2
  63. data/views/scaffold/search.slim +2 -2
  64. data/views/scaffold/search_collapse.slim +6 -5
  65. data/views/scaffold/search_tabs.slim +4 -3
  66. data/views/scaffold/view.slim +2 -2
  67. data/views/scaffold/view_collapse.slim +5 -4
  68. data/views/scaffold/view_tabs.slim +4 -3
  69. data/views/search_fields/bsmselect_picker.slim +4 -4
  70. data/views/search_fields/bsselect_picker.slim +4 -4
  71. data/views/search_fields/checkbox.slim +3 -3
  72. data/views/search_fields/checkbox2.slim +5 -5
  73. data/views/search_fields/checkbox_buttons.slim +3 -3
  74. data/views/search_fields/date_range.slim +8 -8
  75. data/views/search_fields/decimal_date_range.slim +5 -5
  76. data/views/search_fields/input_text.slim +2 -2
  77. data/views/search_fields/integer.slim +1 -1
  78. data/views/search_fields/integer_range.slim +2 -2
  79. data/views/search_fields/list_bsmselect.slim +4 -4
  80. data/views/search_fields/list_bsselect.slim +4 -4
  81. data/views/search_fields/list_buttons.slim +2 -2
  82. data/views/search_fields/list_select.slim +3 -3
  83. data/views/search_fields/scaffold_picker.slim +2 -2
  84. data/views/search_fields/select_picker.slim +3 -3
  85. data/views/search_fields/typeahead_picker.slim +4 -4
  86. metadata +6 -5
@@ -45,7 +45,7 @@ module Engine2
45
45
 
46
46
  if order_str = params[:order]
47
47
  order = order_str.to_sym
48
- handler.permit lookup(:info, order, :sort)
48
+ handler.permit lookup(:fields, order, :sort)
49
49
  entries = entries.sort_by{|e|e[order].to_s}
50
50
  entries = entries.reverse if params[:asc] == "true"
51
51
  end
@@ -60,14 +60,14 @@ module Engine2
60
60
  def list_search entries, handler, search
61
61
  hash = JSON.parse(search, symbolize_names: true) rescue handler.halt_forbidden
62
62
  model = assets[:model]
63
- sfields = lookup(:search_fields)
63
+ sfields = lookup(:search_field_list)
64
64
  handler.permit sfields
65
65
  hash.each_pair do |name, value|
66
66
  handler.permit sfields.include?(name)
67
67
 
68
- type_info = get_type_info(name)
68
+ type_info = model.find_type_info(name)
69
69
  entries = if filter = (@filters && @filters[name]) || (dynamic? && (static.filters && static.filters[name]))
70
- filter.(entries, hash, handler)
70
+ filter.(handler, entries, hash)
71
71
  elsif filter = DefaultFilters[type_info[:otype]]
72
72
  filter.(entries, name, value, type_info, hash)
73
73
  else
@@ -19,8 +19,8 @@ module Engine2
19
19
  end
20
20
 
21
21
  def post_process
22
- if fields = @meta[:fields]
23
- fields = fields - static.meta[:fields] if dynamic?
22
+ if fields = @meta[:field_list]
23
+ fields = fields - static.meta[:field_list] if dynamic?
24
24
  # no decorate here
25
25
  fields.each do |name|
26
26
  type_info = assets[:model].type_info[name] # foreign keys ?
@@ -55,7 +55,7 @@ module Engine2
55
55
  action_type :decode_list
56
56
 
57
57
  def invoke handler
58
- {entries: get_query.limit(200).all}
58
+ {entries: get_query.limit(200).load_all}
59
59
  end
60
60
  end
61
61
 
@@ -64,20 +64,24 @@ module Engine2
64
64
 
65
65
  def pre_run
66
66
  super
67
- limit 10
67
+ @limit = 10
68
68
  end
69
69
 
70
70
  def limit lmt
71
- @meta[:limit] = lmt
71
+ @limit = lmt
72
+ end
73
+
74
+ def case_insensitive
75
+ @case_insensitive = true
72
76
  end
73
77
 
74
78
  def invoke handler
75
79
  if query = handler.params[:query]
76
- condition = @meta[:decode_fields].map{|f|f.like("%#{query}%")}.reduce{|q, f| q | f}
77
- {entries: get_query.where(condition).limit(@meta[:limit]).all}
80
+ condition = @meta[:decode_fields].map{|f|f.like("%#{query}%", case_insensitive: @case_insensitive)}.reduce{|q, f| q | f}
81
+ {entries: get_query.where(condition).limit(@limit).load_all}
78
82
  else
79
83
  handler.permit id = handler.params[:id]
80
- record = get_query[Hash[assets[:model].primary_keys.zip(split_keys(id))]]
84
+ record = get_query.load Hash[assets[:model].primary_keys.zip(split_keys(id))]
81
85
  # handler.halt_not_found(LOCS[:no_entry]) unless record
82
86
  {entry: record}
83
87
  end
@@ -92,7 +96,7 @@ module Engine2
92
96
  end
93
97
 
94
98
  def invoke_decode handler, ids
95
- records = get_query.where(ids.map{|keys| Hash[assets[:model].primary_keys.zip(keys)]}.reduce{|q, c| q | c}).all
99
+ records = get_query.where(ids.map{|keys| Hash[assets[:model].primary_keys.zip(keys)]}.reduce{|q, c| q | c}).load_all
96
100
  # handler.halt_not_found(LOCS[:no_entry]) if records.empty?
97
101
  records
98
102
  end
@@ -263,9 +263,9 @@ module Engine2
263
263
  super
264
264
  panel_class 'modal-default'
265
265
  panel_title LOCS[:login_title]
266
- info! :name, loc: LOCS[:user_name]
266
+ fields! :name, loc: LOCS[:user_name]
267
267
  menu(:panel_menu).modify_option :approve, name: :login, icon: :"log-in"
268
- @meta[:fields] = [:name, :password]
268
+ @meta[:field_list] = [:name, :password]
269
269
  parent_action = node.parent.*
270
270
  if parent_action.is_a? ActionMenuSupport
271
271
  parent_action.menu(:menu).option :login_form, icon: :"log-in", disabled: "action.action_pending()"
@@ -281,7 +281,7 @@ module Engine2
281
281
  include ActionApproveSupport
282
282
  action_type :login
283
283
 
284
- def validate_record handler, record
284
+ def validate_record handler, record, parent_id
285
285
  super
286
286
  record.values[:password] = nil
287
287
  end
@@ -9,6 +9,9 @@ module Engine2
9
9
  (DefaultFilters ||= {}).merge!(
10
10
  string: lambda{|query, name, value, type_info, hash|
11
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)
12
15
  when :many_to_one
13
16
  query.where(name => value)
14
17
  else
@@ -39,8 +42,8 @@ module Engine2
39
42
  else
40
43
  query.where(from ? name >= from.to_i : name <= to.to_i)
41
44
  end
42
- elsif value.is_a? Integer
43
- query.where(name => value)
45
+ elsif value.is_a?(Integer) || value.is_a?(String)
46
+ query.where(name => value.to_i)
44
47
  elsif value.is_a? Array
45
48
  if !value.empty?
46
49
  case type_info[:type]
@@ -52,7 +55,11 @@ module Engine2
52
55
  query.where(keys.map{|k| hash[k]}.transpose.map{|vals| Hash[keys.zip(vals)]}.reduce{|q, c| q | c})
53
56
  end
54
57
  when :list_select
55
- query.where(name => value) # decode in sql query ?
58
+ if type_info[:multiselect]
59
+ query.where(~{(name.sql_number & value.reduce(0, :|)) => 0})
60
+ else
61
+ query.where(name => value) # decode in sql query ?
62
+ end
56
63
  when :integer
57
64
  query
58
65
  else
@@ -88,10 +95,10 @@ module Engine2
88
95
 
89
96
  if order_str = params[:order]
90
97
  order = order_str.to_sym
91
- handler.permit lookup(:info, order, :sort)
98
+ handler.permit lookup(:fields, order, :sort)
92
99
 
93
100
  if order_blk = (@orders && @orders[order]) || (dynamic? && (static.orders && static.orders[order]))
94
- query = order_blk.(query, handler)
101
+ query = order_blk.(handler, query)
95
102
  else
96
103
  order = model.table_name.q(order) if model.type_info[order]
97
104
  query = query.order(order)
@@ -106,7 +113,7 @@ module Engine2
106
113
 
107
114
  query = query.limit(per_page, page)
108
115
 
109
- res = {entries: query.all}
116
+ res = {entries: query.load_all}
110
117
  res[:count] = count if count
111
118
  res
112
119
  end
@@ -114,14 +121,14 @@ module Engine2
114
121
  def list_search query, handler, search
115
122
  hash = JSON.parse(search, symbolize_names: true) rescue handler.halt_forbidden
116
123
  model = assets[:model]
117
- sfields = lookup(:search_fields)
124
+ sfields = lookup(:search_field_list)
118
125
  handler.permit sfields
119
126
  hash.each_pair do |name, value|
120
127
  handler.permit name = sfields.find{|sf|sf.to_sym == name}
121
128
 
122
- type_info = get_type_info(name)
129
+ type_info = model.find_type_info(name)
123
130
  query = if filter = (@filters && @filters[name]) || (dynamic? && (static.filters && static.filters[name]))
124
- filter.(query, hash, handler)
131
+ filter.(handler, query, hash)
125
132
  elsif filter = DefaultFilters[type_info[:otype]]
126
133
  name = model.type_info[name] ? model.table_name.q(name) : Sequel.expr(name)
127
134
  filter.(query, name, value, type_info, hash)
@@ -205,7 +212,7 @@ module Engine2
205
212
  if h.initial? && nd = node.parent.nodes[:decode_entry]
206
213
  action = nd.*
207
214
  rec = action.invoke_decode(h, [[h.params[:parent_id]]]).first
208
- panel_title "#{static.panel[:title]} - #{rec}"
215
+ panel_title "#{static.panel[:title]} - #{action.meta[:decode_fields].map{|f|rec[f]}.join(action.meta[:separator])}"
209
216
  end
210
217
  end
211
218
  end
@@ -38,7 +38,7 @@ module Engine2
38
38
  end
39
39
 
40
40
  def check_access! handler
41
- !@access_block || @access_block.(handler)
41
+ !@access_block || @access_block.(handler)
42
42
  end
43
43
 
44
44
  def run_scheme name, *args, &blk
@@ -162,7 +162,6 @@ module Engine2
162
162
  each_node do |node|
163
163
  if model = node.*.assets[:model]
164
164
  model_name = model.name.to_sym
165
- model.synchronize_type_info
166
165
  model_nodes[model_name] = node.to_a_rec{|a| !a.*.assets[:assoc]}
167
166
  node.run_scheme(model_name) if SCHEMES[model_name, false]
168
167
  false
@@ -328,6 +328,33 @@ module E2Model
328
328
  end
329
329
 
330
330
  module DatasetMethods
331
+ def load *args
332
+ if entry = self[*args]
333
+ model.after_load_processors.each do |name, proc|
334
+ type_info = model.find_type_info(name)
335
+ name_sym = name.to_sym
336
+ proc.(entry, name_sym, type_info) if entry.key?(name_sym)
337
+ end if model.after_load_processors
338
+ entry
339
+ end
340
+ end
341
+
342
+ def load_all
343
+ entries = self.all
344
+ apply_after_load_processors(model, entries) if model.after_load_processors
345
+ entries
346
+ end
347
+
348
+ def apply_after_load_processors model, entries
349
+ model.after_load_processors.each do |name, proc|
350
+ type_info = model.find_type_info(name)
351
+ name_sym = name.to_sym
352
+ entries.each do |entry|
353
+ proc.(entry, name_sym, type_info) if entry.key?(name_sym)
354
+ end
355
+ end
356
+ end
357
+
331
358
  def ensure_primary_key
332
359
  pk = model.primary_keys
333
360
  raise Engine2::E2Error.new("No primary key defined for model #{model}") unless pk && pk.all?
@@ -376,12 +403,12 @@ module E2Model
376
403
  end
377
404
  end
378
405
 
379
- def setup! fields
406
+ def setup_query fields
380
407
  joins = {}
381
408
  type_info = model.type_info
382
409
  model_table_name = model.table_name
383
410
 
384
- @opts[:select] = @opts[:select].map do |sel|
411
+ select = @opts[:select].map do |sel|
385
412
  extract_select sel do |table, name, aliaz|
386
413
  info = if table
387
414
  if table == model_table_name
@@ -416,11 +443,9 @@ module E2Model
416
443
  end
417
444
  end
418
445
  end
419
- end
446
+ end.compact
420
447
 
421
- @opts[:select].compact!.freeze
422
-
423
- joins.reduce(self) do |joined, (table, assoc)|
448
+ joins.reduce(clone(select: select)) do |joined, (table, assoc)|
424
449
  m = assoc.associated_class
425
450
  case assoc[:type]
426
451
  when :many_to_one
@@ -521,7 +546,10 @@ module Engine2
521
546
  load 'engine2/models/UserInfo.rb'
522
547
  Dir["#{Engine2::SETTINGS.path_for(:model_path)}/*"].each{|m| load m}
523
548
  puts "MODELS: #{Sequel::DATABASES.reduce(0){|s, d|s + d.models.size}}, Time: #{Time.now - t}"
524
- Sequel::DATABASES.each &:dump_schema_cache_to_file
549
+ Sequel::DATABASES.each do |db|
550
+ db.dump_schema_cache_to_file
551
+ db.models.each{|n, m|m.synchronize_type_info}
552
+ end
525
553
 
526
554
  send(:remove_const, :ROOT) if defined? ROOT
527
555
  const_set(:ROOT, ActionNode.new(nil, :api, RootAction, {}))
@@ -5,7 +5,7 @@ module Engine2
5
5
  module Model
6
6
  attr_reader :dummies
7
7
  attr_reader :many_to_one_associations, :one_to_many_associations, :many_to_many_associations #, :one_to_one_associations
8
- attr_reader :before_save_processors, :after_save_processors, :before_destroy_processors, :after_destroy_processors
8
+ attr_reader :after_load_processors, :before_save_processors, :after_save_processors, :before_destroy_processors, :after_destroy_processors
9
9
  attr_reader :validation_in_transaction
10
10
 
11
11
  def self.extended cls
@@ -19,6 +19,7 @@ module Engine2
19
19
  @many_to_many_associations = association_reflections.select{|n, a| a[:type] == :many_to_many}
20
20
  # @one_to_one_associations = association_reflections.select{|n, a| a[:type] == :one_to_one}
21
21
  @validation_in_transaction = nil
22
+ @after_load_processors = nil
22
23
  @before_save_processors = nil
23
24
  @after_save_processors = nil
24
25
  @around_save_processors = nil
@@ -109,9 +110,27 @@ module Engine2
109
110
  end
110
111
  end
111
112
 
113
+ def find_type_info name
114
+ model = self
115
+ info = case name
116
+ when Symbol
117
+ model.type_info[name]
118
+ when Sequel::SQL::QualifiedIdentifier
119
+ assoc = model.many_to_one_associations[name.table] || model.many_to_many_associations[name.table]
120
+ raise E2Error.new("Association #{name.table} not found for model #{model}") unless assoc
121
+ assoc.associated_class.type_info[name.column]
122
+ else
123
+ raise E2Error.new("Unknown type info key: #{name} in model #{model}")
124
+ end
125
+
126
+ raise E2Error.new("Type info not found for '#{name}' in model '#{model}'") unless info
127
+ info
128
+ end
129
+
112
130
  def synchronize_type_info
113
131
  resolve_dependencies
114
132
  verify_associations
133
+ @after_load_processors = install_processors(AfterLoadProcessors)
115
134
  @before_save_processors = install_processors(BeforeSaveProcessors)
116
135
  @after_save_processors = install_processors(AfterSaveProcessors)
117
136
  @around_save_processors = {}
@@ -120,18 +139,6 @@ module Engine2
120
139
  @type_info_synchronized = true
121
140
  end
122
141
 
123
- def verify_associations
124
- one_to_many_associations.each do |name, assoc|
125
- other = assoc.associated_class
126
- other_type_info = other.type_info
127
- if other_keys = assoc[:keys]
128
- other_keys.each do |key|
129
- raise E2Error.new("No key '#{key}' found in model '#{other}' being related from #{self}") unless other_type_info[key]
130
- end
131
- end
132
- end
133
- end
134
-
135
142
  def resolve_dependencies
136
143
  resolved = {}
137
144
  @type_info.each_pair do |name, info|
@@ -153,6 +160,18 @@ module Engine2
153
160
  resolved[name] = @type_info[name]
154
161
  end
155
162
 
163
+ def verify_associations
164
+ one_to_many_associations.each do |name, assoc|
165
+ other = assoc.associated_class
166
+ other_type_info = other.type_info
167
+ if other_keys = assoc[:keys]
168
+ other_keys.each do |key|
169
+ raise E2Error.new("No key '#{key}' found in model '#{other}' being related from #{self}") unless other_type_info[key]
170
+ end
171
+ end
172
+ end
173
+ end
174
+
156
175
  attr_reader :scheme_name, :scheme_args
157
176
 
158
177
  def scheme s_name = :default, opts = nil, &blk
@@ -272,7 +291,14 @@ module Engine2
272
291
  },
273
292
  list_select: lambda{|record, field, info|
274
293
  value = record.values[field]
275
- LOCS[:invalid_list_value] unless info[:list].any?{|a|a.first == value}
294
+ values = info[:values].map(&:first)
295
+
296
+ result = if info[:multiselect]
297
+ value.is_a?(Array) && (values - value).length == values.length - value.length
298
+ else
299
+ values.include?(value)
300
+ end
301
+ LOCS[:invalid_list_value] unless result
276
302
  },
277
303
  decimal: lambda{|record, field, info|
278
304
  value = record.values[field]
@@ -300,6 +326,20 @@ module Engine2
300
326
  }
301
327
  )
302
328
 
329
+ (AfterLoadProcessors ||= {}).merge!(
330
+ list_select: lambda{|record, field, info|
331
+ value = record[field]
332
+ record[field] = case info[:otype]
333
+ when :string
334
+ value.split(info[:separator])
335
+ when :integer
336
+ arr = []
337
+ value.bit_length.times{|i| arr << (1 << i) unless value[i].zero?}
338
+ arr
339
+ end if value && info[:multiselect]
340
+ }
341
+ )
342
+
303
343
  (BeforeSaveProcessors ||= {}).merge!(
304
344
  blob_store: lambda{|record, field, info|
305
345
  if value = record.values[field] # attachment info
@@ -326,6 +366,15 @@ module Engine2
326
366
  end
327
367
  File.delete("#{upload}/#{value[:rackname]}")
328
368
  end
369
+ },
370
+ list_select: lambda{|record, field, info|
371
+ value = record.values[field]
372
+ record[field] = case info[:otype]
373
+ when :string
374
+ value.join(info[:separator])
375
+ when :integer
376
+ value.reduce(0, :|)
377
+ end if value && info[:multiselect]
329
378
  }
330
379
  )
331
380
 
@@ -359,7 +408,7 @@ module Engine2
359
408
  # record.model.where(id).update(info[:field] => Sequel.blob(open("#{upload}/#{value[:rackname]}", "rb"){|f|f.read}))
360
409
  File.delete("#{upload}/#{value[:rackname]}")
361
410
  end
362
- }
411
+ },
363
412
  )
364
413
 
365
414
  (BeforeDestroyProcessors ||= {}).merge!(
@@ -9,7 +9,7 @@ module Sequel
9
9
  when Sequel::SQL::QualifiedIdentifier
10
10
  sel.column
11
11
  when Sequel::SQL::AliasedExpression
12
- Sequel::SQL::Identifier.new sel.aliaz
12
+ Sequel::SQL::Identifier.new sel.alias
13
13
  else
14
14
  sel # symbol ?
15
15
  end
@@ -24,6 +24,16 @@ module Sequel
24
24
  rs.getInt(1)
25
25
  end
26
26
  end
27
+
28
+ def valid_connection_sql
29
+ 'select 1 from sysibm.sysdummy1'
30
+ end
31
+ end if defined?(JDBC::AS400)
32
+
33
+ class JDBC::AS400::Dataset
34
+ def supports_where_true?
35
+ false
36
+ end
27
37
  end if defined?(JDBC::AS400)
28
38
 
29
39
  module SchemaCaching
@@ -163,7 +163,7 @@ module Engine2
163
163
  define_scheme :blob_store do |model, field|
164
164
  define_node :"#{field}_blob_store!", BlobStoreAction do
165
165
  self.*.model = model
166
- self.*.field = field # model.type_info[field][:field]
166
+ self.*.field = field
167
167
  define_node :download, DownloadBlobStoreAction
168
168
  define_node :upload, UploadBlobStoreAction
169
169
  end
@@ -175,7 +175,7 @@ module Engine2
175
175
  define_scheme :foreign_blob_store do |model, field|
176
176
  define_node :"#{field}_blob_store!", ForeignBlobStoreAction do
177
177
  self.*.model = model
178
- self.*.field = field # model.type_info[field][:field]
178
+ self.*.field = field
179
179
  define_node :download, DownloadForeignBlobStoreAction
180
180
  define_node :upload, UploadBlobStoreAction
181
181
  end