active_scaffold 3.7.4 → 3.7.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9206609200e1c9330a6e04b1731300dad5fcba8a52c70ae26ef6ba6427da4c25
4
- data.tar.gz: 0b501f82598b99ff587b1fa484d017a73f1fd018b5a26a7968c065d0152ae74b
3
+ metadata.gz: cfe45d0a0b28f5cb324e7104add97e7a105de6986accf8d64db02cf5f65f19ea
4
+ data.tar.gz: f8461b6a58ce6b1c93df2eaf95540a759fdcd73d21f256ee1a38ea4436b73987
5
5
  SHA512:
6
- metadata.gz: '08c1c4a3b4dad7c731793ebecd4e238a36ee49e340964c8321a88b562fafbf0b12d7992501c5ffad8efa60820a40e80879f0c9e60715cc1da2e31a5c764781bc'
7
- data.tar.gz: 310c5bf214800a98efd1d7af45b771608ce85cdf27db3df8437a0ea9cbabb243f2df8d8ee3603c81a9e43a76ba09dcaae20df26aa3a1b0df9bf0b18f2c244bab
6
+ metadata.gz: a65de91f886605e57d0f183295d9d1f8ea358b4657826143265f8359f4d41761b6325644fcb6d5dc1d5e5d4b87bbc966bf8cd9d4ee66db0ae4048455eb0c6b62
7
+ data.tar.gz: 9fb4e3b6a38c5f340cdf430146ae9616a5e9a93e66c11e32dece9cb5704229499610da861916371e3bafb973d43705c9716228b5d04c50f896d5538320e0125c
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,22 @@
1
+ = 3.7.6
2
+ - Respect :label_method set in form_ui_options on list.
3
+ - Fix grouped search for PostgreSQL
4
+ - Fix loading current search params when search form is open and store_user_settings is disabled
5
+
6
+ = 3.7.5
7
+ - Fix always show search for nested list on multi-level through associations
8
+ - Support changing step, min and max options with column options or search_ui options for decimal and integer search_ui
9
+ - Support null and not null comparators in date and datetime search_ui
10
+ - Support defining options_for_association_conditions, association_klass_scoped and active_scaffold_enum_options helpers prefixed with model name. Useful when clear_helpers is not called in ApplicationController.
11
+ - Fix class for columns with form_ui or css_class when refreshing column
12
+ - Eval after_config_callback proc in the context of AS config
13
+ - Fix registering constrained fields on polymorphic association
14
+ - Improve applying constraints for polymorphic associations when constraining with multiple ids, set only the foreign type
15
+ - Support prefixing helpers with base class for models using STI besides model name prefix
16
+ - Support create when nested on through association when source association is singular, as rails creates the joint model
17
+ - Allow to set controller nil for association columns to STI models
18
+ - Add session argument to condition_for_column and condition_for_<column>_column, so custom conditions on columns may use the session, make it optional so it's backwards compatible
19
+
1
20
  = 3.7.4
2
21
  - Support loading both jquery and jquery-ui externally
3
22
  - Fix subquery in search with STI
@@ -5,7 +5,7 @@
5
5
  <table>
6
6
  <tbody class="before-header" id="<%= before_header_id -%>">
7
7
  <% if active_scaffold_config.list.always_show_search %>
8
- <% old_record, @record = @record, new_model %>
8
+ <% old_record, @record = @record, active_scaffold_config.model.new %>
9
9
  <tr>
10
10
  <td>
11
11
  <div class="active-scaffold show_search-view <%= "#{id_from_controller params[:controller]}-view" %> view">
@@ -1,9 +1,10 @@
1
1
  <%
2
- column = if render_field.is_a? ActiveScaffold::DataStructures::Column
3
- render_field
4
- else
5
- active_scaffold_config.columns[render_field]
6
- end
2
+ column =
3
+ if render_field.is_a? ActiveScaffold::DataStructures::Column
4
+ render_field
5
+ else
6
+ active_scaffold_config.columns[render_field]
7
+ end
7
8
  return unless @main_columns.include? column.name
8
9
  @rendered ||= Set.new
9
10
  return if @rendered.include? column.name
@@ -14,13 +15,15 @@
14
15
  renders_as = column_renders_as(column)
15
16
  form_ui = column.form_ui
16
17
  end
18
+
19
+ column_css_class = column.css_class unless column.css_class.nil? || column.css_class.is_a?(Proc)
17
20
  options = {field_class: "#{column.name}-input", hidden: form_ui == :hidden}
18
21
  options[:subform_class] = "#{column.name}-sub-form" if column.association
19
22
  options[:attrs] =
20
23
  if renders_as == :subform
21
- active_scaffold_subform_attributes(column, (column.css_class unless column.css_class.is_a?(Proc)))
24
+ active_scaffold_subform_attributes(column, column_css_class)
22
25
  else
23
- {class: 'form-element', id: ''}
26
+ {class: "form-element #{:required if column.required?(@form_action)} #{column.form_ui} #{column_css_class}", id: ''}
24
27
  end
25
28
  html =
26
29
  if scope
