glib-web 3.10.0 → 3.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 017c468688e16caca324d9eb790e5392cdcf220b5f259cfd926f7d05a0f2823f
4
- data.tar.gz: 473404c0d90bf386e723e1c62f65456b4e0375932c144063d1c07526dad3e3f2
3
+ metadata.gz: 29bf404f48d26b824a2e10162a8ec01be7a13754027b4bd3e6b625c1255dbb80
4
+ data.tar.gz: 5ed5e65ac4e3cbd23180de8fd03f2a98746b4a5df787d8d69f5f25c9d0f66bbb
5
5
  SHA512:
6
- metadata.gz: e35b713b672704816511274ee46282209d190b067c4ae012cb4b0a00cf5d443d00345384f487e7afdb33526df82178bf621deaef3a04b001653ca772b651e028
7
- data.tar.gz: 224571d4d294707c2f9b1967f8a444e0718b333a616923d62c69a808b9852111b870a1d9d30f9036ad2e950cf7fb0cde45acdad91eb592b643c1c7c2e77d90b3
6
+ metadata.gz: 3c42ecfe127600686502e3721e37a05bf62ce0b194801f42c0c1d49c5df09d12f86cdd8435e682686de4d2c2cf7d288ca6c205cf3da48a452d1e2aadb1f685e1
7
+ data.tar.gz: '082866223cfe93db662d532a2030bc60428d82ae1d62c2b98c9910d2e37c3054ca5120d851cb021210eaca9cf60828483a3f91acc70061110d3581dcf17d015a'
@@ -1,10 +1,17 @@
1
1
  module Glib
2
2
  module EnumHelper
3
- def glib_enum_options(clazz, enum_field, keys = nil)
4
- # keys ||= clazz.send("#{enum_field}s").keys
5
-
6
- keys ||= clazz.send(enum_field.to_s.pluralize).keys
7
- keys.map { |i| { text: clazz.glib_enum_humanize(enum_field, i), value: i } }
3
+ def glib_enum_options(clazz, enum_field, keys: nil, include_hints: false)
4
+ enum_name = enum_field.to_s
5
+ keys ||= clazz.defined_enums[enum_name].keys
6
+ keys.map do |i|
7
+ text = clazz.glib_enum_humanize(enum_field, i)
8
+ if include_hints
9
+ i18n_key = clazz.model_name.i18n_key
10
+ hint = I18n.t("dt_models.#{i18n_key}.#{enum_name.pluralize}.#{i}.hint")
11
+ text += " #{hint}"
12
+ end
13
+ { text: text, value: i }
14
+ end
8
15
  end
9
16
 
10
17
  # See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones and description
@@ -1,15 +1,16 @@
1
1
  module Glib
2
2
  module FormsHelper
3
3
  def glib_form_field_label(model_name, prop, args = {})
4
- I18n.t("dt_models.#{model_name}.#{prop}.label", args.merge(default: nil)) || I18n.t("activerecord.attributes.#{model_name}.#{prop}", args)
4
+ I18n.t("dt_models.#{model_name}.#{prop}.label", **args.merge(default: nil)) ||
5
+ I18n.t("activerecord.attributes.#{model_name}.#{prop}", **args)
5
6
  end
6
7
 
7
8
  def glib_form_hint_label(model_name, prop, args = {})
8
- I18n.t("dt_models.#{model_name}.#{prop}.hint", args.merge(default: nil))
9
+ I18n.t("dt_models.#{model_name}.#{prop}.hint", **args.merge(default: nil))
9
10
  end
10
11
 
11
12
  def glib_form_placeholder_label(model_name, prop, args = {})
12
- I18n.t("dt_models.#{model_name}.#{prop}.placeholder", args.merge(default: nil))
13
+ I18n.t("dt_models.#{model_name}.#{prop}.placeholder", **args.merge(default: nil))
13
14
  end
14
15
  end
15
16
  end
@@ -5,7 +5,7 @@ module Glib
5
5
  ### Padding
6
6
 
7
7
  def glib_json_padding_body
8
- { top: 12, left: 20, right: 20, bottom: 12 }
8
+ { x: 20, y: 12, top: 22 }
9
9
  end
