active_scaffold 4.3.2 → 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 +19 -0
- data/README.md +1 -1
- data/app/assets/javascripts/active_scaffold/active_scaffold.js +98 -11
- 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/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 +3 -5
- 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/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
|
|
@@ -41,6 +41,9 @@ module ActiveScaffold::DataStructures
|
|
|
41
41
|
# a textual description of the column and its contents. this will be displayed with any associated form input widget, so you may want to consider adding a content example.
|
|
42
42
|
attr_writer :description
|
|
43
43
|
|
|
44
|
+
# enable it if local sorting shouldn't be used for this column at all
|
|
45
|
+
attr_writer :skip_local_sorting
|
|
46
|
+
|
|
44
47
|
attr_reader :update_columns
|
|
45
48
|
|
|
46
49
|
# send all the form instead of only new value when this column changes
|
|
@@ -107,6 +110,9 @@ module ActiveScaffold::DataStructures
|
|
|
107
110
|
|
|
108
111
|
# define the fields to use with logical search
|
|
109
112
|
attr_accessor :logical_search
|
|
113
|
+
|
|
114
|
+
# SQL operator used for text searches. Defaults according to the adapter when nil.
|
|
115
|
+
attr_accessor :like_operator
|
|
110
116
|
end
|
|
111
117
|
|
|
112
118
|
def inplace_edit=(value)
|
|
@@ -125,6 +131,11 @@ module ActiveScaffold::DataStructures
|
|
|
125
131
|
!(calculate == false || calculate.nil?)
|
|
126
132
|
end
|
|
127
133
|
|
|
134
|
+
# if local sorting shouldn't be used for this column at all
|
|
135
|
+
def skip_local_sorting?
|
|
136
|
+
@skip_local_sorting == true
|
|
137
|
+
end
|
|
138
|
+
|
|
128
139
|
def required?(action = nil)
|
|
129
140
|
if action && @required
|
|
130
141
|
@required == true || @required.include?(action)
|
|
@@ -345,7 +356,14 @@ module ActiveScaffold::DataStructures
|
|
|
345
356
|
|
|
346
357
|
def search_sql
|
|
347
358
|
initialize_search_sql if @search_sql == true
|
|
348
|
-
@search_sql
|
|
359
|
+
return @search_sql unless @search_sql.is_a?(Array) && active_record?
|
|
360
|
+
|
|
361
|
+
connection = active_record_class.connection
|
|
362
|
+
@search_sql.map do |sql|
|
|
363
|
+
next sql unless sql.is_a?(Symbol)
|
|
364
|
+
|
|
365
|
+
"#{connection.quote_table_name(active_record_class.table_name)}.#{connection.quote_column_name(sql)}"
|
|
366
|
+
end
|
|
349
367
|
end
|
|
350
368
|
|
|
351
369
|
def searchable?
|
|
@@ -388,7 +406,7 @@ module ActiveScaffold::DataStructures
|
|
|
388
406
|
def initialize_sort
|
|
389
407
|
self.sort =
|
|
390
408
|
if column && !tableless?
|
|
391
|
-
{sql:
|
|
409
|
+
{sql: name}
|
|
392
410
|
else
|
|
393
411
|
false
|
|
394
412
|
end
|
|
@@ -398,7 +416,7 @@ module ActiveScaffold::DataStructures
|
|
|
398
416
|
self.search_sql =
|
|
399
417
|
unless virtual?
|
|
400
418
|
if association.nil?
|
|
401
|
-
|
|
419
|
+
name unless tableless?
|
|
402
420
|
elsif association.allow_join?
|
|
403
421
|
[association.quoted_table_name, association.quoted_primary_key].join('.') unless association.klass < ActiveScaffold::Tableless
|
|
404
422
|
end
|
|
@@ -517,14 +535,14 @@ module ActiveScaffold::DataStructures
|
|
|
517
535
|
def initialize(name, active_record_class, delegated_association = nil) # :nodoc:
|
|
518
536
|
@name = name.to_sym
|
|
519
537
|
@active_record_class = active_record_class
|
|
520
|
-
@column = _columns_hash[name.to_s]
|
|
538
|
+
@column = db_column = _columns_hash[name.to_s]
|
|
521
539
|
if @column.nil? && active_record? && active_record_class._default_attributes.key?(name.to_s)
|
|
522
540
|
@column = active_record_class._default_attributes[name.to_s]
|
|
523
541
|
end
|
|
524
542
|
@disable_on_update_column = true
|
|
525
543
|
@db_default_value = ActiveScaffold::OrmChecks.default_value active_record_class, name if @column
|
|
526
544
|
@delegated_association = delegated_association
|
|
527
|
-
@cache_key = [@active_record_class.name, name].compact.
|
|
545
|
+
@cache_key = [@active_record_class.name, name].compact.join('#')
|
|
528
546
|
setup_association_info
|
|
529
547
|
|
|
530
548
|
@link = nil
|
|
@@ -538,6 +556,9 @@ module ActiveScaffold::DataStructures
|
|
|
538
556
|
@select_columns = default_select_columns
|
|
539
557
|
|
|
540
558
|
@text = @column.nil? || [:string, :text, :citext, String].include?(column_type)
|
|
559
|
+
if active_record? && db_column&.collation.present? && active_record_class.connection.adapter_name.in?(%w[PostgreSQL PostGIS])
|
|
560
|
+
@like_operator = 'LIKE'
|
|
561
|
+
end
|
|
541
562
|
@number = false
|
|
542
563
|
setup_defaults_for_column if @column
|
|
543
564
|
@allow_add_existing = true
|
|
@@ -545,7 +566,7 @@ module ActiveScaffold::DataStructures
|
|
|
545
566
|
|
|
546
567
|
self.includes = [association.name] if association&.allow_join?
|
|
547
568
|
if delegated_association
|
|
548
|
-
self.includes = includes ? [delegated_association.name => includes] : [delegated_association.name]
|
|
569
|
+
self.includes = includes ? [{delegated_association.name => includes}] : [delegated_association.name]
|
|
549
570
|
end
|
|
550
571
|
self.subform_includes = true if association
|
|
551
572
|
|