active_scaffold 4.3.1 → 4.4.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 +4 -4
- data/CHANGELOG.rdoc +26 -1
- data/README.md +1 -1
- data/app/assets/javascripts/active_scaffold/active_scaffold.js +107 -16
- data/app/assets/javascripts/active_scaffold/draggable_lists.js +4 -3
- data/app/assets/javascripts/active_scaffold/load.js +20 -5
- data/app/assets/stylesheets/active_scaffold/_images.scss +53 -1
- data/app/assets/stylesheets/active_scaffold/_layout.scss +1 -66
- data/app/views/active_scaffold_overrides/_add_existing_form.html.erb +17 -11
- data/app/views/active_scaffold_overrides/_base_form.html.erb +29 -19
- data/app/views/active_scaffold_overrides/_field_search.html.erb +9 -9
- data/app/views/active_scaffold_overrides/_field_search_columns.html.erb +29 -33
- data/app/views/active_scaffold_overrides/_form.html.erb +43 -34
- data/app/views/active_scaffold_overrides/_form_association.html.erb +3 -3
- data/app/views/active_scaffold_overrides/_form_association_footer.html.erb +20 -16
- data/app/views/active_scaffold_overrides/_form_association_record.html.erb +3 -4
- data/app/views/active_scaffold_overrides/_horizontal_subform_header.html.erb +2 -1
- data/app/views/active_scaffold_overrides/_list.html.erb +6 -4
- data/app/views/active_scaffold_overrides/_list_calculations.html.erb +1 -1
- data/app/views/active_scaffold_overrides/_list_header.html.erb +3 -3
- data/app/views/active_scaffold_overrides/_list_messages.html.erb +19 -19
- data/app/views/active_scaffold_overrides/_list_messages_content.html.erb +5 -5
- data/app/views/active_scaffold_overrides/_list_pagination.html.erb +7 -5
- data/app/views/active_scaffold_overrides/_list_pagination_links.html.erb +5 -3
- data/app/views/active_scaffold_overrides/_list_record.html.erb +13 -8
- data/app/views/active_scaffold_overrides/_list_with_header.html.erb +8 -8
- data/app/views/active_scaffold_overrides/_messages.html.erb +3 -3
- data/app/views/active_scaffold_overrides/_render_field.js.erb +8 -11
- data/app/views/active_scaffold_overrides/_search.html.erb +7 -6
- data/app/views/active_scaffold_overrides/_show.html.erb +4 -4
- data/app/views/active_scaffold_overrides/_show_actions.html.erb +2 -2
- data/app/views/active_scaffold_overrides/_show_columns.html.erb +28 -30
- data/app/views/active_scaffold_overrides/add_existing.js.erb +7 -4
- data/app/views/active_scaffold_overrides/add_tab.js.erb +2 -3
- data/app/views/active_scaffold_overrides/render_field.js.erb +1 -1
- data/config/locales/de.yml +2 -1
- data/config/locales/en.yml +33 -32
- data/config/locales/es.yml +15 -14
- data/config/locales/fr.yml +3 -2
- data/config/locales/hu.yml +3 -2
- data/config/locales/ja.yml +2 -1
- data/config/locales/ro.yml +169 -0
- data/config/locales/ru.yml +8 -7
- data/config/locales/ta.yml +7 -6
- data/config/locales/zh_Hans.yml +14 -13
- data/lib/active_scaffold/actions/common_search.rb +1 -14
- data/lib/active_scaffold/actions/core.rb +55 -16
- data/lib/active_scaffold/actions/field_search.rb +2 -2
- data/lib/active_scaffold/actions/list.rb +35 -13
- data/lib/active_scaffold/actions/nested.rb +31 -7
- data/lib/active_scaffold/actions/show.rb +1 -1
- data/lib/active_scaffold/active_record_permissions.rb +1 -1
- data/lib/active_scaffold/attribute_params.rb +2 -1
- data/lib/active_scaffold/bridges/date_picker/helper.rb +14 -0
- data/lib/active_scaffold/bridges/date_picker.rb +10 -0
- data/lib/active_scaffold/config/list.rb +10 -1
- data/lib/active_scaffold/core.rb +9 -6
- data/lib/active_scaffold/data_structures/action_columns.rb +2 -0
- data/lib/active_scaffold/data_structures/column.rb +27 -6
- data/lib/active_scaffold/data_structures/filter.rb +4 -0
- data/lib/active_scaffold/data_structures/nested_info.rb +8 -1
- data/lib/active_scaffold/data_structures/sorting.rb +1 -1
- data/lib/active_scaffold/extensions/action_view_rendering.rb +1 -1
- data/lib/active_scaffold/finder.rb +66 -21
- data/lib/active_scaffold/helpers/action_link_helpers.rb +43 -18
- data/lib/active_scaffold/helpers/assets_helpers.rb +4 -4
- data/lib/active_scaffold/helpers/filter_helpers.rb +3 -3
- data/lib/active_scaffold/helpers/form_column_helpers.rb +34 -22
- data/lib/active_scaffold/helpers/form_ui_helpers.rb +15 -10
- data/lib/active_scaffold/helpers/framework_ui_helpers.rb +22 -0
- data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -0
- data/lib/active_scaffold/helpers/list_column_helpers.rb +19 -0
- data/lib/active_scaffold/helpers/pagination_helpers.rb +28 -7
- data/lib/active_scaffold/helpers/search_column_helpers.rb +32 -5
- data/lib/active_scaffold/helpers/tabs_helpers.rb +4 -6
- data/lib/active_scaffold/helpers/view_helpers.rb +12 -0
- data/lib/active_scaffold/tableless.rb +11 -8
- data/lib/active_scaffold/version.rb +2 -2
- data/lib/active_scaffold.rb +169 -0
- data/lib/tasks/bundle.rake +2 -1
- data/lib/tasks/mock_app.rake +43 -0
- metadata +20 -7
- data/shoulda_macros/macros.rb +0 -145
data/config/locales/ru.yml
CHANGED
|
@@ -20,7 +20,7 @@ ru:
|
|
|
20
20
|
begins_with: Начинается с
|
|
21
21
|
between: В интервале
|
|
22
22
|
cancel: Отмена
|
|
23
|
-
cant_destroy_record: Запись %{record} не может быть
|
|
23
|
+
cant_destroy_record: Запись %{record} не может быть удалена.
|
|
24
24
|
changes: Изменения
|
|
25
25
|
check_all: Выбрать все
|
|
26
26
|
click_to_edit: Нажмите для редактирования
|
|
@@ -62,9 +62,9 @@ ru:
|
|
|
62
62
|
template:
|
|
63
63
|
body: 'Проблемы возникли со следующими полями:'
|
|
64
64
|
header:
|
|
65
|
-
one: '%{model}: сохранение не удалось из-за %{count}
|
|
66
|
-
few: '%{model}: сохранение не удалось из-за %{count}
|
|
67
|
-
many: '%{model}: сохранение не удалось из-за %{count}
|
|
65
|
+
one: '%{model}: сохранение не удалось из-за %{count} ошибки.'
|
|
66
|
+
few: '%{model}: сохранение не удалось из-за %{count} ошибок.'
|
|
67
|
+
many: '%{model}: сохранение не удалось из-за %{count} ошибок.'
|
|
68
68
|
export: Экспорт
|
|
69
69
|
'false': Нет
|
|
70
70
|
filtered: "(Найденное)"
|
|
@@ -96,7 +96,7 @@ ru:
|
|
|
96
96
|
next_month: В следующем месяце
|
|
97
97
|
next_week: На следующей неделе
|
|
98
98
|
next_year: В следующем году
|
|
99
|
-
no_authorization_for_action: Нет прав на выполнение действия "%{action}"
|
|
99
|
+
no_authorization_for_action: Нет прав на выполнение действия "%{action}".
|
|
100
100
|
no_color: Без цвета
|
|
101
101
|
no_entries: Нет записей
|
|
102
102
|
no_group: Без группы
|
|
@@ -114,7 +114,7 @@ ru:
|
|
|
114
114
|
previous: Предыдущее
|
|
115
115
|
print: Печать
|
|
116
116
|
range: Интервал
|
|
117
|
-
record_not_saved: Запись
|
|
117
|
+
record_not_saved: Запись не может быть сохранена из-за неизвестной ошибки.
|
|
118
118
|
records_marked:
|
|
119
119
|
one: Отмечена 1 запись
|
|
120
120
|
few: Отмечено %{count} записи
|
|
@@ -147,10 +147,11 @@ ru:
|
|
|
147
147
|
update_model: "%{model}: обновить запись"
|
|
148
148
|
updated_model: "%{model}: запись обновлена"
|
|
149
149
|
version_inconsistency: Эта запись была обновлена с того момента, как вы
|
|
150
|
-
начали ее
|
|
150
|
+
начали ее редактировать.
|
|
151
151
|
weeks: недель
|
|
152
152
|
years: лет
|
|
153
153
|
yesterday: Вчера
|
|
154
|
+
already_added_model: '%{model} has already been added.'
|
|
154
155
|
date:
|
|
155
156
|
formats:
|
|
156
157
|
month: '%B'
|
data/config/locales/ta.yml
CHANGED
|
@@ -20,7 +20,7 @@ ta:
|
|
|
20
20
|
begins_with: 'உடன் தொடங்குகிறது'
|
|
21
21
|
between: 'இடையில்'
|
|
22
22
|
cancel: 'ரத்துசெய்'
|
|
23
|
-
cant_destroy_record: "%{record} அழிக்க
|
|
23
|
+
cant_destroy_record: "%{record} அழிக்க முடியாது."
|
|
24
24
|
changes: 'மாற்றங்கள்'
|
|
25
25
|
check_all: 'அனைத்தையும் சரிபார்க்கவும்'
|
|
26
26
|
click_to_edit: 'திருத்த சொடுக்கு செய்யவும்'
|
|
@@ -38,8 +38,8 @@ ta:
|
|
|
38
38
|
customize: 'தனிப்பயனாக்கு'
|
|
39
39
|
date_picker_options:
|
|
40
40
|
firstDay: '0'
|
|
41
|
-
isRTL: '
|
|
42
|
-
showMonthAfterYear: '
|
|
41
|
+
isRTL: 'false'
|
|
42
|
+
showMonthAfterYear: 'false'
|
|
43
43
|
weekHeader: 'Wk'
|
|
44
44
|
datetime_picker_options:
|
|
45
45
|
closeText: 'மூடு'
|
|
@@ -63,7 +63,7 @@ ta:
|
|
|
63
63
|
body: 'பின்வரும் துறைகளில் சிக்கல்கள் உள்ளன:'
|
|
64
64
|
header:
|
|
65
65
|
one: 1 பிழை இந்த %{model} சேமிக்கப்படுவதைத் தடுக்கிறது.
|
|
66
|
-
other: '%{count} பிழைகள் இந்த %{model} சேமிக்கப்படுவதைத்
|
|
66
|
+
other: '%{count} பிழைகள் இந்த %{model} சேமிக்கப்படுவதைத் தடுக்கின்றன.'
|
|
67
67
|
export: 'ஏற்றுமதி'
|
|
68
68
|
'false': 'பொய்'
|
|
69
69
|
filtered: "(வடிகட்டப்பட்டது)"
|
|
@@ -92,7 +92,7 @@ ta:
|
|
|
92
92
|
next_month: 'அடுத்த மாதம்'
|
|
93
93
|
next_week: 'அடுத்த வாரம்'
|
|
94
94
|
next_year: 'அடுத்த வருடம்'
|
|
95
|
-
no_authorization_for_action: 'செயலுக்கு ஏற்பு இல்லை %{action}'
|
|
95
|
+
no_authorization_for_action: 'செயலுக்கு ஏற்பு இல்லை %{action}.'
|
|
96
96
|
no_color: 'நிறம் இல்லை'
|
|
97
97
|
no_entries: 'உள்ளீடுகள் இல்லை'
|
|
98
98
|
no_group: 'குழு இல்லை'
|
|
@@ -110,7 +110,7 @@ ta:
|
|
|
110
110
|
previous: 'முந்தைய'
|
|
111
111
|
print: 'அச்சிடுக'
|
|
112
112
|
range: 'வீச்சு'
|
|
113
|
-
record_not_saved: 'அறியப்படாத பிழைக்கான பதிவைச் சேமிப்பதில்
|
|
113
|
+
record_not_saved: 'அறியப்படாத பிழைக்கான பதிவைச் சேமிப்பதில் தோல்வி.'
|
|
114
114
|
records_marked:
|
|
115
115
|
one: 1 குறிக்கப்பட்டது %{model}
|
|
116
116
|
other: '%{count} %{model} எனக் குறிக்கப்பட்டது'
|
|
@@ -146,6 +146,7 @@ ta:
|
|
|
146
146
|
weeks: 'வாரங்கள்'
|
|
147
147
|
years: 'ஆண்டுகள்'
|
|
148
148
|
yesterday: 'நேற்று'
|
|
149
|
+
already_added_model: '%{model} has already been added.'
|
|
149
150
|
date:
|
|
150
151
|
formats:
|
|
151
152
|
month: "%B"
|
data/config/locales/zh_Hans.yml
CHANGED
|
@@ -20,7 +20,7 @@ zh_Hans:
|
|
|
20
20
|
begins_with: 以……开始
|
|
21
21
|
between: 介于……之间
|
|
22
22
|
cancel: 取消
|
|
23
|
-
cant_destroy_record: '%{record}
|
|
23
|
+
cant_destroy_record: '%{record}不能被删除。'
|
|
24
24
|
changes: 变更记录
|
|
25
25
|
check_all: 全选
|
|
26
26
|
click_to_edit: 点击以编辑
|
|
@@ -30,7 +30,7 @@ zh_Hans:
|
|
|
30
30
|
config_list_model: 为%{model}配置列
|
|
31
31
|
contains: 包含
|
|
32
32
|
create: 创建
|
|
33
|
-
create_another:
|
|
33
|
+
create_another: 新建另一 %{model}
|
|
34
34
|
create_apply: 创建并继续
|
|
35
35
|
create_model: 创建 %{model}
|
|
36
36
|
create_new: 新建
|
|
@@ -62,15 +62,15 @@ zh_Hans:
|
|
|
62
62
|
template:
|
|
63
63
|
body: 以下字段存在问题:
|
|
64
64
|
header:
|
|
65
|
-
other: '%{count}
|
|
66
|
-
export:
|
|
67
|
-
'false': '
|
|
65
|
+
other: '%{count} 个错误阻止了该 %{model} 的保存。'
|
|
66
|
+
export: 导出
|
|
67
|
+
'false': '假'
|
|
68
68
|
filtered: (Filtered)
|
|
69
69
|
found: 发现了
|
|
70
70
|
future: Future
|
|
71
71
|
group_by: Group by
|
|
72
72
|
grouped_by: Grouped by %{column}
|
|
73
|
-
hide: (
|
|
73
|
+
hide: (隐藏)
|
|
74
74
|
hours: Hours
|
|
75
75
|
human_conditions:
|
|
76
76
|
association: '%{column} = %{value}'
|
|
@@ -81,9 +81,9 @@ zh_Hans:
|
|
|
81
81
|
loading: Loading…
|
|
82
82
|
logical_search: Logical Search
|
|
83
83
|
mark_all_records: Mark all
|
|
84
|
-
maximum:
|
|
85
|
-
minimum:
|
|
86
|
-
minutes:
|
|
84
|
+
maximum: 最大值
|
|
85
|
+
minimum: 最小值
|
|
86
|
+
minutes: 分钟
|
|
87
87
|
months: 月
|
|
88
88
|
nested_for_model: '%{nested_model} for %{parent_model}'
|
|
89
89
|
nested_of_model: '%{nested_model} of %{parent_model}'
|
|
@@ -92,8 +92,8 @@ zh_Hans:
|
|
|
92
92
|
next_week: Next Week
|
|
93
93
|
next_year: Next Year
|
|
94
94
|
no_authorization_for_action: No Authorization for action %{action}
|
|
95
|
-
no_color:
|
|
96
|
-
no_entries:
|
|
95
|
+
no_color: 无颜色
|
|
96
|
+
no_entries: 无条目
|
|
97
97
|
no_group: No group
|
|
98
98
|
no_options: no options
|
|
99
99
|
'null': 'Null'
|
|
@@ -104,8 +104,8 @@ zh_Hans:
|
|
|
104
104
|
pdf: PDF
|
|
105
105
|
prev_month: Last Month
|
|
106
106
|
prev_week: Last Week
|
|
107
|
-
prev_year:
|
|
108
|
-
previous:
|
|
107
|
+
prev_year: 去年
|
|
108
|
+
previous: 上一个
|
|
109
109
|
print: Print
|
|
110
110
|
range: Range
|
|
111
111
|
record_not_saved: Failed to save record cause of an unknown error
|
|
@@ -144,6 +144,7 @@ zh_Hans:
|
|
|
144
144
|
years: Years
|
|
145
145
|
yesterday: Yesterday
|
|
146
146
|
not_null: Not Null
|
|
147
|
+
already_added_model: '%{model} has already been added.'
|
|
147
148
|
date:
|
|
148
149
|
formats:
|
|
149
150
|
month: '%B'
|
|
@@ -35,20 +35,7 @@ module ActiveScaffold::Actions
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def set_outer_joins_for_search(columns) # rubocop:disable Naming/AccessorMethodName
|
|
38
|
-
|
|
39
|
-
outer_joins = []
|
|
40
|
-
columns.each do |column|
|
|
41
|
-
next if column.search_joins.blank?
|
|
42
|
-
|
|
43
|
-
if column.includes.present? && list_columns.include?(column)
|
|
44
|
-
references << (column.search_joins & column.includes)
|
|
45
|
-
outer_joins << (column.search_joins - column.includes)
|
|
46
|
-
else
|
|
47
|
-
outer_joins << column.search_joins
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
active_scaffold_references.concat references.flatten.uniq.compact
|
|
51
|
-
active_scaffold_outer_joins.concat outer_joins.flatten.uniq.compact
|
|
38
|
+
active_scaffold_outer_joins.concat columns.flat_map(&:search_joins).uniq
|
|
52
39
|
end
|
|
53
40
|
|
|
54
41
|
def store_search_params_into_session
|
|
@@ -366,34 +366,29 @@ module ActiveScaffold::Actions
|
|
|
366
366
|
active_scaffold_config.model
|
|
367
367
|
end
|
|
368
368
|
|
|
369
|
-
# Builds search conditions
|
|
369
|
+
# Builds search conditions from query parameters whose names match columns.
|
|
370
|
+
# For example, `contacts/list?company_id=5` matches records with company_id 5.
|
|
371
|
+
# Append `!` to a column name to negate the condition, as in `company_id!=5`.
|
|
372
|
+
# A value containing exactly one `..` defines a range for supported column types, for example
|
|
373
|
+
# `created_at=2025-01-01..2025-12-31`. A missing range endpoint creates an infinite range,
|
|
374
|
+
# such as `created_at=..2025-12-31` or `created_at=2025-01-01..`.
|
|
375
|
+
# Array parameters use the `column[]=value` syntax, for example `company_id[]=5&company_id[]=10`.
|
|
376
|
+
# Negation may also be combined with ranges or arrays, using `not_between` or `not_in`.
|
|
370
377
|
def conditions_from_params
|
|
371
378
|
@conditions_from_params ||= begin
|
|
372
379
|
conditions = [{}]
|
|
373
|
-
supporting_range = %i[date datetime integer decimal float bigint]
|
|
374
380
|
params.except(:controller, :action, :page, :sort, :sort_direction, :format, :id).each do |key, value|
|
|
375
|
-
distinct =
|
|
381
|
+
distinct = key.match?(/!$/)
|
|
376
382
|
column = active_scaffold_config._columns_hash[key.to_s[0..(distinct ? -2 : -1)]]
|
|
377
383
|
next unless column
|
|
378
384
|
|
|
379
385
|
key = column.name.to_sym
|
|
380
|
-
not_string = %i[string text].exclude?(column.type)
|
|
381
386
|
next if active_scaffold_constraints[key]
|
|
382
387
|
next if nested? && nested.param_name == key
|
|
383
388
|
|
|
384
|
-
|
|
385
|
-
value = value.split('..') if range
|
|
386
|
-
value =
|
|
387
|
-
if value.is_a?(Array)
|
|
388
|
-
value.map { |v| v == '' && not_string ? nil : ActiveScaffold::Core.column_type_cast(v, column) }
|
|
389
|
-
elsif value == '' && (not_string || column.null)
|
|
390
|
-
ActiveScaffold::Core.column_type_cast(column.default, column)
|
|
391
|
-
else
|
|
392
|
-
ActiveScaffold::Core.column_type_cast(value, column)
|
|
393
|
-
end
|
|
394
|
-
value = Range.new(*value) if range
|
|
389
|
+
value = condition_value_from_param(value, column, active_scaffold_config.model)
|
|
395
390
|
if distinct
|
|
396
|
-
conditions <<
|
|
391
|
+
conditions << distinct_condition(key, value)
|
|
397
392
|
else
|
|
398
393
|
conditions[0][key] = value
|
|
399
394
|
end
|
|
@@ -402,6 +397,46 @@ module ActiveScaffold::Actions
|
|
|
402
397
|
end
|
|
403
398
|
end
|
|
404
399
|
|
|
400
|
+
def distinct_condition(key, value)
|
|
401
|
+
attribute = active_scaffold_config.model.arel_table[key]
|
|
402
|
+
case value
|
|
403
|
+
when Array
|
|
404
|
+
attribute.not_in(value)
|
|
405
|
+
when Range
|
|
406
|
+
attribute.not_between(value)
|
|
407
|
+
else
|
|
408
|
+
attribute.not_eq(value)
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def condition_value_from_param(value, column, model)
|
|
413
|
+
range = range_from_param(value, column)
|
|
414
|
+
value = range.captures if range
|
|
415
|
+
not_string = %i[string text].exclude?(column.type)
|
|
416
|
+
value =
|
|
417
|
+
if value.is_a?(Array)
|
|
418
|
+
value.map { |item| conditional_cast_for_column(item, column, not_string, model) }
|
|
419
|
+
elsif value == '' && (not_string || column.null)
|
|
420
|
+
ActiveScaffold::Core.column_type_cast(column.default, column, model)
|
|
421
|
+
else
|
|
422
|
+
ActiveScaffold::Core.column_type_cast(value, column, model)
|
|
423
|
+
end
|
|
424
|
+
range ? Range.new(*value) : value
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def conditional_cast_for_column(value, column, not_string, model)
|
|
428
|
+
return if value == '' && not_string
|
|
429
|
+
|
|
430
|
+
ActiveScaffold::Core.column_type_cast(value, column, model)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def range_from_param(value, column)
|
|
434
|
+
supporting_range = %i[date datetime timestamp timestamptz integer decimal float bigint]
|
|
435
|
+
return unless supporting_range.include?(column.type) && value.is_a?(String) && value.scan('..').size == 1
|
|
436
|
+
|
|
437
|
+
value.match(/\A(.*)\.\.(.*)\z/)
|
|
438
|
+
end
|
|
439
|
+
|
|
405
440
|
def empty_model
|
|
406
441
|
relation = beginning_of_chain
|
|
407
442
|
if nested? && nested.plural_association? && nested.match_model?(active_scaffold_config.model)
|
|
@@ -437,6 +472,10 @@ module ActiveScaffold::Actions
|
|
|
437
472
|
end
|
|
438
473
|
|
|
439
474
|
def get_row(crud_type_or_security_options = :read)
|
|
475
|
+
get_record(crud_type_or_security_options)
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def get_record(crud_type_or_security_options = :read)
|
|
440
479
|
klass = filtered_query
|
|
441
480
|
klass = klass.preload(active_scaffold_preload) unless active_scaffold_config.mongoid?
|
|
442
481
|
@record = find_if_allowed(params[:id], crud_type_or_security_options, klass)
|
|
@@ -110,8 +110,8 @@ module ActiveScaffold::Actions
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
def grouped_columns_calculations
|
|
113
|
-
@grouped_columns_calculations ||= list_columns[1..].
|
|
114
|
-
|
|
113
|
+
@grouped_columns_calculations ||= list_columns[1..].to_h do |c|
|
|
114
|
+
[c.name, calculation_for_group_search(c)]
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|
|
@@ -172,11 +172,13 @@ module ActiveScaffold::Actions
|
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
def count_query_for_column(column, records)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
query =
|
|
176
|
+
if count_on_association_class?(column)
|
|
177
|
+
count_query_on_association_class(column, records)
|
|
178
|
+
else
|
|
179
|
+
count_query_with_join(column, records)
|
|
180
|
+
end
|
|
181
|
+
query.unscope(:order)
|
|
180
182
|
end
|
|
181
183
|
|
|
182
184
|
def count_query_on_association_class(column, records)
|
|
@@ -207,8 +209,8 @@ module ActiveScaffold::Actions
|
|
|
207
209
|
end
|
|
208
210
|
group = {_id: "$#{column.association.foreign_key}", count: {'$sum' => 1}}
|
|
209
211
|
query = column.association.klass.collection.aggregate([{'$match' => matches}, {'$group' => group}])
|
|
210
|
-
query.
|
|
211
|
-
|
|
212
|
+
query.to_h do |row|
|
|
213
|
+
[row['_id'], row['count']]
|
|
212
214
|
end
|
|
213
215
|
end
|
|
214
216
|
|
|
@@ -281,24 +283,44 @@ module ActiveScaffold::Actions
|
|
|
281
283
|
active_scaffold_config.list.refresh_with_header = true
|
|
282
284
|
|
|
283
285
|
active_scaffold_config.list.filters.inject(query) do |q, filter|
|
|
284
|
-
next q unless filter.security_method.nil? || send(filter.security_method)
|
|
285
|
-
|
|
286
286
|
default_option = filter[filter.default_option]
|
|
287
|
-
|
|
287
|
+
filter_option = params[filter.name] ? filter[params[filter.name]] : default_option
|
|
288
|
+
unless filter_allowed?(filter)
|
|
289
|
+
track_filter filter_option, default_option, :disallowed if params.key?(filter.name)
|
|
290
|
+
next q
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
apply_filter q, filter_option, default_option
|
|
288
294
|
end
|
|
289
295
|
end
|
|
290
296
|
|
|
297
|
+
def filter_allowed?(filter)
|
|
298
|
+
!filter.security_method_set? || send(filter.security_method)
|
|
299
|
+
end
|
|
300
|
+
|
|
291
301
|
def apply_filter(query, filter_option, default_option)
|
|
292
|
-
|
|
302
|
+
if filter_option.nil? || !filter_allowed?(filter_option)
|
|
303
|
+
track_filter filter_option, default_option, filter_option ? :disallowed : :unknown
|
|
304
|
+
return query
|
|
305
|
+
end
|
|
293
306
|
|
|
294
|
-
|
|
295
|
-
@applied_filters << filter_option unless filter_option == default_option
|
|
307
|
+
track_filter filter_option, default_option, :applied
|
|
296
308
|
case filter_option.conditions
|
|
297
309
|
when Proc then instance_exec query, &filter_option.conditions
|
|
298
310
|
else query.where(filter_option.conditions)
|
|
299
311
|
end
|
|
300
312
|
end
|
|
301
313
|
|
|
314
|
+
def track_filter(filter_option, default_option, status)
|
|
315
|
+
@filter_states ||= {}
|
|
316
|
+
@filter_states[default_option.filter_name] = {
|
|
317
|
+
option: filter_option,
|
|
318
|
+
status: status,
|
|
319
|
+
default: filter_option == default_option,
|
|
320
|
+
requested: params.key?(default_option.filter_name)
|
|
321
|
+
}
|
|
322
|
+
end
|
|
323
|
+
|
|
302
324
|
def scoped_query
|
|
303
325
|
@scoped_query ||= begin
|
|
304
326
|
do_search if respond_to? :do_search, true
|
|
@@ -179,7 +179,9 @@ module ActiveScaffold::Actions::Nested
|
|
|
179
179
|
|
|
180
180
|
def add_existing_respond_to_html
|
|
181
181
|
if successful?
|
|
182
|
-
|
|
182
|
+
unless @association_already_exists
|
|
183
|
+
flash[:info] = as_(:created_model, model: ERB::Util.h(@record.to_label))
|
|
184
|
+
end
|
|
183
185
|
return_to_main
|
|
184
186
|
else
|
|
185
187
|
render action: 'add_existing_form'
|
|
@@ -250,12 +252,34 @@ module ActiveScaffold::Actions::Nested
|
|
|
250
252
|
def do_add_existing
|
|
251
253
|
parent_record = nested_parent_record(:update)
|
|
252
254
|
@record = active_scaffold_config.model.find(params[:associated_id])
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
255
|
+
return false unless parent_record && @record
|
|
256
|
+
|
|
257
|
+
association = parent_record.send(nested.association.name)
|
|
258
|
+
added = add_existing_to_association(association)
|
|
259
|
+
return true if @association_already_exists
|
|
260
|
+
|
|
261
|
+
self.successful = false unless added
|
|
262
|
+
parent_record.save if successful?
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def add_existing_to_association(association)
|
|
266
|
+
return association_already_exists if association.include?(@record)
|
|
267
|
+
|
|
268
|
+
association << @record
|
|
269
|
+
rescue ActiveRecord::RecordNotUnique
|
|
270
|
+
# Handles concurrent submissions. Only suppress the exception when the
|
|
271
|
+
# requested association now exists; otherwise it was a different
|
|
272
|
+
# uniqueness violation.
|
|
273
|
+
association.reset
|
|
274
|
+
raise unless association.include?(@record)
|
|
275
|
+
|
|
276
|
+
association_already_exists
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def association_already_exists
|
|
280
|
+
@association_already_exists = true
|
|
281
|
+
self.successful = true
|
|
282
|
+
flash[:warning] = as_(:already_added_model, model: ERB::Util.h(@record.to_label))
|
|
259
283
|
end
|
|
260
284
|
|
|
261
285
|
def do_destroy_existing
|
|
@@ -48,7 +48,7 @@ module ActiveScaffold::Actions
|
|
|
48
48
|
# May be overridden to customize show routine
|
|
49
49
|
def do_show
|
|
50
50
|
set_includes_for_columns(:show) if active_scaffold_config.actions.include? :list
|
|
51
|
-
|
|
51
|
+
get_record
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# The default security delegates to ActiveRecordPermissions.
|
|
@@ -119,7 +119,7 @@ module ActiveScaffold
|
|
|
119
119
|
key = "#{options[:crud_type]}##{options[:column]}##{options[:action]}"
|
|
120
120
|
if is_a? Class
|
|
121
121
|
self.class_security_methods ||= {}
|
|
122
|
-
|
|
122
|
+
class_security_methods[key] ||= authorized_for_methods(options)
|
|
123
123
|
else
|
|
124
124
|
self.class.instance_security_methods ||= {}
|
|
125
125
|
self.class.instance_security_methods[key] ||= authorized_for_methods(options)
|
|
@@ -267,7 +267,8 @@ module ActiveScaffold
|
|
|
267
267
|
elsif klass.authorized_for?(crud_type: :create)
|
|
268
268
|
association = parent_column.association
|
|
269
269
|
record = klass.new
|
|
270
|
-
if association.reverse_association&.belongs_to? &&
|
|
270
|
+
if association.reverse_association&.belongs_to? && association.singular? && current.nil?
|
|
271
|
+
# assign belongs_to on the new record, for has_one associations
|
|
271
272
|
record.send(:"#{parent_column.association.reverse}=", parent_record)
|
|
272
273
|
end
|
|
273
274
|
record
|
|
@@ -32,6 +32,13 @@ module ActiveScaffold::Bridges
|
|
|
32
32
|
end.join
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
def self.date_options_by_locale
|
|
36
|
+
I18n.available_locales.filter_map do |locale|
|
|
37
|
+
locale_date_options = date_options(locale)
|
|
38
|
+
[locale.to_s, locale_date_options] if locale_date_options
|
|
39
|
+
end.to_h
|
|
40
|
+
end
|
|
41
|
+
|
|
35
42
|
def self.date_options(locale)
|
|
36
43
|
date_picker_options = {
|
|
37
44
|
closeText: as_(:close),
|
|
@@ -76,6 +83,13 @@ module ActiveScaffold::Bridges
|
|
|
76
83
|
end.join
|
|
77
84
|
end
|
|
78
85
|
|
|
86
|
+
def self.datetime_options_by_locale
|
|
87
|
+
I18n.available_locales.filter_map do |locale|
|
|
88
|
+
locale_datetime_options = datetime_options(locale)
|
|
89
|
+
[locale.to_s, locale_datetime_options] if locale_datetime_options
|
|
90
|
+
end.to_h
|
|
91
|
+
end
|
|
92
|
+
|
|
79
93
|
def self.datetime_options(locale)
|
|
80
94
|
rails_time_format = I18n.translate! 'time.formats.picker', locale: locale, default: '%a, %d %b %Y %H:%M:%S'
|
|
81
95
|
datetime_picker_options = {
|
|
@@ -30,5 +30,15 @@ module ActiveScaffold::Bridges
|
|
|
30
30
|
}
|
|
31
31
|
});\n"
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
# Data-only equivalent of .localization, consumed directly by load.js without eval,
|
|
35
|
+
# so it works under a CSP that disallows 'unsafe-eval'.
|
|
36
|
+
def self.localization_data
|
|
37
|
+
{
|
|
38
|
+
locale: ::I18n.locale.to_s,
|
|
39
|
+
datepickerRegional: Helper.date_options_by_locale,
|
|
40
|
+
timepickerRegional: Helper.datetime_options_by_locale
|
|
41
|
+
}
|
|
42
|
+
end
|
|
33
43
|
end
|
|
34
44
|
end
|
|
@@ -33,6 +33,7 @@ module ActiveScaffold::Config
|
|
|
33
33
|
@messages_above_header = self.class.messages_above_header
|
|
34
34
|
@auto_select_columns = self.class.auto_select_columns
|
|
35
35
|
@refresh_with_header = self.class.refresh_with_header
|
|
36
|
+
@local_sorting = self.class.local_sorting
|
|
36
37
|
@calculate_etag = self.class.calculate_etag
|
|
37
38
|
end
|
|
38
39
|
|
|
@@ -42,6 +43,10 @@ module ActiveScaffold::Config
|
|
|
42
43
|
cattr_accessor :refresh_with_header, instance_accessor: false
|
|
43
44
|
@@refresh_with_header = false
|
|
44
45
|
|
|
46
|
+
# enable sorting locally in the browser, when there is only one page
|
|
47
|
+
cattr_accessor :local_sorting, instance_accessor: false
|
|
48
|
+
@@local_sorting = false
|
|
49
|
+
|
|
45
50
|
# how many records to show per page
|
|
46
51
|
cattr_accessor :per_page, instance_accessor: false
|
|
47
52
|
@@per_page = 15
|
|
@@ -134,6 +139,9 @@ module ActiveScaffold::Config
|
|
|
134
139
|
# include list header on refresh
|
|
135
140
|
attr_accessor :refresh_with_header
|
|
136
141
|
|
|
142
|
+
# enable sorting locally in the browser, when there is only one page
|
|
143
|
+
attr_accessor :local_sorting
|
|
144
|
+
|
|
137
145
|
# how many rows to show at once
|
|
138
146
|
attr_accessor :per_page
|
|
139
147
|
|
|
@@ -265,7 +273,8 @@ module ActiveScaffold::Config
|
|
|
265
273
|
user_attr :page_links_inner_window, :page_links_outer_window, :refresh_with_header, :empty_field_text,
|
|
266
274
|
:association_join_text, :messages_above_header, :wrap_tag, :auto_select_columns, :calculate_etag,
|
|
267
275
|
:no_entries_message, :filtered_message, :show_search_reset, :always_show_create, :always_show_search,
|
|
268
|
-
:hide_nested_column, :pagination, :auto_pagination, :filter_human_message, :show_filter_reset
|
|
276
|
+
:hide_nested_column, :pagination, :auto_pagination, :filter_human_message, :show_filter_reset,
|
|
277
|
+
:local_sorting
|
|
269
278
|
|
|
270
279
|
def initialize(conf, storage, params)
|
|
271
280
|
super(conf, storage, params, :list)
|
data/lib/active_scaffold/core.rb
CHANGED
|
@@ -260,11 +260,11 @@ module ActiveScaffold
|
|
|
260
260
|
raise ActiveScaffold::ControllerNotFound, "Could not find #{error_message.join(' or ')}", caller
|
|
261
261
|
end
|
|
262
262
|
|
|
263
|
-
def self.column_type_cast(value, column)
|
|
263
|
+
def self.column_type_cast(value, column, model)
|
|
264
264
|
if defined?(ActiveRecord) && column.is_a?(ActiveRecord::ConnectionAdapters::Column)
|
|
265
|
-
active_record_column_type_cast(value, column)
|
|
265
|
+
active_record_column_type_cast(value, column, model)
|
|
266
266
|
elsif defined?(ActiveModel) && column.is_a?(ActiveModel::Attribute)
|
|
267
|
-
active_record_column_type_cast(value, column.type)
|
|
267
|
+
active_record_column_type_cast(value, column.type, model)
|
|
268
268
|
elsif defined?(Mongoid) && column.is_a?(Mongoid::Fields::Standard)
|
|
269
269
|
mongoid_column_type_cast(value, column)
|
|
270
270
|
else
|
|
@@ -278,10 +278,13 @@ module ActiveScaffold
|
|
|
278
278
|
column.type.evolve value
|
|
279
279
|
end
|
|
280
280
|
|
|
281
|
-
def self.active_record_column_type_cast(value, column_or_type)
|
|
282
|
-
return Time.zone.at(value.to_i) if %i[time datetime].include?(column_or_type.type) && value =~ /\A\d+\z/
|
|
281
|
+
def self.active_record_column_type_cast(value, column_or_type, model)
|
|
282
|
+
return Time.zone.at(value.to_i) if %i[time datetime timestamp timestamptz].include?(column_or_type.type) && value =~ /\A\d+\z/
|
|
283
283
|
|
|
284
|
-
cast_type = column_or_type
|
|
284
|
+
cast_type = column_or_type
|
|
285
|
+
if cast_type.is_a?(ActiveRecord::ConnectionAdapters::Column)
|
|
286
|
+
cast_type = model.type_for_attribute(column_or_type.name)
|
|
287
|
+
end
|
|
285
288
|
cast_type ? cast_type.cast(value) : value
|
|
286
289
|
end
|
|
287
290
|
end
|