10
10
 
11
11
  def glib_json_padding_list
@@ -48,8 +48,8 @@ class Glib::JsonUi::ViewBuilder
48
48
  # Override
49
49
  def created
50
50
  if @prop && (form = page.current_form)
51
- dynamic_group = form.current_dynamic_group
52
- context = dynamic_group || form
51
+ association = form.nested_associations.last
52
+ context = association || form
53
53
 
54
54
  context.field_assert_respond_to(@prop)
55
55
 
@@ -57,10 +57,11 @@ class Glib::JsonUi::ViewBuilder
57
57
  @label ||= context.field_label(@prop, @label_args || {})
58
58
  @hint ||= context.hint_label(@prop, @hint_args || {})
59
59
  @placeholder ||= context.placeholder_label(@prop, @placeholder_args || {})
60
+ @validation ||= context.field_validation(@prop)
60
61
 
61
- if dynamic_group.nil?
62
- @value ||= form.field_value(@prop)
63
- @validation ||= form.field_validation(@prop)
62
+ if form.current_dynamic_group.nil?
63
+ # This is not relevant inside a dynamic group
64
+ @value ||= context.field_value(@prop)
64
65
  end
65
66
  end
66
67
  json.name @name
@@ -201,47 +202,23 @@ class Glib::JsonUi::ViewBuilder
201
202
  end
202
203
 
203
204
  class DynamicGroup < AbstractField
205
+ include Panels::ModelPanel
206
+
204
207
  string :titlePrefix
205
- # panels_builder :content, :template
208
+ panels_builder :content, :template
206
209
  hash :groupFieldProperties
207
210
 
208
211
  # NOTE: Consider using sub-panel instead (e.g. groupTemplate)
209
212
  # views :groupTemplateViews
210
213
 
211
- attr_writer :model
212
-
213
- def model(model)
214
- @model = model
215
- @model_name = @model.class.model_name.singular
216
- end
217
-
218
- def field_name(prop, multiple)
219
- prop
220
- end
221
-
222
- def field_label(prop, args)
223
- @delegate_class.field_label(@model, @model_name, prop, args)
224
- end
225
-
226
- def hint_label(prop, args)
227
- @delegate_class.hint_label(@model_name, prop, args)
228
- end
229
-
230
- def placeholder_label(prop, args)
231
- @delegate_class.placeholder_label(@model_name, prop, args)
232
- end
233
-
234
- def field_assert_respond_to(prop)
235
- raise "Please specify a model for #{self.class.component_name} before using its property" unless @model
236
- @delegate_class.field_assert_respond_to(@model, prop)
237
- end
238
-
239
214
  def content(value)
240
215
  @delegate_class = Glib::JsonUi::ViewBuilder::Panels::Form
241
216
  form = page.current_form
242
217
 
243
218
  form.current_dynamic_group = self
219
+ form.nested_associations << self
244
220
  value.call(page.content_builder([:template]))
221
+ form.nested_associations.pop
245
222
  form.current_dynamic_group = nil
246
223
  end
247
224
  end
@@ -1,12 +1,17 @@
1
1
  class Glib::JsonUi::ViewBuilder
2
2
  module Panels
3
3
  class Form < View
4
+ attr_reader :model_name # See Panels::Form.field_name
4
5
  attr_accessor :current_dynamic_group
5
6
 
6
7
  action :onSubmit
7
8
  string :paramNameForFormData
8
9
  bool :local
9
10
 
11
+ def nested_associations
12
+ @nested_associations ||= []
13
+ end
14
+
10
15
  # TODO: Enable this when we know it won't break existing apps.
11
16
  # Even for pure client-side apps, this is required because form.validate() requires a URL to construct form data.
12
17
  # required :url
@@ -39,19 +44,45 @@ class Glib::JsonUi::ViewBuilder
39
44
  end
40
45
 
41
46
  def field_name(prop, multiple)
42
- self.class.field_name(@model, @model_name, prop, multiple)
47
+ self.class.field_name(@model, prop, multiple, page)
43
48
  end
44
49
 