@@ -3,9 +3,9 @@ module ActiveScaffold::Actions
3
3
  def self.included(base)
4
4
  return if base < InstanceMethods
5
5
  base.send :include, InstanceMethods
6
- base.before_action :search_authorized_filter, :only => :show_search
7
- base.before_action :store_search_params_into_session, :only => [:index]
8
- base.before_action :do_search, :only => [:index]
6
+ base.before_action :search_authorized_filter, only: :show_search
7
+ base.before_action :store_search_params_into_session, :only => %i[index show_search]
8
+ base.before_action :do_search, only: [:index]
9
9
  base.helper_method :search_params
10
10
  end
11
11
 
@@ -17,6 +17,7 @@ module ActiveScaffold::Actions
17
17
  base.helper_method :embedded?
18
18
  base.helper_method :loading_embedded?
19
19
  base.helper_method :calculate_query
20
+ base.helper_method :calculate_subquery
20
21
  base.helper_method :new_model
21
22
  base.helper_method :touch_device?
22
23
  base.helper_method :hover_via_click?
@@ -134,8 +135,8 @@ module ActiveScaffold::Actions
134
135
  parent = parent_model.new
135
136
  copy_attributes(find_if_allowed(params[:parent_id], :read, parent_model), parent) if params[:parent_id]
136
137
  parent.id = params[:parent_id]
137
- parent = update_record_from_params(parent, cfg.send(params[:parent_id] ? :update : :create).columns, params[:record], true) if @column.send_form_on_update_column
138
138
  apply_constraints_to_record(parent) unless params[:parent_id]
139
+ parent = update_record_from_params(parent, cfg.send(params[:parent_id] ? :update : :create).columns, params[:record], true) if @column.send_form_on_update_column
139
140
  if association.collection?
140
141
  record.send(association.name) << parent
141
142
  else
@@ -93,8 +93,10 @@ module ActiveScaffold::Actions
93
93
  def do_create(options = {})
94
94
  attributes = options[:attributes] || params[:record]
95
95
  active_scaffold_config.model.transaction do
96
- @record = update_record_from_params(new_model, active_scaffold_config.create.columns, attributes)
96
+ @record = new_model
97
+ # before assign params, to set foreign_type of constraints in polymorphic association with multiple id
97
98
  apply_constraints_to_record(@record, :allow_autosave => true)
99
+ @record = update_record_from_params(@record, active_scaffold_config.create.columns, attributes)
98
100
  create_association_with_parent(@record) if nested?
99
101
  before_create_save(@record)
100
102
  # errors to @record can be added by update_record_from_params when association fails to set and ActiveRecord::RecordNotSaved is raised
@@ -56,25 +56,33 @@ module ActiveScaffold::Actions
56
56
 
57
57
  def custom_finder_options
58
58
  if grouped_search?
59
- group_sql = calculation_for_group_by(search_group_column&.field || search_group_name)
60
- select_query = grouped_search_select
61
- select_query << group_sql.as(search_group_column.name.to_s) if search_group_column && group_sql.respond_to?(:to_sql)
62
- {group: group_sql, select: select_query}
59
+ grouped_search_finder_options
63
60
  else
64
61
  super
65
62
  end
66
63
  end
67
64
 
65
+ def grouped_search_finder_options
66
+ group_sql = calculation_for_group_by(search_group_column&.field || search_group_name, search_group_function) if search_group_function
67
+ group_by = group_sql&.to_sql || quoted_select_columns(search_group_column&.select_columns || [search_group_name])
68
+ select_query = grouped_search_select + (group_sql ? [group_sql.as(search_group_column.name.to_s)] : group_by)
69
+ {group: group_by, select: select_query, reorder: grouped_sorting(group_by)}
70
+ end
71
+
68
72
  def grouped_search_select
69
- select_query = quoted_select_columns(search_group_column&.select_columns || [search_group_name])
70
- if active_scaffold_config.model.columns_hash.include?(active_scaffold_config.model.inheritance_column)
71
- select_query << active_scaffold_config.columns[active_scaffold_config.model.inheritance_column].field
72
- end
73
- grouped_columns_calculations.each do |name, part|
74
- select_query << (part.respond_to?(:as) ? part : Arel::Nodes::SqlLiteral.new(part)).as(name.to_s)
73
+ grouped_columns_calculations.map do |name, part|
74
+ (part.respond_to?(:as) ? part : Arel::Nodes::SqlLiteral.new(part)).as(name.to_s)
75
75
  end
76
76
  end
77
77
 
78
+ def grouped_sorting(group_sql)
79
+ return unless search_group_column && active_scaffold_config.list.user.sorting
80
+ group_sort = search_group_function ? group_sql : search_group_column.sort[:sql] if search_group_column.sortable?
81
+ grouped_columns = grouped_columns_calculations.merge(search_group_column.name => group_sort)
82
+ sorting = active_scaffold_config.list.user.sorting.clause(grouped_columns)
83
+ active_scaffold_config.active_record? ? sorting&.map(&Arel.method(:sql)) : sorting
84
+ end
85
+
78
86
  def grouped_columns_calculations
