active_scaffold 3.7.0 → 3.7.1
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 +20 -0
- data/README.md +2 -0
- data/app/assets/javascripts/jquery/active_scaffold.js +68 -62
- data/app/assets/stylesheets/active_scaffold_layout.css +1 -1
- data/app/views/active_scaffold_overrides/_form_association_record.html.erb +2 -1
- data/app/views/active_scaffold_overrides/_render_field.js.erb +9 -6
- data/config/locales/de.yml +6 -3
- data/config/locales/en.yml +3 -0
- data/config/locales/es.yml +3 -0
- data/config/locales/fr.yml +9 -6
- data/config/locales/hu.yml +20 -17
- data/config/locales/ja.yml +25 -22
- data/config/locales/ru.yml +17 -14
- data/lib/active_scaffold/actions/update.rb +3 -3
- data/lib/active_scaffold/attribute_params.rb +7 -17
- data/lib/active_scaffold/bridges/active_storage/form_ui.rb +6 -6
- data/lib/active_scaffold/bridges/active_storage/list_ui.rb +7 -7
- data/lib/active_scaffold/bridges/ancestry/ancestry_bridge.rb +2 -2
- data/lib/active_scaffold/bridges/calendar_date_select/as_cds_bridge.rb +12 -14
- data/lib/active_scaffold/bridges/carrierwave/form_ui.rb +2 -2
- data/lib/active_scaffold/bridges/carrierwave/list_ui.rb +1 -1
- data/lib/active_scaffold/bridges/chosen/helpers.rb +10 -10
- data/lib/active_scaffold/bridges/country_select/country_select_bridge_helper.rb +7 -7
- data/lib/active_scaffold/bridges/date_picker/ext.rb +20 -9
- data/lib/active_scaffold/bridges/date_picker/helper.rb +5 -5
- data/lib/active_scaffold/bridges/date_picker.rb +2 -0
- data/lib/active_scaffold/bridges/dragonfly/form_ui.rb +3 -3
- data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +5 -5
- data/lib/active_scaffold/bridges/file_column/form_ui.rb +1 -1
- data/lib/active_scaffold/bridges/file_column/list_ui.rb +3 -3
- data/lib/active_scaffold/bridges/file_column/test/functional/file_column_keep_test.rb +1 -1
- data/lib/active_scaffold/bridges/paperclip/form_ui.rb +3 -3
- data/lib/active_scaffold/bridges/paperclip/list_ui.rb +1 -1
- data/lib/active_scaffold/bridges/record_select/helpers.rb +15 -15
- data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +6 -6
- data/lib/active_scaffold/bridges/usa_state_select/usa_state_select_helper.rb +5 -5
- data/lib/active_scaffold/bridges.rb +0 -3
- data/lib/active_scaffold/constraints.rb +22 -7
- data/lib/active_scaffold/core.rb +5 -3
- data/lib/active_scaffold/data_structures/column.rb +108 -23
- data/lib/active_scaffold/engine.rb +15 -0
- data/lib/active_scaffold/extensions/routing_mapper.rb +1 -0
- data/lib/active_scaffold/finder.rb +142 -27
- data/lib/active_scaffold/helpers/controller_helpers.rb +9 -4
- data/lib/active_scaffold/helpers/form_column_helpers.rb +114 -94
- data/lib/active_scaffold/helpers/human_condition_helpers.rb +48 -14
- data/lib/active_scaffold/helpers/list_column_helpers.rb +34 -18
- data/lib/active_scaffold/helpers/search_column_helpers.rb +131 -55
- data/lib/active_scaffold/helpers/show_column_helpers.rb +6 -6
- data/lib/active_scaffold/orm_checks.rb +21 -1
- data/lib/active_scaffold/version.rb +1 -1
- data/lib/active_scaffold.rb +3 -2
- data/test/bridges/date_picker_test.rb +3 -2
- data/test/bridges/paperclip_test.rb +3 -2
- data/test/bridges/tiny_mce_test.rb +4 -2
- data/test/helpers/form_column_helpers_test.rb +7 -5
- data/test/helpers/search_column_helpers_test.rb +2 -1
- data/test/misc/constraints_test.rb +1 -0
- data/test/misc/finder_test.rb +38 -0
- metadata +2 -6
- data/config/brakeman.ignore +0 -26
- data/config/brakeman.yml +0 -3
- data/config/i18n-tasks.yml +0 -121
- data/lib/active_scaffold/bridges/shared/date_bridge.rb +0 -221
@@ -11,13 +11,27 @@ module ActiveScaffold
|
|
11
11
|
@active_scaffold_constraints ||= active_scaffold_embedded_params[:constraints] || {}
|
12
12
|
end
|
13
13
|
|
14
|
+
def register_constraint?(column_name, value)
|
15
|
+
if params_hash?(value)
|
16
|
+
false
|
17
|
+
elsif value.is_a?(Array)
|
18
|
+
column = active_scaffold_config.columns[column_name]
|
19
|
+
column && value.size > (column.association&.polymorphic? ? 2 : 1)
|
20
|
+
else
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
14
25
|
# For each enabled action, adds the constrained columns to the ActionColumns object (if it exists).
|
15
26
|
# This lets the ActionColumns object skip constrained columns.
|
16
27
|
#
|
17
|
-
# If the constraint value is a Hash, then we assume the constraint is a multi-level association constraint
|
28
|
+
# If the constraint value is a Hash, then we assume the constraint is a multi-level association constraint
|
29
|
+
# (the reverse of a has_many :through) and we do NOT register the constraint column.
|
30
|
+
# If the constraint value is an Array, or Array with more than 2 items for polymorphic column,
|
31
|
+
# we do NOT register the constraint column, as records will have different values in the column.
|
18
32
|
def register_constraints_with_action_columns(constrained_fields = nil)
|
19
33
|
constrained_fields ||= []
|
20
|
-
constrained_fields |= active_scaffold_constraints.
|
34
|
+
constrained_fields |= active_scaffold_constraints.select { |k, v| register_constraint?(k, v) }.keys.collect(&:to_sym)
|
21
35
|
exclude_actions = []
|
22
36
|
%i[list update].each do |action_name|
|
23
37
|
if active_scaffold_config.actions.include? action_name
|
@@ -111,10 +125,10 @@ module ActiveScaffold
|
|
111
125
|
value = association.klass.find(value).send(association.primary_key) if association.primary_key
|
112
126
|
|
113
127
|
if association.polymorphic?
|
114
|
-
unless value.is_a?(Array) && value.size
|
128
|
+
unless value.is_a?(Array) && value.size >= 2
|
115
129
|
raise ActiveScaffold::MalformedConstraint, polymorphic_constraint_error(association), caller
|
116
130
|
end
|
117
|
-
condition = {table => {association.foreign_type => value[0], field => value[1]}}
|
131
|
+
condition = {table => {association.foreign_type => value[0], field => value.size == 2 ? value[1] : value[1..-1]}}
|
118
132
|
else
|
119
133
|
condition = {table => {field.to_s => value}}
|
120
134
|
end
|
@@ -149,11 +163,12 @@ module ActiveScaffold
|
|
149
163
|
if column.association.collection?
|
150
164
|
record.send(k.to_s).send(:<<, column.association.klass.find(v))
|
151
165
|
elsif column.association.polymorphic?
|
152
|
-
unless v.is_a?(Array) && v.size
|
166
|
+
unless v.is_a?(Array) && v.size >= 2
|
153
167
|
raise ActiveScaffold::MalformedConstraint, polymorphic_constraint_error(column.association), caller
|
154
168
|
end
|
155
|
-
record.send("#{k}=", v[0].constantize.find(v[1]))
|
156
|
-
elsif !column.association.source_reflection&.options&.include?(:through) # regular singular association, or one-level through association
|
169
|
+
record.send("#{k}=", v[0].constantize.find(v[1])) if v.size == 2
|
170
|
+
elsif !column.association.source_reflection&.options&.include?(:through) && # regular singular association, or one-level through association
|
171
|
+
!v.is_a?(Array)
|
157
172
|
record.send("#{k}=", column.association.klass.find(v))
|
158
173
|
|
159
174
|
# setting the belongs_to side of a has_one isn't safe. if the has_one was already
|
data/lib/active_scaffold/core.rb
CHANGED
@@ -257,6 +257,8 @@ module ActiveScaffold
|
|
257
257
|
def self.column_type_cast(value, column)
|
258
258
|
if defined?(ActiveRecord) && column.is_a?(ActiveRecord::ConnectionAdapters::Column)
|
259
259
|
active_record_column_type_cast(value, column)
|
260
|
+
elsif defined?(ActiveModel) && column.is_a?(ActiveModel::Attribute)
|
261
|
+
active_record_column_type_cast(value, column.type)
|
260
262
|
elsif defined?(Mongoid) && column.is_a?(Mongoid::Fields::Standard)
|
261
263
|
mongoid_column_type_cast(value, column)
|
262
264
|
else
|
@@ -269,9 +271,9 @@ module ActiveScaffold
|
|
269
271
|
column.type.evolve value
|
270
272
|
end
|
271
273
|
|
272
|
-
def self.active_record_column_type_cast(value,
|
273
|
-
return Time.zone.at(value.to_i) if value =~ /\A\d+\z/ && %i[time datetime].include?(
|
274
|
-
cast_type = ActiveRecord::Type.lookup
|
274
|
+
def self.active_record_column_type_cast(value, column_or_type)
|
275
|
+
return Time.zone.at(value.to_i) if value =~ /\A\d+\z/ && %i[time datetime].include?(column_or_type.type)
|
276
|
+
cast_type = column_or_type.is_a?(ActiveRecord::ConnectionAdapters::Column) ? ActiveRecord::Type.lookup(column_or_type.type) : column_or_type
|
275
277
|
cast_type ? cast_type.cast(value) : value
|
276
278
|
end
|
277
279
|
end
|
@@ -144,24 +144,58 @@ module ActiveScaffold::DataStructures
|
|
144
144
|
# supported options:
|
145
145
|
# * for association columns
|
146
146
|
# * :select - displays a simple <select> or a collection of checkboxes to (dis)associate records
|
147
|
-
|
147
|
+
attr_reader :form_ui
|
148
148
|
|
149
|
-
|
149
|
+
attr_reader :form_ui_options
|
150
|
+
|
151
|
+
# value must be a Symbol, or an Array of form_ui and options hash which will be used with form_ui only
|
152
|
+
def form_ui=(value)
|
153
|
+
check_valid_action_ui_params(value)
|
154
|
+
@form_ui, @form_ui_options = *value
|
155
|
+
end
|
156
|
+
|
157
|
+
# value must be a Symbol, or an Array of list_ui and options hash which will be used with list_ui only
|
158
|
+
def list_ui=(value)
|
159
|
+
check_valid_action_ui_params(value)
|
160
|
+
@list_ui, @list_ui_options = *value
|
161
|
+
end
|
150
162
|
|
151
163
|
def list_ui
|
152
164
|
@list_ui || form_ui
|
153
165
|
end
|
154
166
|
|
155
|
-
|
167
|
+
def list_ui_options
|
168
|
+
@list_ui ? @list_ui_options : form_ui_options
|
169
|
+
end
|
170
|
+
|
171
|
+
# value must be a Symbol, or an Array of show_ui and options hash which will be used with show_ui only
|
172
|
+
def show_ui=(value)
|
173
|
+
check_valid_action_ui_params(value)
|
174
|
+
@show_ui, @show_ui_options = *value
|
175
|
+
end
|
176
|
+
|
156
177
|
def show_ui
|
157
178
|
@show_ui || list_ui
|
158
179
|
end
|
159
180
|
|
160
|
-
|
181
|
+
def show_ui_options
|
182
|
+
@show_ui ? @show_ui_options : list_ui_options
|
183
|
+
end
|
184
|
+
|
185
|
+
# value must be a Symbol, or an Array of search_ui and options hash which will be used with search_ui only
|
186
|
+
def search_ui=(value)
|
187
|
+
check_valid_action_ui_params(value)
|
188
|
+
@search_ui, @search_ui_options = *value
|
189
|
+
end
|
190
|
+
|
161
191
|
def search_ui
|
162
192
|
@search_ui || @form_ui || (:select if association && !association.polymorphic?)
|
163
193
|
end
|
164
194
|
|
195
|
+
def search_ui_options
|
196
|
+
@search_ui ? @search_ui_options : form_ui_options
|
197
|
+
end
|
198
|
+
|
165
199
|
# a place to store dev's column specific options
|
166
200
|
attr_writer :options
|
167
201
|
def options
|
@@ -340,6 +374,9 @@ module ActiveScaffold::DataStructures
|
|
340
374
|
@name = name.to_sym
|
341
375
|
@active_record_class = active_record_class
|
342
376
|
@column = _columns_hash[name.to_s]
|
377
|
+
if @column.nil? && active_record? && active_record_class._default_attributes.key?(name.to_s)
|
378
|
+
@column = active_record_class._default_attributes[name.to_s]
|
379
|
+
end
|
343
380
|
@delegated_association = delegated_association
|
344
381
|
@cache_key = [@active_record_class.name, name].compact.map(&:to_s).join('#')
|
345
382
|
setup_association_info
|
@@ -356,22 +393,7 @@ module ActiveScaffold::DataStructures
|
|
356
393
|
|
357
394
|
@text = @column.nil? || [:string, :text, :citext, String].include?(column_type)
|
358
395
|
@number = false
|
359
|
-
if @column
|
360
|
-
if active_record_class.respond_to?(:defined_enums) && active_record_class.defined_enums[name.to_s]
|
361
|
-
@form_ui = :select
|
362
|
-
@options = {:options => active_record_class.send(name.to_s.pluralize).keys.map(&:to_sym)}
|
363
|
-
elsif column_number?
|
364
|
-
@number = true
|
365
|
-
@form_ui = :number
|
366
|
-
@options = {:format => :i18n_number}
|
367
|
-
else
|
368
|
-
@form_ui =
|
369
|
-
case @column.type
|
370
|
-
when :boolean then :checkbox
|
371
|
-
when :text then :textarea
|
372
|
-
end
|
373
|
-
end
|
374
|
-
end
|
396
|
+
setup_defaults_for_column if @column
|
375
397
|
@allow_add_existing = true
|
376
398
|
@form_ui = self.class.association_form_ui if @association && self.class.association_form_ui
|
377
399
|
|
@@ -396,7 +418,7 @@ module ActiveScaffold::DataStructures
|
|
396
418
|
# just the field (not table.field)
|
397
419
|
def field_name
|
398
420
|
return nil if virtual?
|
399
|
-
@field_name ||= column ? quoted_field_name(column.name) : association.foreign_key
|
421
|
+
@field_name ||= column ? quoted_field_name(column.name) : quoted_field_name(association.foreign_key)
|
400
422
|
end
|
401
423
|
|
402
424
|
def <=>(other)
|
@@ -430,7 +452,22 @@ module ActiveScaffold::DataStructures
|
|
430
452
|
end
|
431
453
|
|
432
454
|
def default_for_empty_value
|
433
|
-
|
455
|
+
return nil unless column
|
456
|
+
if column.is_a?(ActiveModel::Attribute)
|
457
|
+
column.value
|
458
|
+
elsif active_record?
|
459
|
+
null? ? nil : column.default
|
460
|
+
elsif mongoid?
|
461
|
+
column.default_val
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
def null?
|
466
|
+
if active_record? && !column.is_a?(ActiveModel::Attribute)
|
467
|
+
column&.null
|
468
|
+
else
|
469
|
+
true
|
470
|
+
end
|
434
471
|
end
|
435
472
|
|
436
473
|
# the table.field name for this column, if applicable
|
@@ -438,6 +475,10 @@ module ActiveScaffold::DataStructures
|
|
438
475
|
@field ||= quoted_field(field_name)
|
439
476
|
end
|
440
477
|
|
478
|
+
def quoted_foreign_type
|
479
|
+
quoted_field(quoted_field_name(association.foreign_type))
|
480
|
+
end
|
481
|
+
|
441
482
|
def type_for_attribute
|
442
483
|
ActiveScaffold::OrmChecks.type_for_attribute active_record_class, name
|
443
484
|
end
|
@@ -446,8 +487,39 @@ module ActiveScaffold::DataStructures
|
|
446
487
|
ActiveScaffold::OrmChecks.column_type active_record_class, name
|
447
488
|
end
|
448
489
|
|
490
|
+
def default_value
|
491
|
+
ActiveScaffold::OrmChecks.column_type active_record_class, name
|
492
|
+
end
|
493
|
+
|
494
|
+
def attributes=(opts)
|
495
|
+
opts.each do |setting, value|
|
496
|
+
send "#{setting}=", value
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
def cast(value)
|
501
|
+
ActiveScaffold::OrmChecks.cast active_record_class, name, value
|
502
|
+
end
|
503
|
+
|
449
504
|
protected
|
450
505
|
|
506
|
+
def setup_defaults_for_column
|
507
|
+
if active_record_class.respond_to?(:defined_enums) && active_record_class.defined_enums[name.to_s]
|
508
|
+
@form_ui = :select
|
509
|
+
@options = {:options => active_record_class.send(name.to_s.pluralize).keys.map(&:to_sym)}
|
510
|
+
elsif column_number?
|
511
|
+
@number = true
|
512
|
+
@form_ui = :number
|
513
|
+
@options = {:format => :i18n_number}
|
514
|
+
else
|
515
|
+
@form_ui =
|
516
|
+
case column_type
|
517
|
+
when :boolean then null? ? :boolean : :checkbox
|
518
|
+
when :text then :textarea
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
451
523
|
def setup_association_info
|
452
524
|
assoc = active_record_class.reflect_on_association(name)
|
453
525
|
@association =
|
@@ -506,7 +578,7 @@ module ActiveScaffold::DataStructures
|
|
506
578
|
end
|
507
579
|
|
508
580
|
def column_number?
|
509
|
-
return %i[float decimal integer].include?
|
581
|
+
return %i[float decimal integer].include? column_type if active_record?
|
510
582
|
return @column.type < Numeric if mongoid?
|
511
583
|
end
|
512
584
|
|
@@ -560,5 +632,18 @@ module ActiveScaffold::DataStructures
|
|
560
632
|
300
|
561
633
|
end
|
562
634
|
end
|
635
|
+
|
636
|
+
def check_valid_action_ui_params(value)
|
637
|
+
return true if valid_action_ui_params?(value)
|
638
|
+
raise ArgumentError, 'value must be a Symbol, or an array of Symbol and Hash'
|
639
|
+
end
|
640
|
+
|
641
|
+
def valid_action_ui_params?(value)
|
642
|
+
if value.is_a?(Array)
|
643
|
+
value.size <= 2 && value[0].is_a?(Symbol) && (value[1].nil? || value[1].is_a?(Hash))
|
644
|
+
else
|
645
|
+
value.nil? || value.is_a?(Symbol)
|
646
|
+
end
|
647
|
+
end
|
563
648
|
end
|
564
649
|
end
|
@@ -2,6 +2,9 @@ module ActiveScaffold
|
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
initializer 'active_scaffold.action_controller' do
|
4
4
|
ActiveSupport.on_load :action_controller do
|
5
|
+
require 'active_scaffold/extensions/action_controller_rescueing'
|
6
|
+
require 'active_scaffold/extensions/action_controller_rendering'
|
7
|
+
require 'active_scaffold/extensions/routing_mapper'
|
5
8
|
include ActiveScaffold::Core
|
6
9
|
include ActiveScaffold::RespondsToParent
|
7
10
|
include ActiveScaffold::Helpers::ControllerHelpers
|
@@ -12,12 +15,17 @@ module ActiveScaffold
|
|
12
15
|
|
13
16
|
initializer 'active_scaffold.action_view' do
|
14
17
|
ActiveSupport.on_load :action_view do
|
18
|
+
require 'active_scaffold/extensions/action_view_rendering'
|
19
|
+
require 'active_scaffold/extensions/name_option_for_datetime'
|
15
20
|
include ActiveScaffold::Helpers::ViewHelpers
|
16
21
|
end
|
17
22
|
end
|
18
23
|
|
19
24
|
initializer 'active_scaffold.active_record' do
|
20
25
|
ActiveSupport.on_load :active_record do
|
26
|
+
require 'active_scaffold/extensions/to_label'
|
27
|
+
require 'active_scaffold/extensions/unsaved_associated'
|
28
|
+
require 'active_scaffold/extensions/unsaved_record'
|
21
29
|
include ActiveScaffold::ActiveRecordPermissions::ModelUserAccess::Model
|
22
30
|
module ActiveRecord::Associations
|
23
31
|
Association.send :include, ActiveScaffold::Tableless::Association
|
@@ -39,5 +47,12 @@ module ActiveScaffold
|
|
39
47
|
initializer 'active_scaffold.assets' do
|
40
48
|
config.assets.precompile << 'active_scaffold/indicator.gif'
|
41
49
|
end
|
50
|
+
|
51
|
+
initializer 'active_scaffold.extensions' do
|
52
|
+
require 'active_scaffold/extensions/cow_proxy'
|
53
|
+
require 'active_scaffold/extensions/ice_nine'
|
54
|
+
require 'active_scaffold/extensions/localize'
|
55
|
+
require 'active_scaffold/extensions/paginator_extensions'
|
56
|
+
end
|
42
57
|
end
|
43
58
|
end
|
@@ -110,15 +110,42 @@ module ActiveScaffold
|
|
110
110
|
end
|
111
111
|
return nil unless sql
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
where_values = []
|
114
|
+
sql_conditions = []
|
115
|
+
column.search_sql.each do |search_sql|
|
116
|
+
if search_sql.is_a?(Hash)
|
117
|
+
subquery_sql, *subquery_values = subquery_condition(column, sql, search_sql, values)
|
118
|
+
sql_conditions << subquery_sql
|
119
|
+
where_values.concat subquery_values
|
120
|
+
else
|
121
|
+
sql_conditions << sql % {:search_sql => search_sql}
|
122
|
+
where_values.concat values
|
123
|
+
end
|
124
|
+
end
|
125
|
+
[sql_conditions.join(' OR '), *where_values]
|
116
126
|
rescue StandardError => e
|
117
127
|
Rails.logger.error "#{e.class.name}: #{e.message} -- on the ActiveScaffold column :#{column.name}, search_ui = #{search_ui} in #{name}"
|
118
128
|
raise e
|
119
129
|
end
|
120
130
|
end
|
121
131
|
|
132
|
+
def subquery_condition(column, sql, options, values)
|
133
|
+
relation, *columns = options[:subquery]
|
134
|
+
conditions = [columns.map { |search_sql| sql % {:search_sql => search_sql} }.join(' OR ')]
|
135
|
+
conditions += values * columns.size if values.present?
|
136
|
+
subquery = relation.where(conditions)
|
137
|
+
subquery = subquery.select(relation.primary_key) if subquery.select_values.blank?
|
138
|
+
|
139
|
+
conditions = [["#{options[:field] || column.field} IN (?)", options[:conditions]&.first].compact.join(' AND ')]
|
140
|
+
conditions << subquery
|
141
|
+
conditions.concat options[:conditions][1..-1] if options[:conditions]
|
142
|
+
if column.association&.polymorphic?
|
143
|
+
conditions[0] << " AND #{column.quoted_foreign_type} = ?"
|
144
|
+
conditions << relation.name
|
145
|
+
end
|
146
|
+
conditions
|
147
|
+
end
|
148
|
+
|
122
149
|
def condition_for_search_ui(column, value, like_pattern, search_ui)
|
123
150
|
case search_ui
|
124
151
|
when :boolean, :checkbox
|
@@ -133,7 +160,7 @@ module ActiveScaffold
|
|
133
160
|
condition_for_range(column, value, like_pattern)
|
134
161
|
when :date, :time, :datetime, :timestamp
|
135
162
|
condition_for_datetime(column, value)
|
136
|
-
when :select, :multi_select, :country, :usa_state, :chosen, :multi_chosen
|
163
|
+
when :select, :select_multiple, :draggable, :multi_select, :country, :usa_state, :chosen, :multi_chosen
|
137
164
|
values = Array(value).select(&:present?)
|
138
165
|
['%<search_sql>s in (?)', values] if values.present?
|
139
166
|
else
|
@@ -171,7 +198,10 @@ module ActiveScaffold
|
|
171
198
|
elsif value[:from].blank?
|
172
199
|
nil
|
173
200
|
elsif ActiveScaffold::Finder::STRING_COMPARATORS.values.include?(value[:opt])
|
174
|
-
[
|
201
|
+
[
|
202
|
+
"%<search_sql>s #{'NOT ' if value[:opt].start_with?('not_')}#{ActiveScaffold::Finder.like_operator} ?",
|
203
|
+
value[:opt].sub('not_', '').sub('?', value[:from])
|
204
|
+
]
|
175
205
|
elsif value[:opt] == 'BETWEEN'
|
176
206
|
['(%<search_sql>s BETWEEN ? AND ?)', value[:from], value[:to]]
|
177
207
|
elsif ActiveScaffold::Finder::NUMERIC_COMPARATORS.include?(value[:opt])
|
@@ -241,14 +271,19 @@ module ActiveScaffold
|
|
241
271
|
nil
|
242
272
|
end
|
243
273
|
|
244
|
-
def
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
274
|
+
def format_for_date(column, value, format_name = column.options[:format])
|
275
|
+
if format_name
|
276
|
+
format = I18n.t("date.formats.#{format_name}")
|
277
|
+
format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m
|
278
|
+
en_value = I18n.locale == :en ? value : translate_days_and_months(value, format)
|
279
|
+
end
|
280
|
+
[en_value || value, format]
|
281
|
+
end
|
282
|
+
|
283
|
+
def parse_date_with_format(value, format)
|
284
|
+
Date.strptime(value, *format)
|
249
285
|
rescue StandardError => e
|
250
|
-
message = "Error parsing date from #{
|
251
|
-
message << " (#{value})" if en_value != value
|
286
|
+
message = "Error parsing date from #{value}"
|
252
287
|
message << ", with format #{format}" if format
|
253
288
|
Rails.logger.warn "#{message}:\n#{e.message}\n#{e.backtrace.join("\n")}"
|
254
289
|
nil
|
@@ -280,7 +315,7 @@ module ActiveScaffold
|
|
280
315
|
value.send(conversion)
|
281
316
|
end
|
282
317
|
elsif conversion == :to_date
|
283
|
-
parse_date_with_format(
|
318
|
+
parse_date_with_format(*format_for_date(column, value))
|
284
319
|
elsif value.include?('T')
|
285
320
|
Time.zone.parse(value)
|
286
321
|
else # datetime
|
@@ -292,7 +327,7 @@ module ActiveScaffold
|
|
292
327
|
def condition_value_for_numeric(column, value)
|
293
328
|
return value if value.nil?
|
294
329
|
value = column.number_to_native(value) if column.options[:format] && column.search_ui != :number
|
295
|
-
case (column.search_ui || column.
|
330
|
+
case (column.search_ui || column.column_type)
|
296
331
|
when :integer then
|
297
332
|
if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
298
333
|
value ? 1 : 0
|
@@ -300,8 +335,7 @@ module ActiveScaffold
|
|
300
335
|
value.to_i
|
301
336
|
end
|
302
337
|
when :float then value.to_f
|
303
|
-
when :decimal
|
304
|
-
::ActiveRecord::Type::Decimal.new.send(Rails.version < '5.0' ? :type_cast_from_user : :cast, value)
|
338
|
+
when :decimal then ::ActiveRecord::Type::Decimal.new.cast(value)
|
305
339
|
else
|
306
340
|
value
|
307
341
|
end
|
@@ -309,28 +343,102 @@ module ActiveScaffold
|
|
309
343
|
|
310
344
|
def datetime_conversion_for_condition(column)
|
311
345
|
if column.column
|
312
|
-
column.
|
346
|
+
column.column_type == :date ? :to_date : :to_time
|
313
347
|
else
|
314
348
|
:to_time
|
315
349
|
end
|
316
350
|
end
|
317
351
|
|
318
352
|
def condition_for_datetime(column, value, like_pattern = nil)
|
353
|
+
operator = ActiveScaffold::Finder::NUMERIC_COMPARATORS.include?(value['opt']) && value['opt'] != 'BETWEEN' ? value['opt'] : nil
|
354
|
+
from_value, to_value = datetime_from_to(column, value)
|
355
|
+
|
356
|
+
if column.search_sql.is_a? Proc
|
357
|
+
column.search_sql.call(from_value, to_value, operator)
|
358
|
+
elsif operator.nil?
|
359
|
+
['%<search_sql>s BETWEEN ? AND ?', from_value, to_value] unless from_value.nil? || to_value.nil?
|
360
|
+
else
|
361
|
+
["%<search_sql>s #{value['opt']} ?", from_value] unless from_value.nil?
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def datetime_from_to(column, value)
|
319
366
|
conversion = datetime_conversion_for_condition(column)
|
320
|
-
|
321
|
-
|
367
|
+
case value['opt']
|
368
|
+
when 'RANGE'
|
369
|
+
values = datetime_from_to_for_range(column, value)
|
370
|
+
# Avoid calling to_time, not needed and broken on rails >= 4, because return local time instead of UTC
|
371
|
+
values.collect!(&conversion) if conversion != :to_time
|
372
|
+
values
|
373
|
+
when 'PAST', 'FUTURE'
|
374
|
+
values = datetime_from_to_for_trend(column, value)
|
375
|
+
# Avoid calling to_time, not needed and broken on rails >= 4, because return local time instead of UTC
|
376
|
+
values.collect!(&conversion) if conversion != :to_time
|
377
|
+
values
|
378
|
+
else
|
379
|
+
%w[from to].collect { |field| condition_value_for_datetime(column, value[field], conversion) }
|
380
|
+
end
|
381
|
+
end
|
322
382
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
383
|
+
def datetime_now
|
384
|
+
Time.zone.now
|
385
|
+
end
|
386
|
+
|
387
|
+
def datetime_from_to_for_trend(column, value)
|
388
|
+
case value['opt']
|
389
|
+
when 'PAST'
|
390
|
+
trend_number = [value['number'].to_i, 1].max
|
391
|
+
now = datetime_now
|
392
|
+
if datetime_column_date?(column)
|
393
|
+
from = now.beginning_of_day.ago(trend_number.send(value['unit'].downcase.singularize.to_sym))
|
394
|
+
to = now.end_of_day
|
395
|
+
else
|
396
|
+
from = now.ago(trend_number.send(value['unit'].downcase.singularize.to_sym))
|
397
|
+
to = now
|
398
|
+
end
|
399
|
+
[from, to]
|
400
|
+
when 'FUTURE'
|
401
|
+
trend_number = [value['number'].to_i, 1].max
|
402
|
+
now = datetime_now
|
403
|
+
if datetime_column_date?(column)
|
404
|
+
from = now.beginning_of_day
|
405
|
+
to = now.end_of_day.in(trend_number.send(value['unit'].downcase.singularize.to_sym))
|
406
|
+
else
|
407
|
+
from = now
|
408
|
+
to = now.in(trend_number.send(value['unit'].downcase.singularize.to_sym))
|
409
|
+
end
|
410
|
+
[from, to]
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
def datetime_from_to_for_range(column, value)
|
415
|
+
case value['range']
|
416
|
+
when 'TODAY'
|
417
|
+
[datetime_now.beginning_of_day, datetime_now.end_of_day]
|
418
|
+
when 'YESTERDAY'
|
419
|
+
[datetime_now.ago(1.day).beginning_of_day, datetime_now.ago(1.day).end_of_day]
|
420
|
+
when 'TOMORROW'
|
421
|
+
[datetime_now.in(1.day).beginning_of_day, datetime_now.in(1.day).end_of_day]
|
329
422
|
else
|
330
|
-
|
423
|
+
range_type, range = value['range'].downcase.split('_')
|
424
|
+
raise ArgumentError unless %w[week month year].include?(range)
|
425
|
+
case range_type
|
426
|
+
when 'this'
|
427
|
+
return datetime_now.send("beginning_of_#{range}".to_sym), datetime_now.send("end_of_#{range}")
|
428
|
+
when 'prev'
|
429
|
+
return datetime_now.ago(1.send(range.to_sym)).send("beginning_of_#{range}".to_sym), datetime_now.ago(1.send(range.to_sym)).send("end_of_#{range}".to_sym)
|
430
|
+
when 'next'
|
431
|
+
return datetime_now.in(1.send(range.to_sym)).send("beginning_of_#{range}".to_sym), datetime_now.in(1.send(range.to_sym)).send("end_of_#{range}".to_sym)
|
432
|
+
else
|
433
|
+
return nil, nil
|
434
|
+
end
|
331
435
|
end
|
332
436
|
end
|
333
437
|
|
438
|
+
def datetime_column_date?(column)
|
439
|
+
column.column&.type == :date
|
440
|
+
end
|
441
|
+
|
334
442
|
def condition_for_record_select_type(column, value, like_pattern = nil)
|
335
443
|
if value.is_a?(Array)
|
336
444
|
['%<search_sql>s IN (?)', value]
|
@@ -361,9 +469,16 @@ module ActiveScaffold
|
|
361
469
|
STRING_COMPARATORS = {
|
362
470
|
:contains => '%?%',
|
363
471
|
:begins_with => '?%',
|
364
|
-
:ends_with => '%?'
|
472
|
+
:ends_with => '%?',
|
473
|
+
:doesnt_contain => 'not_%?%',
|
474
|
+
:doesnt_begin_with => 'not_?%',
|
475
|
+
:doesnt_end_with => 'not_%?'
|
365
476
|
}.freeze
|
366
477
|
NULL_COMPARATORS = %w[null not_null].freeze
|
478
|
+
DATE_COMPARATORS = %w[PAST FUTURE RANGE].freeze
|
479
|
+
DATE_UNITS = %w[DAYS WEEKS MONTHS YEARS].freeze
|
480
|
+
TIME_UNITS = %w[SECONDS MINUTES HOURS].freeze
|
481
|
+
DATE_RANGES = %w[TODAY YESTERDAY TOMORROW THIS_WEEK PREV_WEEK NEXT_WEEK THIS_MONTH PREV_MONTH NEXT_MONTH THIS_YEAR PREV_YEAR NEXT_YEAR].freeze
|
367
482
|
|
368
483
|
def self.included(klass)
|
369
484
|
klass.extend ClassMethods
|
@@ -25,17 +25,22 @@ module ActiveScaffold
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def generate_temporary_id(record = nil, generated_id = nil)
|
28
|
-
|
29
|
-
|
28
|
+
unless generated_id
|
29
|
+
temp_id = (Time.now.to_f * 1_000_000).to_i.to_s
|
30
|
+
temp_id.succ! while @temporary_ids&.dig(record.class.name)&.include?(temp_id)
|
31
|
+
generated_id = temp_id
|
30
32
|
end
|
33
|
+
cache_generated_id record, generated_id
|
34
|
+
generated_id
|
31
35
|
end
|
32
36
|
|
33
37
|
def cache_generated_id(record, generated_id)
|
34
|
-
|
38
|
+
# cache all generated ids for the same class, so generate_temporary_id can check and ensure ids are unique
|
39
|
+
((@temporary_ids ||= {})[record.class.name] ||= []) << generated_id if record && generated_id
|
35
40
|
end
|
36
41
|
|
37
42
|
def generated_id(record)
|
38
|
-
@temporary_ids[record.class.name] if record && @temporary_ids
|
43
|
+
@temporary_ids[record.class.name]&.last if record && @temporary_ids
|
39
44
|
end
|
40
45
|
|
41
46
|
# These params should not propagate:
|