45
- def self.field_name(model, model_name, prop, multiple)
50
+ def self.field_name(model, prop, multiple, page)
51
+ form = page.current_form
52
+ include_form_model = true
53
+ reversed_model_panels = []
54
+ form.nested_associations.reverse.each do |panel|
55
+ if panel.is_a?(Fields::DynamicGroup)
56
+ include_form_model = false
57
+ break # Remove anything before Fields::DynamicGroup
58
+ end
59
+ reversed_model_panels << panel
60
+ end
61
+
62
+ name = include_form_model ? form.model_name : ''
63
+ reversed_model_panels.reverse.each do |panel|
64
+ index_exists = !panel.assoc_order_index.nil?
65
+ field_name = index_exists ? panel.model_name.pluralize : panel.model_name
66
+ name += "[#{field_name}_attributes]"
67
+ if index_exists
68
+ index = panel.assoc_order_index == :auto ? '' : panel.assoc_order_index
69
+ name += "[#{index}]"
70
+ end
71
+ end
72
+
46
73
  suffix = is_array_association?(model, prop) || multiple ? '[]' : ''
47
- "#{model_name}[#{prop}]#{suffix}"
74
+ "#{name}[#{prop}]#{suffix}"
48
75
  end
49
76
 
50
77
  def field_value(prop)
51
- if self.class.is_array_association?(@model, prop)
52
- @model.send(prop)&.map { |record| record.id }
78
+ self.class.field_value(@model, prop)
79
+ end
80
+
81
+ def self.field_value(model, prop)
82
+ if is_array_association?(model, prop)
83
+ model.send(prop)&.map { |record| record.id }
53
84
  else
54
- @model.send(prop)
85
+ model.send(prop)
55
86
  end
56
87
  end
57
88
 
@@ -84,8 +115,12 @@ class Glib::JsonUi::ViewBuilder
84
115
  end
85
116
 
86
117
  def field_validation(prop)
118
+ self.class.field_validation(@model, prop)
119
+ end
120
+
121
+ def self.field_validation(model, prop)
87
122
  validations = {}
88
- @model.class.validators_on(prop).each do |validator|
123
+ model.class.validators_on(prop).each do |validator|
89
124
  if validator.kind == :presence
90
125
  validations[:required] = { message: 'Required' }
91
126
  end
@@ -332,5 +367,61 @@ class Glib::JsonUi::ViewBuilder
332
367
  # end
333
368
  end
334
369
 
370
+ module ModelPanel
371
+ attr_reader :model_name # See Panels::Form.field_name
372
+ attr_reader :assoc_order_index
373
+
374
+ def model(model)
375
+ @model = model
376
+ @model_name = @model.class.model_name.singular
377
+ end
378
+
379
+ def order_index(index)
380
+ @assoc_order_index = index
381
+ end
382
+
383
+ def field_name(prop, multiple)
384
+ @delegate_class.field_name(@model, prop, multiple, page)
385
+ end
386
+
387
+ def field_value(prop)
388
+ @delegate_class.field_value(@model, prop)
389
+ end
390
+
391
+ def field_label(prop, args)
392
+ @delegate_class.field_label(@model, @model_name, prop, args)
393
+ end
394
+
395
+ def hint_label(prop, args)
396
+ @delegate_class.hint_label(@model_name, prop, args)
397
+ end
398
+
399
+ def placeholder_label(prop, args)
400
+ @delegate_class.placeholder_label(@model_name, prop, args)
401
+ end
402
+
403
+ def field_validation(prop)
404
+ @delegate_class.field_validation(@model, prop)
405
+ end
406
+
407
+ def field_assert_respond_to(prop)
408
+ raise "Please specify a model for #{self.class.component_name} before using its property" unless @model
409
+ @delegate_class.field_assert_respond_to(@model, prop)
410
+ end
411
+ end
412
+
413
+ class Association < View
414
+ include ModelPanel
415
+
416
+ def childViews(value)
417
+ @delegate_class = Glib::JsonUi::ViewBuilder::Panels::Form
418
+ form = page.current_form
419
+
420
+ form.nested_associations << self
421
+ json.set!(:childViews) { value.call(page.view_builder) }
422
+ form.nested_associations.pop
423
+ end
424
+ end
425
+
335
426
  end