79
87
  @grouped_columns_calculations ||= list_columns[1..-1].each_with_object({}) do |c, h|
80
88
  h[c.name] = calculation_for_group_search(c)
@@ -85,21 +93,24 @@ module ActiveScaffold::Actions
85
93
  sql_function column.calculate.to_s, column.active_record_class.arel_table[column.name]
86
94
  end
87
95
 
88
- def calculation_for_group_by(group_sql)
89
- return group_sql unless search_group_function
96
+ def calculation_for_group_by(group_sql, group_function)
90
97
  group_sql = Arel::Nodes::SqlLiteral.new(group_sql)
91
- case search_group_function
98
+ case group_function
92
99
  when 'year', 'month', 'quarter'
93
- sql_function(search_group_function, group_sql)
100
+ extract_sql_fn(group_function, group_sql)
94
101
  when 'year_month'
95
- sql_function('extract', sql_operator(Arel::Nodes::SqlLiteral.new('YEAR_MONTH'), 'FROM', group_sql))
102
+ sql_operator(sql_operator(extract_sql_fn('year', group_sql), '*', 100), '+', extract_sql_fn('month', group_sql))
96
103
  when 'year_quarter'
97
- sql_operator(sql_operator(sql_function('year', group_sql), '*', 10), '+', sql_function('quarter', group_sql))
104
+ sql_operator(sql_operator(extract_sql_fn('year', group_sql), '*', 10), '+', extract_sql_fn('quarter', group_sql))
98
105
  else
99
- raise "#{search_group_function} unsupported, override calculation_for_group_by in #{self.class.name}"
106
+ raise "#{group_function} unsupported, override calculation_for_group_by in #{self.class.name}"
100
107
  end
101
108
  end
102
109
 
110
+ def extract_sql_fn(part, column)
111
+ sql_function('extract', sql_operator(Arel::Nodes::SqlLiteral.new(part), 'FROM', column))
112
+ end
113
+
103
114
  def sql_function(function, *args)
104
115
  args.map! { |arg| quoted_arel_value(arg) }
105
116
  Arel::Nodes::NamedFunction.new(function, args)
@@ -157,7 +168,7 @@ module ActiveScaffold::Actions
157
168
  search_params.each do |key, value|
158
169
  next unless columns.include? key
159
170
  column = active_scaffold_config.columns[key]
160
- search_condition = self.class.condition_for_column(column, value, text_search)
171
+ search_condition = self.class.condition_for_column(column, value, text_search, session)
161
172
  next if search_condition.blank?
162
173
 
163
174
  active_scaffold_conditions << search_condition
@@ -8,17 +8,20 @@ module ActiveScaffold
8
8
 
9
9
  # Returns the current constraints
10
10
  def active_scaffold_constraints
11
- @active_scaffold_constraints ||= active_scaffold_embedded_params[:constraints] || {}
11
+ @active_scaffold_constraints ||= active_scaffold_embedded_params[:constraints]&.to_unsafe_h || {}
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)
14
+ def columns_from_constraint(column_name, value)
15
+ return if params_hash?(value)
16
+ if value.is_a?(Array)
18
17
  column = active_scaffold_config.columns[column_name]
19
- column && value.size > (column.association&.polymorphic? ? 2 : 1)
18
+ if column&.association&.polymorphic?
19
+ [column.association.foreign_type.to_sym, (column.name if value.size == 2)].compact
20
+ elsif column && value.size == 1
21
+ column.name
22
+ end
20
23
  else
21
- true
24
+ column_name.to_sym
22
25
  end
23
26
  end
24
27
 
@@ -31,7 +34,7 @@ module ActiveScaffold
31
34
  # we do NOT register the constraint column, as records will have different values in the column.
32
35
  def register_constraints_with_action_columns(constrained_fields = nil)
33
36
  constrained_fields ||= []
34
- constrained_fields |= active_scaffold_constraints.select { |k, v| register_constraint?(k, v) }.keys.collect(&:to_sym)
37
+ constrained_fields |= active_scaffold_constraints.flat_map { |k, v| columns_from_constraint(k, v) }.compact
35
38
  exclude_actions = []
36
39
  %i[list update].each do |action_name|
37
40
  if active_scaffold_config.actions.include? action_name
@@ -161,12 +164,16 @@ module ActiveScaffold
161
164
  column = config.columns[k]
162
165
  if column&.association
163
166
  if column.association.collection?
164
- record.send(k.to_s).send(:<<, column.association.klass.find(v))
167
+ record.send(k.to_s).send(:<<, column.association.klass.find(v)) unless column.association.nested?
165
168
  elsif column.association.polymorphic?
166
169
  unless v.is_a?(Array) && v.size >= 2
167
170
  raise ActiveScaffold::MalformedConstraint, polymorphic_constraint_error(column.association), caller
168
171
  end
169
- record.send("#{k}=", v[0].constantize.find(v[1])) if v.size == 2
172
+ if v.size == 2
173
+ record.send("#{k}=", v[0].constantize.find(v[1]))
174
+ else
175
+ record.send("#{column.association.foreign_type}=", v[0])
176
+ end
170
177
  elsif !column.association.source_reflection&.options&.include?(:through) && # regular singular association, or one-level through association
