active_scaffold 3.7.0 → 3.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.rdoc +24 -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 +19 -1
- 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 +4 -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,22 +2,33 @@ 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'
|
5
7
|
include ActiveScaffold::Core
|
6
8
|
include ActiveScaffold::RespondsToParent
|
7
9
|
include ActiveScaffold::Helpers::ControllerHelpers
|
8
10
|
include ActiveScaffold::ActiveRecordPermissions::ModelUserAccess::Controller
|
9
|
-
ActiveScaffold::Bridges.prepare_all
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
14
|
+
config.after_initialize do
|
15
|
+
require 'active_scaffold/extensions/routing_mapper'
|
16
|
+
ActiveScaffold::Bridges.prepare_all
|
17
|
+
end
|
18
|
+
|
13
19
|
initializer 'active_scaffold.action_view' do
|
14
20
|
ActiveSupport.on_load :action_view do
|
21
|
+
require 'active_scaffold/extensions/action_view_rendering'
|
22
|
+
require 'active_scaffold/extensions/name_option_for_datetime'
|
15
23
|
include ActiveScaffold::Helpers::ViewHelpers
|
16
24
|
end
|
17
25
|
end
|
18
26
|
|
19
27
|
initializer 'active_scaffold.active_record' do
|
20
28
|
ActiveSupport.on_load :active_record do
|
29
|
+
require 'active_scaffold/extensions/to_label'
|
30
|
+
require 'active_scaffold/extensions/unsaved_associated'
|
31
|
+
require 'active_scaffold/extensions/unsaved_record'
|
21
32
|
include ActiveScaffold::ActiveRecordPermissions::ModelUserAccess::Model
|
22
33
|
module ActiveRecord::Associations
|
23
34
|
Association.send :include, ActiveScaffold::Tableless::Association
|
@@ -39,5 +50,12 @@ module ActiveScaffold
|
|
39
50
|
initializer 'active_scaffold.assets' do
|
40
51
|
config.assets.precompile << 'active_scaffold/indicator.gif'
|
41
52
|
end
|
53
|
+
|
54
|
+
initializer 'active_scaffold.extensions' do
|
55
|
+
require 'active_scaffold/extensions/cow_proxy'
|
56
|
+
require 'active_scaffold/extensions/ice_nine'
|
57
|
+
require 'active_scaffold/extensions/localize'
|
58
|
+
require 'active_scaffold/extensions/paginator_extensions'
|
59
|
+
end
|
42
60
|
end
|
43
61
|
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:
|