336
427
  end
@@ -11,19 +11,19 @@ module Glib
11
11
  extend ClassMethods
12
12
  end
13
13
 
14
- def glib_enum_humanize(enum_name, default_value = nil)
15
- self.class.glib_enum_humanize(enum_name, send(enum_name), default_value)
14
+ def glib_enum_humanize(enum_name, default_enum_value = nil)
15
+ self.class.glib_enum_humanize(enum_name, send(enum_name) || default_enum_value)
16
16
  end
17
17
  end
18
18
 
19
19
  module ClassMethods
20
- def glib_enum_humanize(enum_name, enum_value, default_value = nil)
20
+ def glib_enum_humanize(enum_name, enum_value, default_translation = nil)
21
21
  if enum_value
22
- if default_value.nil? && Rails.env.development?
22
+ if default_translation.nil? && Rails.env.development?
23
23
  i18n_key = "activerecord.attributes.#{model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{enum_value}"
24
24
  I18n.t(i18n_key, raise: I18n::MissingTranslationData)
25
25
  else
26
- I18n.t(i18n_key, default: default_value)
26
+ I18n.t(i18n_key, default: default_translation)
27
27
  end
28
28
  end
29
29
  end
@@ -1,40 +1,72 @@
1
1
  # To use this, simply:
2
2
  # - Include the module
3
3
  # - Add this migration: `add_column :model, :deleted_at, :datetime, null: true`
4
- #
5
- # After that, any call to `destroy` will automatically be a soft-deletion.
6
4
  module Glib
7
5
  module SoftDeletable
8
6
  extend ActiveSupport::Concern
9
7
 
8
+ module ClassMethods
9
+ def auto_hide_soft_deleted_records
10
+ column_name = :deleted_at
11
+ default_scope { where(column_name => nil) }
12
+
13
+ if Rails.env.development? || Rails.env.test?
14
+ raise ActiveRecord::ConfigurationError, "#{table_name}.#{column_name} need to be indexed" unless connection.index_exists?(table_name, column_name)
15
+ end
16
+ end
17
+
18
+ # `behaviour` can be either `:soft_destroy` or `:hard_destroy``
19
+ def on_mark_for_destruction(behaviour)
20
+ @__glib_mark_for_destruction_behaviour = behaviour
21
+ end
22
+
23
+ def __mark_for_destruction_behaviour
24
+ @__glib_mark_for_destruction_behaviour ||= :disallowed
25
+ end
26
+ end
27
+
10
28
  included do
11
- default_scope { where(deleted_at: nil) }
29
+ extend ClassMethods
30
+
12
31
  scope :with_deleted, -> { unscope(where: :deleted_at) }
13
32
 
14
- # "Soft delete" - set deleted_at if it is nil, actually destroy the record
15
- # if forced.
16
- #
17
- # @param force [Boolean]
18
- def destroy(force = nil)
19
- return force_destroy_record if force == :force
20
- return self if deleted?
33
+ # Override
34
+ def destroy
35
+ if marked_for_destruction?
36
+ case (behaviour = self.class.__mark_for_destruction_behaviour)
37
+ when :soft_destroy
38
+ soft_destroy
39
+ when :hard_destroy
40
+ # Make sure that the associated records are handled the same way as this record (i.e. the parent record).
41
+ mark_applicable_associations_for_destruction
42
+
43
+ hard_destroy
44
+ when :disallowed
45
+ raise_hard_delete_disallowed
46
+ else
47
+ raise "Unsupported on_mark_for_destruction behaviour: #{behaviour}"
48
+ end
49
+
50
+ return true # Tell Rails that destroy has succeeded
51
+ end
21
52
 
22
- soft_destroy_record
53
+ raise_hard_delete_disallowed
23
54
  end
24
55
 
25
- # More explicit naming which is sometimes useful for readability.
26
- def soft_destroy
27
- soft_destroy_record
56
+
57
+ def hard_destroy
58
+ method(:destroy).super_method.call
28
59
  end
29
60
 