171
178
  !v.is_a?(Array)
172
179
  record.send("#{k}=", column.association.klass.find(v))
@@ -177,7 +184,7 @@ module ActiveScaffold
177
184
  # note that we can't take the extra step to correct this unless we're permitted to
178
185
  # run operations where activerecord auto-saves the object.
179
186
  reverse = column.association.reverse_association
180
- if reverse.singular? && !reverse.belongs_to? && options[:allow_autosave]
187
+ if reverse&.singular? && !reverse.belongs_to? && options[:allow_autosave]
181
188
  record.send(k).send("#{reverse.name}=", record)
182
189
  end
183
190
  end
@@ -63,7 +63,7 @@ module ActiveScaffold
63
63
  active_scaffold_config.configure(&block) if block_given?
64
64
  active_scaffold_config.class.after_config_callbacks.each do |callback|
65
65
  if callback.is_a?(Proc)
66
- callback.call
66
+ instance_eval(&callback)
67
67
  elsif active_scaffold_config.respond_to?(callback)
68
68
  active_scaffold_config.send(callback)
69
69
  end
@@ -46,6 +46,10 @@ module ActiveScaffold::DataStructures::Association
46
46
  false
47
47
  end
48
48
 
49
+ def nested?
50
+ false
51
+ end
52
+
49
53
  def through_singular?
50
54
  through? && !through_reflection.collection?
51
55
  end
@@ -10,6 +10,10 @@ module ActiveScaffold::DataStructures::Association
10
10
  @association.options[:through].present?
11
11
  end
12
12
 
13
+ def nested?
14
+ @association.nested?
15
+ end
16
+
13
17
  def readonly?
14
18
  scope_values[:readonly]
15
19
  end
@@ -90,15 +90,22 @@ module ActiveScaffold::DataStructures
90
90
  delegate :name, :belongs_to?, :has_one?, :has_many?, :habtm?, :readonly?, :to => :association
91
91
 
92
92
  # A through association with has_one or has_many as source association
93
- # create cannot be called in nested through associations, and not-nested through associations
94
- # unless is through singular or create columns include through reflection of reverse association
95
- # e.g. customer -> networks -> firewall, reverse is firewall -> network -> customer,
96
- # firewall can be created if create columns include network
93
+ # create cannot be called in nested through associations, and not-nested through associations, unless:
94
+ # 1. is through singular and source association has reverse, e.g.:
95
+ # Employee belongs to vendor, Vendor has many rates, Rate belongs to vendor, Employee has many rates through vendor
96
+ # Rates association through singular association vendor, source association in Vendor (rates) has reverse (vendor in Rate)
97
+ # AS will assign the vendor of the employee to the new Rate
98
+ # 2. source association is singular, e.g.:
99
+ # Customer has many networks, Network has one (or belongs to) firewall, Customer has many firewalls through networks
100
+ # 3. create columns include through association of reverse association, e.g.:
101
+ # Vendor has many employees, Employee has many rates, Vendor has many rates through employees, Rate has one vendor through employee
102
+ # RatesController has employee in create action columns (reverse is vendor, and through association employee is in create form).
97
103
  def readonly_through_association?(columns)
98
104
  return false unless through_association?
99
105
  return true if association.through_reflection.options[:through] # create not possible, too many levels
100
106
  return true if association.source_reflection.options[:through] # create not possible, too many levels
101
107
  return false if create_through_singular? # create allowed, AS has code for this
108
+ return false unless association.source_reflection.collection? # create allowed if source is singular, rails creates joint model
102
109
 
103
110
  # create allowed only if through reflection in record to be created is included in create columns
104
111
  !child_association || !columns.include?(child_association.through_reflection.name)
@@ -121,14 +121,14 @@ module ActiveScaffold::DataStructures
121
121
  end
122
122
 
123
123
  # builds an order-by clause
124
- def clause(grouped_columns_calculations = nil)
124
+ def clause(grouped_columns = nil)
125
125
  return nil if sorts_by_method? || default_sorting?
126
126
 
127
127
  # unless the sorting is by method, create the sql string
128
128
  order = []
129
129
  each do |sort_column, sort_direction|
130
130
  next if constraint_columns.include? sort_column.name
131
- sql = grouped_columns_calculations&.dig(sort_column.name) || sort_column.sort[:sql]
131
+ sql = grouped_columns ? grouped_columns[sort_column.name] : sort_column.sort[:sql]
132
132
  next if sql.blank?
133
133
  sql = sql.to_sql if sql.respond_to?(:to_sql)
134
134
 
@@ -138,7 +138,7 @@ module ActiveScaffold::DataStructures
138
138
  order << parts
139
139
  end
140
140
 
141
- order << @primary_key_order_clause if @sorting_by_primary_key
141
+ order << @primary_key_order_clause if @sorting_by_primary_key && grouped_columns.nil?
142
142
  order.flatten!(1)
143
143
  order unless order.empty?
144
144
  end
@@ -96,11 +96,14 @@ module ActiveScaffold
96
96
  # Generates an SQL condition for the given ActiveScaffold column based on
