active_scaffold 3.7.4 → 3.7.6

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 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