30
- # Revive a soft-deleted record and associated records if soft-deleted,
31
- # otherwise return self
32
- def revive
33
- return self unless deleted?
61
+ def soft_destroy
62
+ ActiveRecord::Base.transaction do
63
+ _soft_destroy
64
+ end
65
+ end
34
66
 
67
+ def undo_soft_destroy
35
68
  ActiveRecord::Base.transaction do
36
- update(deleted_at: nil)
37
- revive_associated_records
69
+ _undo_soft_destroy
38
70
  end
39
71
  end
40
72
 
@@ -43,34 +75,69 @@ module Glib
43
75
  deleted_at.present?
44
76
  end
45
77
 
78
+ # Bypass validation because it is impossible to ensure records that have circular dependencies are valid at all time because
79
+ # records are updated/validated one at a time.
80
+ # Besides, it's probably not a good idea to prevent soft-deletion of objects that are already not valid.
81
+ def _soft_destroy
82
+ update_columns(deleted_at: DateTime.current)
83
+ soft_destroy_associated_records
84
+ end
85
+
86
+ def _undo_soft_destroy
87
+ undo_soft_destroy_associated_records
88
+ update_columns(deleted_at: nil)
89
+ end
90
+
46
91
  private
47
- def force_destroy_record
48
- ActiveRecord::Base.transaction do
49
- destroy_associated_records(:force)
50
- method(:destroy).super_method.call
92
+ def mark_applicable_associations_for_destruction
93
+ self.class.reflect_on_all_associations.each do |assoc|
94
+ next unless assoc.options[:dependent] == :destroy
95
+
96
+ process_soft_deletable_relationship(assoc.name) do |record|
97
+ record.mark_for_destruction
98
+ end
51
99
  end
52
100
  end
53
101
 
54
- def soft_destroy_record
55
- ActiveRecord::Base.transaction do
56
- destroy_associated_records
57
- update(deleted_at: Time.zone.now)
102
+ def raise_hard_delete_disallowed
103
+ raise ActiveRecord::ConfigurationError, "Hard deletion is not allowed for #{self.class.name}"
104
+ end
105
+
106
+ def process_soft_deletable_relationship(relationship, &block)
107
+ association(relationship) # Raises an exception if relationship doesn't exist. This is a security safe guard.
108
+
109
+ # Use `send()` instead of `association(relationship).scope` because the latter returns new record objects as opposed
110
+ # to existing objects which could cause a side effect.
111
+ relation = send(relationship)
112
+
113
+ if relation.respond_to?(:find_each)
114
+ relation.find_each do |record|
115
+ block.call(record)
116
+ end
117
+ else
118
+ block.call(relation) unless relation.nil? # Process a single record
58
119
  end
59
120
  end
60
121
 
61
- def destroy_associated_records(force = nil)
62
- associated_records.each { |r| r.destroy(force) }
122
+ def soft_destroy_associated_records
123
+ soft_deletable_associations.each do |relationship|
124
+ process_soft_deletable_relationship(relationship) { |record| record._soft_destroy }
125
+ end
63
126
  end
64
127
 
65
- def revive_associated_records
66
- associated_records.each(&:revive)
128
+ def undo_soft_destroy_associated_records
129
+ soft_deletable_associations.each do |relationship|
130
+ process_soft_deletable_relationship(relationship) { |record| record._undo_soft_destroy }
131
+ end
67
132
  end
68
133
 
69
134
  # This should return an array of all associated records of an object that
70
- # should be soft deleted with it (i.e. those that have dependent: :destroy
71
- # set). In principle we could figure this out automatically but in the
72
- # interest of simplicity we'll just define it manually for each class.
73
- def associated_records
135
+ # should be soft deleted with it. This provides a non-intrusive way that can
136
+ # co-exist with hard deletion (e.g. using `dependent: :destroy`), which means
137
+ # that the model can use both for different purposes.
138
+ # For example, a model may use soft-deletion for archiving and hard-deletion
139
+ # for editing (e.g. using `accepts_nested_attributes_for``)
140
+ def soft_deletable_associations
74
141
  []
75
142
  end
76
143
  end
@@ -7,14 +7,16 @@ module Glib
7
7
  scope :created_asc, -> { order(created_at: :asc) }