97
97
  # that column's database type (or form_ui ... for virtual columns?).
98
98
  # TODO: this should reside on the column, not the controller
99
- def condition_for_column(column, value, text_search = :full)
99
+ def condition_for_column(column, value, text_search, session)
100
100
  like_pattern = like_pattern(text_search)
101
101
  value = value.with_indifferent_access if value.is_a? Hash
102
- if respond_to?("condition_for_#{column.name}_column")
103
- return send("condition_for_#{column.name}_column", column, value, like_pattern)
102
+ column_method = "condition_for_#{column.name}_column"
103
+ if respond_to?(column_method)
104
+ args = [column, value, like_pattern]
105
+ args << session if method(column_method).arity == 4
106
+ return send("condition_for_#{column.name}_column", *args)
104
107
  end
105
108
  return unless column&.search_sql && value.present?
106
109
  search_ui = column.search_ui || column.column_type
@@ -363,6 +366,8 @@ module ActiveScaffold
363
366
 
364
367
  if column.search_sql.is_a? Proc
365
368
  column.search_sql.call(from_value, to_value, operator)
369
+ elsif ActiveScaffold::Finder::NULL_COMPARATORS.include?(value['opt'])
370
+ condition_for_null_type(column, value['opt'], like_pattern)
366
371
  elsif operator.nil?
367
372
  ['%<search_sql>s BETWEEN ? AND ?', from_value, to_value] unless from_value.nil? || to_value.nil?
368
373
  else
@@ -572,7 +577,7 @@ module ActiveScaffold
572
577
  def finder_options(options = {})
573
578
  search_conditions = all_conditions
574
579
 
575
- sorting = options[:sorting]&.clause((grouped_columns_calculations if grouped_search?))
580
+ sorting = options[:sorting]&.clause
576
581
  sorting = sorting.map(&Arel.method(:sql)) if sorting && active_scaffold_config.active_record?
577
582
  # create a general-use options array that's compatible with Rails finders
578
583
  finder_options = {
@@ -650,7 +655,7 @@ module ActiveScaffold
650
655
  @last_modified = query.maximum(:updated_at)
651
656
  end
652
657
 
653
- def calculate_query(id_condition = true)
658
+ def calculate_subquery(id_condition = true)
654
659
  conditions = all_conditions(id_condition)
655
660
  includes = active_scaffold_config.list.count_includes
656
661
  includes ||= active_scaffold_references if conditions.present?
@@ -658,8 +663,11 @@ module ActiveScaffold
658
663
  left_joins += includes if includes
659
664
  primary_key = active_scaffold_config.primary_key
660
665
  subquery = append_to_query(beginning_of_chain, :conditions => conditions, :joins => joins_for_finder, :left_joins => left_joins, :select => active_scaffold_config.columns[primary_key].field)
661
- subquery = subquery.unscope(:order)
662
- active_scaffold_config.model.where(primary_key => subquery)
666
+ subquery.unscope(:order)
667
+ end
668
+
669
+ def calculate_query(id_condition = true)
670
+ active_scaffold_config.model.where(active_scaffold_config.primary_key => calculate_subquery(id_condition))
663
671
  end
664
672
 
665
673
  def append_to_query(relation, options)
@@ -99,7 +99,7 @@ module ActiveScaffold
99
99
  def action_link_to_inline_form(link, record)
100
100
  link = link.dup
101
101
  associated = record.send(link.column.association.name)
102
- if link.column.association&.polymorphic?
102
+ if link.column.association&.polymorphic? || link.controller.nil?
103
103
  link.controller = controller_path_for_activerecord(associated.class)
104
104
  return link if link.controller.nil?
105
105
  end
@@ -1,3 +1,4 @@
1
+
1
2
  module ActiveScaffold
2
3
  module Helpers
3
4
  module AssociationHelpers
@@ -12,6 +13,11 @@ module ActiveScaffold
12
13
  end
13
14
  end
14
15
 
16
+ def association_helper_method(association, method)
17
+ model = association.inverse_klass
18
+ override_helper_per_model(method, model)
19
+ end
20
+
15
21
  # Provides a way to honor the :conditions on an association while searching the association's klass
16
22
  def association_options_find(association, conditions = nil, klass = nil, record = nil)
17
23
  if klass.nil? && association.polymorphic?
@@ -24,9 +30,9 @@ module ActiveScaffold
24
30
  klass ||= association.klass
25
31
  end
26
32
 
27
- conditions ||= options_for_association_conditions(association, record)
33
+ conditions ||= send(association_helper_method(association, :options_for_association_conditions), association, record)
28
34
  cache_association_options(association, conditions, klass, cache) do
29
- klass = association_klass_scoped(association, klass, record)
35
+ klass = send(association_helper_method(association, :association_klass_scoped), association, klass, record)
30
36
  relation = klass.where(conditions)
31
37
  column = column_for_association(association, record)
32
38
  if column&.includes
@@ -80,7 +86,7 @@ module ActiveScaffold
80
86
  end
81
87
 
82
88
  def options_for_association_count(association, record)
83
- conditions = options_for_association_conditions(association, record)
89
+ conditions = send(association_helper_method(association, :options_for_association_conditions), association, record)
84
90
  association_options_count(association, conditions)
85
91
  end
86
92
 
@@ -486,7 +486,8 @@ module ActiveScaffold
486
486
  record = html_options.delete(:object)
487
487
  options[:selected] = record.send(column.name)
488
488
  options[:object] = record
489
- options_for_select = active_scaffold_enum_options(column, record, ui_options: ui_options).collect do |text, value|
489
+ enum_options_method = override_helper_per_model(:active_scaffold_enum_options, record.class)
490
+ options_for_select = send(enum_options_method, column, record, ui_options: ui_options).collect do |text, value|
490
491
  active_scaffold_translated_option(column, text, value)
491
492
  end
492
493
  html_options.merge!(ui_options[:html_options] || {})
@@ -535,7 +536,8 @@ module ActiveScaffold
535
536
  if column.association
536
537
  sorted_association_options_find(column.association, nil, record)
537
538
  else
538
- active_scaffold_enum_options(column, record, ui_options: ui_options)
539
+ enum_options_method = override_helper_per_model(:active_scaffold_enum_options, record.class)
540
+ send(enum_options_method, column, record, ui_options: ui_options)
539
541
  end
540
542
 
541
543
  selected = record.send(column.association.name) if column.association
@@ -55,6 +55,8 @@ module ActiveScaffold
55
55
  when 'PAST', 'FUTURE'
56
56
  from, to = controller.class.datetime_from_to(column, value)
57
57
  "#{column.active_record_class.human_attribute_name(column.name)} #{as_('BETWEEN'.downcase).downcase} #{I18n.l(from)} - #{I18n.l(to)}"
58
+ when 'null', 'not_null'
59
+ "#{column.active_record_class.human_attribute_name(column.name)} #{as_(value['opt'].downcase).downcase}"
58
60
  else
59
61
  from, to = controller.class.datetime_from_to(column, value)
60
62
  "#{column.active_record_class.human_attribute_name(column.name)} #{as_(value['opt'].downcase).downcase} #{I18n.l(from)} #{value['opt'] == 'BETWEEN' ? '- ' + I18n.l(to) : ''}"
@@ -226,7 +226,10 @@ module ActiveScaffold
226
226
  I18n.t(options[:group_format] || search_group_function, scope: 'date.formats', num: value)
227
227
  when 'month'
228
228
  I18n.l(Date.new(Time.zone.today.year, value, 1), format: options[:group_format] || search_group_function.to_sym)
229
- else value
229
+ when 'year'
230
+ value.to_i
231
+ else
232
+ value
230
233
  end
231
234
  end
232
235
 
@@ -263,7 +266,7 @@ module ActiveScaffold
263
266
  end
264
267
 
265
268
  def format_association_value(value, column, size)
266
- method = column.options[:label_method] || :to_label
269
+ method = (column.list_ui_options || column.options)[:label_method] || :to_label
267
270
  value =
268
271
  if column.association.collection?
269
272
  format_collection_association_value(value, column, method, size)
@@ -85,7 +85,8 @@ module ActiveScaffold
85
85
  [r.send(method), r.id]
86
86
  end
87
87
  else
88
- select_options = active_scaffold_enum_options(column, record, ui_options: ui_options).collect do |text, value|
88
+ enum_options_method = override_helper_per_model(:active_scaffold_enum_options, record.class)
89
+ select_options = send(enum_options_method, column, record, ui_options: ui_options).collect do |text, value|
89
90
  active_scaffold_translated_option(column, text, value)
90
91
  end
91
92
  end
@@ -103,7 +104,8 @@ module ActiveScaffold
103
104
  select_options = sorted_association_options_find(column.association, false, record)
104
105
  else
105
106
  method = column.name
106
- select_options = active_scaffold_enum_options(column, record, ui_options: ui_options).collect do |text, value|
107
+ enum_options_method = override_helper_per_model(:active_scaffold_enum_options, record.class)
108
+ select_options = send(enum_options_method, column, record, ui_options: ui_options).collect do |text, value|
107
109
  active_scaffold_translated_option(column, text, value)
108
110
  end
109
111
  end
@@ -242,11 +244,13 @@ module ActiveScaffold
242
244
  alias active_scaffold_search_string active_scaffold_search_range
243
245
 
244
246
  def active_scaffold_search_integer(column, options, ui_options: column.options)
245
- active_scaffold_search_range(column, options, :number_field_tag, {step: '1'}, ui_options: ui_options) # rubocop:disable Style/BracesAroundHashParameters
247
+ number_opts = ui_options.slice(:step, :min, :max).reverse_merge(step: '1')
248
+ active_scaffold_search_range(column, options, :number_field_tag, number_opts, ui_options: ui_options)
246
249
  end
247
250
 
248
251
  def active_scaffold_search_decimal(column, options, ui_options: column.options)
249
- active_scaffold_search_range(column, options, :number_field_tag, {step: :any}, ui_options: ui_options) # rubocop:disable Style/BracesAroundHashParameters
252
+ number_opts = ui_options.slice(:step, :min, :max).reverse_merge(step: :any)
253
+ active_scaffold_search_range(column, options, :number_field_tag, number_opts, ui_options: ui_options)
250
254
  end
251
255
  alias active_scaffold_search_float active_scaffold_search_decimal
252
256
 
@@ -255,7 +259,7 @@ module ActiveScaffold
255
259
  'number' => 1, 'unit' => 'DAYS', 'range' => nil}