8
8
  scope :created_desc, -> { order(created_at: :desc) }
9
9
 
10
- # def glib_enum_humanize(enum_name, default_value = nil)
11
- # self.class.glib_enum_humanize(enum_name, send(enum_name), default_value)
12
- # end
10
+ def self.glib_has_one_attached(field_name)
11
+ has_one_attached field_name
13
12
 
14
- # def self.glib_enum_humanize(enum_name, enum_value, default_value = nil)
15
- # if enum_value
16
- # I18n.t("activerecord.attributes.#{model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{enum_value}", default: default_value)
17
- # end
18
- # end
13
+ define_method("#{field_name}=") do |value|
14
+ if value == ''
15
+ send(field_name).detach
16
+ else
17
+ super(value)
18
+ end
19
+ end
20
+ end
19
21
  end
20
22
  end
@@ -25,8 +25,8 @@ page.form options.merge(childViews: ->(form) do
25
25
  form.fields_file name: 'user[pdf1][]', width: 'matchParent', label: 'PDF Document', accepts: rules, directUploadUrl: rails_direct_uploads_url,
26
26
  placeholderView: { type: 'image', width: 100, height: 100, url: 'data:image/webp;base64,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' }
27
27
 
28
- rules = { fileType: 'pdf', maxFileSize: 5000 }
29
- form.fields_file name: 'user[pdf2][]', width: 'matchParent', label: 'PDF Document', accepts: rules, directUploadUrl: rails_direct_uploads_url
28
+ rules = { fileType: ['pdf', 'image'], maxFileSize: 5000 }
29
+ form.fields_file name: 'user[pdf2][]', width: 'matchParent', label: 'PDF/Image File', accepts: rules, directUploadUrl: rails_direct_uploads_url
30
30
 
31
31
  rules = { fileType: 'zip', maxFileSize: 5000 }
32
32
  form.fields_file name: 'user[zip][]', width: 'matchParent', label: 'ZIP Document', accepts: rules, directUploadUrl: rails_direct_uploads_url
@@ -50,8 +50,9 @@ page.list sections: [
50
50
  template.thumbnail title: 'Submit on Change', onClick: ->(action) do
51
51
  action.windows_open url: json_ui_garage_url(path: 'forms/submit_on_change')
52
52
  end
53
-
54
-
53
+ template.thumbnail title: 'Dynamic Group', onClick: ->(action) do
54
+ action.windows_open url: json_ui_garage_url(path: 'forms/dynamic_group')
55
+ end
55
56
  end
56
57
  end, ->(section) do
57
58
  section.header padding: glib_json_padding_list, childViews: ->(header) do
@@ -105,7 +106,6 @@ page.list sections: [
105
106
  section.header padding: glib_json_padding_list, childViews: ->(header) do
106
107
  header.h2 text: 'Web Only'
107
108
  end
108
-
109
109
  section.rows builder: ->(template) do
110
110
  template.thumbnail title: 'Basic Rich Text Editor', onClick: ->(action) do
111
111
  action.windows_open url: json_ui_garage_url(path: 'forms/rich_text')
@@ -124,9 +124,6 @@ page.list sections: [
124
124
  end
125
125
 
126
126
  section.rows builder: ->(template) do
127
- template.thumbnail title: 'Dynamic Group', onClick: ->(action) do
128
- action.windows_open url: json_ui_garage_url(path: 'forms/dynamic_group')
129
- end
130
127
  template.thumbnail title: 'Dynamic Select', onClick: ->(action) do
131
128
  action.windows_open url: json_ui_garage_url(path: 'forms/dynamic_select')
132
129
  end
@@ -56,7 +56,7 @@ page.form url: json_ui_garage_url(path: 'forms/generic_post'), method: 'post', p
56
56
  options = []
57
57
  languages.each do |group, sub|
58
58
  options << { type: 'label', text: group }
59
- options.concat(sub.map { |k, v| { value: k, text: v } })
59
+ options.concat(sub.map { |k, v| { value: k, text: v, subtitle: 'Country' } })
60
60
  options << { type: 'divider' }
61
61
  end
62
62
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glib-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.0
4
+ version: 3.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''