256
260
  current_search.merge!(options[:value]) unless options[:value].nil?
257
261
  tags = [
258
- active_scaffold_search_datetime_comparator_tag(column, options, current_search),
262
+ active_scaffold_search_datetime_comparator_tag(column, options, current_search, ui_options: column.options),
259
263
  active_scaffold_search_datetime_trend_tag(column, options, current_search),
260
264
  active_scaffold_search_datetime_numeric_tag(column, options, current_search, ui_options: ui_options, field_ui: field_ui),
261
265
  active_scaffold_search_datetime_range_tag(column, options, current_search)
@@ -275,13 +279,17 @@ module ActiveScaffold
275
279
  active_scaffold_search_datetime(column, options, ui_options: ui_options, field_ui: :date)
276
280
  end
277
281
 
278
- def active_scaffold_search_datetime_comparator_options(column)
282
+ def active_scaffold_search_datetime_comparator_options(column, ui_options: column.options)
279
283
  select_options = ActiveScaffold::Finder::DATE_COMPARATORS.collect { |comp| [as_(comp.downcase.to_sym), comp] }
280
- select_options + ActiveScaffold::Finder::NUMERIC_COMPARATORS.collect { |comp| [as_(comp.downcase.to_sym), comp] }
284
+ select_options.concat(ActiveScaffold::Finder::NUMERIC_COMPARATORS.collect { |comp| [as_(comp.downcase.to_sym), comp] })
285
+ if include_null_comparators? column, ui_options: ui_options
286
+ select_options.concat(ActiveScaffold::Finder::NULL_COMPARATORS.collect { |comp| [as_(comp), comp] })
287
+ end
288
+ select_options
281
289
  end
282
290
 
283
- def active_scaffold_search_datetime_comparator_tag(column, options, current_search)
284
- choices = options_for_select(active_scaffold_search_datetime_comparator_options(column), current_search['opt'])
291
+ def active_scaffold_search_datetime_comparator_tag(column, options, current_search, ui_options: column.options)
292
+ choices = options_for_select(active_scaffold_search_datetime_comparator_options(column, ui_options: ui_options), current_search['opt'])
285
293
  select_tag("#{options[:name]}[opt]", choices, id: "#{options[:id]}_opt", class: 'as_search_range_option as_search_date_time_option')
286
294
  end
287
295
 
@@ -76,15 +76,9 @@ module ActiveScaffold
76
76
  link_to label, '#', :data => data, :style => 'display: none;', :class => 'as-js-button visibility-toggle'
77
77
  end
78
78
 
79
- def list_row_class_method(record)
80
- return @_list_row_class_method if defined? @_list_row_class_method
81
- class_override_helper = "#{clean_class_name(record.class.name)}_list_row_class"
82
- @_list_row_class_method = (class_override_helper if respond_to?(class_override_helper))
83
- end
84
-
85
79
  def list_row_class(record)
86
- class_override_helper = list_row_class_method(record)
87
- class_override_helper ? send(class_override_helper, record) : ''
80
+ class_override_helper = override_helper_per_model(:list_row_class, record.class)
81
+ class_override_helper != :list_row_class ? send(class_override_helper, record) : ''
88
82
  end
89
83
 
90
84
  def list_row_attributes(tr_class, tr_id, data_refresh)
@@ -159,21 +153,22 @@ module ActiveScaffold
159
153
  name.underscore.tr('/', '_')
160
154
  end
161
155
 
162
- # the naming convention for overriding with helpers
163
- def override_helper_name(column, suffix, class_prefix = false)
164
- "#{clean_class_name(column.active_record_class.name) + '_' if class_prefix}#{clean_column_name(column.name)}_#{suffix}"
156
+ def override_helper_per_model(method, model, cache_keys = nil)
157
+ cache_keys ||= [method, model.name]
158
+ ActiveScaffold::Registry.cache(*cache_keys) do
159
+ model_names = [model.name]
160
+ model_names << model.base_class.name if model.base_class != model
161
+ method_with_class = model_names.find do |model_name|
162
+ method_with_class = "#{clean_class_name(model_name)}_#{method}"
163
+ break method_with_class if respond_to?(method_with_class)
164
+ end
165
+ method_with_class || (method if respond_to?(method))
166
+ end
165
167
  end
166
168
 
167
169
  def override_helper(column, suffix)
168
- ActiveScaffold::Registry.cache suffix, column.cache_key do
169
- method_with_class = override_helper_name(column, suffix, true)
170
- if respond_to?(method_with_class)
171
- method_with_class
172
- else
173
- method = override_helper_name(column, suffix)
174
- method if respond_to?(method)
175
- end
176
- end
170
+ method = "#{clean_column_name(column.name)}_#{suffix}"
171
+ override_helper_per_model(method, column.active_record_class, [suffix, column.cache_key])
177
172
  end
178
173
 
179
174
  def history_state
@@ -2,7 +2,7 @@ module ActiveScaffold
2
2
  module Version
3
3
  MAJOR = 3
4
4
  MINOR = 7
5
- PATCH = 4
5
+ PATCH = 6
6
6
  FIX = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, PATCH, FIX].compact.join('.')
@@ -2,10 +2,11 @@ require 'test_helper'
2
2
 
3
3
  class FormColumnHelpersTest < ActionView::TestCase
4
4
  include ActiveScaffold::Helpers::FormColumnHelpers
5
+ include ActiveScaffold::Helpers::ViewHelpers
5
6
 
6
7
  def setup
7
8
  @column = ActiveScaffold::DataStructures::Column.new(:a, ModelStub)
8
- @record = stub(:a => nil)
9
+ @record = ModelStub.new(a: nil)
9
10
  end
10
11
 
11
12
  def test_choices_for_select_form_ui_for_simple_column
@@ -75,13 +75,13 @@ class FinderTest < Minitest::Test
75
75
 
76
76
  def test_condition_for_column
77
77
  column = ActiveScaffold::DataStructures::Column.new('adult', Person)
78
- assert_equal ['"people"."adult" = ?', false], ClassWithFinder.condition_for_column(column, '0')
78
+ assert_equal ['"people"."adult" = ?', false], ClassWithFinder.condition_for_column(column, '0', :full, {})
79
79
  end
80
80
 
81
81
  def test_condition_for_polymorphic_column
82
82
  column = ActiveScaffold::DataStructures::Column.new('addressable', Address)
83
83
  column.search_sql = [{subquery: [Building, 'name']}]
84
- condition = ClassWithFinder.condition_for_column(column, 'test search')
84
+ condition = ClassWithFinder.condition_for_column(column, 'test search', :full, {})
85
85
  assert_equal Building.where(['name LIKE ?', '%test search%']).select(:id).to_sql, condition[1].to_sql
86
86
  assert_equal '"addresses"."addressable_id" IN (?) AND "addresses"."addressable_type" = ?', condition[0]
87
87
  assert_equal ['Building'], condition[2..-1]
@@ -90,7 +90,7 @@ class FinderTest < Minitest::Test
90
90
  def test_condition_for_polymorphic_column_with_relation
91
91
  column = ActiveScaffold::DataStructures::Column.new('contactable', Contact)
92
92
  column.search_sql = [{subquery: [Person.joins(:buildings), 'first_name', 'last_name']}]
93
- condition = ClassWithFinder.condition_for_column(column, 'test search')
93
+ condition = ClassWithFinder.condition_for_column(column, 'test search', :full, {})
94
94
  assert_equal Person.joins(:buildings).where(['first_name LIKE ? OR last_name LIKE ?', '%test search%', '%test search%']).select(:id).to_sql, condition[1].to_sql
95
95
  assert_equal '"contacts"."contactable_id" IN (?) AND "contacts"."contactable_type" = ?', condition[0]
96
96
  assert_equal ['Person'], condition[2..-1]
@@ -100,7 +100,7 @@ class FinderTest < Minitest::Test
100
100
  column = ActiveScaffold::DataStructures::Column.new('owner', Building)
101
101
  column.search_sql = [{subquery: [Person, 'first_name', 'last_name'], conditions: ['floor_count > 0']}]
102
102
  column.search_ui = :text
103
- condition = ClassWithFinder.condition_for_column(column, 'test search')
103
+ condition = ClassWithFinder.condition_for_column(column, 'test search', :full, {})
104
104
  assert_equal Person.where(['first_name LIKE ? OR last_name LIKE ?', '%test search%', '%test search%']).select(:id).to_sql, condition[1].to_sql
105
105
  assert_equal '"buildings"."owner_id" IN (?) AND floor_count > 0', condition[0]
106
106
  assert_equal [], condition[2..-1]
@@ -110,7 +110,7 @@ class FinderTest < Minitest::Test
110
110
  column = ActiveScaffold::DataStructures::Column.new('owner', Building)
111
111
  column.search_sql = [{subquery: [Person, 'first_name', 'last_name'], conditions: ['floor_count > 0 AND name != ?', '']}]
112
112
  column.search_ui = :text
113
- condition = ClassWithFinder.condition_for_column(column, 'test search')
113
+ condition = ClassWithFinder.condition_for_column(column, 'test search', :full, {})
114
114
  assert_equal Person.where(['first_name LIKE ? OR last_name LIKE ?', '%test search%', '%test search%']).select(:id).to_sql, condition[1].to_sql
115
115
  assert_equal '"buildings"."owner_id" IN (?) AND floor_count > 0 AND name != ?', condition[0]
116
116
  assert_equal [''], condition[2..-1]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_scaffold
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.7.4
4
+ version: 3.7.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Many, see README
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-20 00:00:00.000000000 Z
11
+ date: 2024-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails