active_scaffold 3.5.4 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGELOG → CHANGELOG.rdoc} +72 -0
  3. data/README.md +20 -8
  4. data/app/assets/javascripts/active_scaffold.js.erb +0 -1
  5. data/app/assets/javascripts/jquery/active_scaffold.js +98 -7
  6. data/app/assets/stylesheets/active_scaffold_colors.scss +1 -1
  7. data/app/assets/stylesheets/active_scaffold_layout.css +52 -29
  8. data/app/views/active_scaffold_overrides/_base_form.html.erb +2 -2
  9. data/app/views/active_scaffold_overrides/_form.html.erb +1 -1
  10. data/app/views/active_scaffold_overrides/_form_association.html.erb +2 -1
  11. data/app/views/active_scaffold_overrides/_form_association_footer.html.erb +3 -2
  12. data/app/views/active_scaffold_overrides/_form_association_record.html.erb +9 -7
  13. data/app/views/active_scaffold_overrides/_horizontal_subform.html.erb +4 -4
  14. data/app/views/active_scaffold_overrides/_horizontal_subform_header.html.erb +2 -1
  15. data/app/views/active_scaffold_overrides/_list.html.erb +2 -1
  16. data/app/views/active_scaffold_overrides/_list_header.html.erb +5 -7
  17. data/app/views/active_scaffold_overrides/_list_messages.html.erb +1 -0
  18. data/app/views/active_scaffold_overrides/_list_record.html.erb +4 -5
  19. data/app/views/active_scaffold_overrides/_list_with_header.html.erb +1 -1
  20. data/app/views/active_scaffold_overrides/_messages.html.erb +1 -0
  21. data/app/views/active_scaffold_overrides/_refresh_list.js.erb +4 -0
  22. data/app/views/active_scaffold_overrides/_render_field.js.erb +2 -1
  23. data/app/views/active_scaffold_overrides/_show_association_horizontal.html.erb +2 -1
  24. data/app/views/active_scaffold_overrides/_show_columns.html.erb +2 -2
  25. data/app/views/active_scaffold_overrides/_show_horizontal_record.html.erb +4 -4
  26. data/app/views/active_scaffold_overrides/_update_calculations.js.erb +1 -1
  27. data/app/views/active_scaffold_overrides/_update_column.js.erb +2 -2
  28. data/app/views/active_scaffold_overrides/_vertical_subform.html.erb +2 -2
  29. data/app/views/active_scaffold_overrides/action_confirmation.html.erb +2 -2
  30. data/app/views/active_scaffold_overrides/delete.html.erb +2 -2
  31. data/app/views/active_scaffold_overrides/on_action_update.js.erb +16 -6
  32. data/app/views/active_scaffold_overrides/on_update.js.erb +1 -1
  33. data/app/views/active_scaffold_overrides/row.js.erb +1 -1
  34. data/app/views/active_scaffold_overrides/update_column.js.erb +2 -2
  35. data/config/locales/de.yml +2 -1
  36. data/config/locales/en.yml +1 -0
  37. data/config/locales/es.yml +1 -0
  38. data/config/locales/fr.yml +2 -1
  39. data/config/locales/hu.yml +1 -0
  40. data/config/locales/ja.yml +1 -0
  41. data/config/locales/ru.yml +1 -0
  42. data/lib/active_scaffold.rb +19 -16
  43. data/lib/active_scaffold/actions/common_search.rb +11 -8
  44. data/lib/active_scaffold/actions/core.rb +91 -70
  45. data/lib/active_scaffold/actions/create.rb +28 -28
  46. data/lib/active_scaffold/actions/delete.rb +3 -3
  47. data/lib/active_scaffold/actions/field_search.rb +53 -43
  48. data/lib/active_scaffold/actions/list.rb +111 -27
  49. data/lib/active_scaffold/actions/nested.rb +65 -48
  50. data/lib/active_scaffold/actions/search.rb +1 -1
  51. data/lib/active_scaffold/actions/show.rb +4 -4
  52. data/lib/active_scaffold/actions/subform.rb +23 -22
  53. data/lib/active_scaffold/actions/update.rb +96 -77
  54. data/lib/active_scaffold/active_record_permissions.rb +2 -11
  55. data/lib/active_scaffold/attribute_params.rb +102 -94
  56. data/lib/active_scaffold/bridges.rb +8 -8
  57. data/lib/active_scaffold/bridges/active_storage.rb +6 -0
  58. data/lib/active_scaffold/bridges/active_storage/active_storage_bridge.rb +34 -0
  59. data/lib/active_scaffold/bridges/active_storage/active_storage_helpers.rb +54 -0
  60. data/lib/active_scaffold/bridges/active_storage/form_ui.rb +22 -0
  61. data/lib/active_scaffold/bridges/active_storage/list_ui.rb +36 -0
  62. data/lib/active_scaffold/bridges/bitfields.rb +2 -1
  63. data/lib/active_scaffold/bridges/bitfields/bitfields_bridge.rb +12 -15
  64. data/lib/active_scaffold/bridges/bitfields/list_ui.rb +19 -0
  65. data/lib/active_scaffold/bridges/calendar_date_select/as_cds_bridge.rb +1 -1
  66. data/lib/active_scaffold/bridges/cancan/cancan_bridge.rb +9 -12
  67. data/lib/active_scaffold/bridges/carrierwave/carrierwave_bridge.rb +1 -1
  68. data/lib/active_scaffold/bridges/carrierwave/list_ui.rb +2 -2
  69. data/lib/active_scaffold/bridges/chosen/helpers.rb +7 -6
  70. data/lib/active_scaffold/bridges/date_picker/ext.rb +0 -13
  71. data/lib/active_scaffold/bridges/date_picker/helper.rb +49 -44
  72. data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +1 -1
  73. data/lib/active_scaffold/bridges/file_column/as_file_column_bridge.rb +1 -1
  74. data/lib/active_scaffold/bridges/file_column/file_column_helpers.rb +3 -3
  75. data/lib/active_scaffold/bridges/file_column/form_ui.rb +3 -3
  76. data/lib/active_scaffold/bridges/file_column/test/functional/file_column_keep_test.rb +10 -7
  77. data/lib/active_scaffold/bridges/paper_trail.rb +1 -1
  78. data/lib/active_scaffold/bridges/paper_trail/actions.rb +3 -1
  79. data/lib/active_scaffold/bridges/paperclip/list_ui.rb +1 -1
  80. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge.rb +1 -1
  81. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge_helpers.rb +2 -2
  82. data/lib/active_scaffold/bridges/record_select/helpers.rb +15 -17
  83. data/lib/active_scaffold/bridges/shared/date_bridge.rb +20 -19
  84. data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +3 -1
  85. data/lib/active_scaffold/bridges/usa_state_select/usa_state_select_helper.rb +21 -4
  86. data/lib/active_scaffold/config/base.rb +133 -41
  87. data/lib/active_scaffold/config/core.rb +146 -18
  88. data/lib/active_scaffold/config/delete.rb +14 -1
  89. data/lib/active_scaffold/config/field_search.rb +7 -1
  90. data/lib/active_scaffold/config/form.rb +10 -1
  91. data/lib/active_scaffold/config/list.rb +39 -13
  92. data/lib/active_scaffold/config/mark.rb +4 -2
  93. data/lib/active_scaffold/config/nested.rb +16 -17
  94. data/lib/active_scaffold/config/search.rb +9 -0
  95. data/lib/active_scaffold/config/show.rb +4 -0
  96. data/lib/active_scaffold/config/update.rb +4 -0
  97. data/lib/active_scaffold/configurable.rb +14 -7
  98. data/lib/active_scaffold/constraints.rb +22 -20
  99. data/lib/active_scaffold/core.rb +67 -28
  100. data/lib/active_scaffold/data_structures/action_columns.rb +50 -59
  101. data/lib/active_scaffold/data_structures/action_link.rb +50 -20
  102. data/lib/active_scaffold/data_structures/action_links.rb +15 -13
  103. data/lib/active_scaffold/data_structures/association/abstract.rb +38 -15
  104. data/lib/active_scaffold/data_structures/association/active_mongoid.rb +2 -6
  105. data/lib/active_scaffold/data_structures/association/active_record.rb +6 -2
  106. data/lib/active_scaffold/data_structures/association/mongoid.rb +0 -3
  107. data/lib/active_scaffold/data_structures/column.rb +75 -66
  108. data/lib/active_scaffold/data_structures/columns.rb +3 -2
  109. data/lib/active_scaffold/data_structures/nested_info.rb +33 -19
  110. data/lib/active_scaffold/data_structures/set.rb +8 -0
  111. data/lib/active_scaffold/data_structures/sorting.rb +10 -2
  112. data/lib/active_scaffold/delayed_setup.rb +16 -5
  113. data/lib/active_scaffold/extensions/action_controller_rendering.rb +3 -2
  114. data/lib/active_scaffold/extensions/action_view_rendering.rb +93 -32
  115. data/lib/active_scaffold/extensions/cow_proxy.rb +95 -0
  116. data/lib/active_scaffold/extensions/ice_nine.rb +36 -0
  117. data/lib/active_scaffold/extensions/left_outer_joins.rb +8 -33
  118. data/lib/active_scaffold/extensions/localize.rb +3 -1
  119. data/lib/active_scaffold/extensions/routing_mapper.rb +6 -45
  120. data/lib/active_scaffold/extensions/to_label.rb +3 -2
  121. data/lib/active_scaffold/extensions/unsaved_record.rb +2 -4
  122. data/lib/active_scaffold/finder.rb +110 -77
  123. data/lib/active_scaffold/helpers/action_link_helpers.rb +62 -36
  124. data/lib/active_scaffold/helpers/association_helpers.rb +18 -16
  125. data/lib/active_scaffold/helpers/controller_helpers.rb +34 -10
  126. data/lib/active_scaffold/helpers/form_column_helpers.rb +196 -124
  127. data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -1
  128. data/lib/active_scaffold/helpers/id_helpers.rb +6 -2
  129. data/lib/active_scaffold/helpers/list_column_helpers.rb +90 -57
  130. data/lib/active_scaffold/helpers/pagination_helpers.rb +2 -2
  131. data/lib/active_scaffold/helpers/search_column_helpers.rb +29 -34
  132. data/lib/active_scaffold/helpers/show_column_helpers.rb +3 -5
  133. data/lib/active_scaffold/helpers/view_helpers.rb +39 -36
  134. data/lib/active_scaffold/marked_model.rb +2 -2
  135. data/lib/active_scaffold/orm_checks.rb +3 -7
  136. data/lib/active_scaffold/paginator.rb +7 -7
  137. data/lib/active_scaffold/registry.rb +33 -0
  138. data/lib/active_scaffold/responds_to_parent.rb +8 -11
  139. data/lib/active_scaffold/tableless.rb +82 -66
  140. data/lib/active_scaffold/version.rb +2 -2
  141. data/lib/generators/active_scaffold/controller_generator.rb +2 -2
  142. data/lib/generators/active_scaffold/install_generator.rb +52 -4
  143. data/lib/generators/active_scaffold/resource_generator.rb +2 -2
  144. data/shoulda_macros/macros.rb +3 -1
  145. data/test/bridges/date_picker_test.rb +1 -2
  146. data/test/bridges/paperclip_test.rb +6 -6
  147. data/test/class_with_finder.rb +2 -2
  148. data/test/company.rb +4 -4
  149. data/test/config/create_test.rb +4 -2
  150. data/test/config/nested_test.rb +1 -1
  151. data/test/config/show_test.rb +1 -1
  152. data/test/config/update_test.rb +7 -6
  153. data/test/data_structures/action_columns_test.rb +2 -2
  154. data/test/data_structures/action_links_test.rb +1 -1
  155. data/test/data_structures/column_test.rb +3 -6
  156. data/test/data_structures/columns_test.rb +2 -2
  157. data/test/data_structures/sorting_test.rb +7 -0
  158. data/test/extensions/action_view_rendering_test.rb +20 -0
  159. data/test/extensions/active_record_test.rb +4 -4
  160. data/test/extensions/routing_mapper_test.rb +2 -2
  161. data/test/helpers/list_column_helpers_test.rb +3 -1
  162. data/test/misc/active_record_permissions_test.rb +3 -11
  163. data/test/misc/attribute_params_test.rb +12 -8
  164. data/test/misc/calculation_test.rb +1 -1
  165. data/test/misc/configurable_test.rb +10 -10
  166. data/test/misc/constraints_test.rb +2 -2
  167. data/test/misc/convert_numbers_format_test.rb +7 -3
  168. data/test/misc/lang_test.rb +1 -1
  169. data/test/misc/parse_datetime_test.rb +3 -4
  170. data/test/misc/tableless_test.rb +14 -0
  171. data/test/mock_app/Rakefile +1 -1
  172. data/test/mock_app/app/assets/config/manifest.js +0 -0
  173. data/test/mock_app/app/controllers/cars_controller.rb +1 -0
  174. data/test/mock_app/app/controllers/people_controller.rb +5 -1
  175. data/test/mock_app/app/controllers/roles_controller.rb +4 -0
  176. data/test/mock_app/app/views/active_scaffold_overrides/_form.html.erb +2 -0
  177. data/test/mock_app/app/views/active_scaffold_overrides/list.html.erb +2 -0
  178. data/test/mock_app/app/views/people/_first_name_form_column.html.erb +2 -0
  179. data/test/mock_app/app/views/people/_form.html.erb +2 -0
  180. data/test/mock_app/app/views/people/list.html.erb +2 -0
  181. data/test/mock_app/config/application.rb +2 -1
  182. data/test/mock_app/config/boot.rb +1 -1
  183. data/test/mock_app/config/environment.rb +2 -2
  184. data/test/mock_app/config/routes.rb +4 -1
  185. data/test/mock_app/db/schema.rb +2 -0
  186. data/test/performance/list_cars_performance_test.rb +34 -0
  187. data/test/performance/list_people_performance_test.rb +31 -0
  188. data/test/performance_test_help.rb +3 -0
  189. data/test/test_helper.rb +12 -4
  190. metadata +69 -18
  191. data/app/assets/javascripts/prototype/rico_corner.js +0 -370
  192. data/lib/active_scaffold/bridges/file_column/test/test_helper.rb +0 -7
@@ -0,0 +1,36 @@
1
+ module IceNine
2
+ class Freezer
3
+ def self.find(name)
4
+ freezer = name.split('::').reduce(self) do |mod, const|
5
+ mod.const_lookup(const) or break mod # rubocop:disable Style/AndOr
6
+ end
7
+ freezer if freezer < self # only return a descendant freezer
8
+ end
9
+
10
+ class ObjectWithExclussion < Object
11
+ class_attribute :excluded_vars
12
+ self.excluded_vars = []
13
+ def self.freeze_instance_variables(object, recursion_guard)
14
+ object.instance_variables.each do |ivar_name|
15
+ next if excluded_vars.include? ivar_name
16
+ Freezer.guarded_deep_freeze(
17
+ object.instance_variable_get(ivar_name),
18
+ recursion_guard
19
+ )
20
+ end
21
+ end
22
+ private_class_method :freeze_instance_variables
23
+ end
24
+
25
+ class ActiveScaffold < ::IceNine::Freezer::Object
26
+ class DataStructures < ::IceNine::Freezer::Object
27
+ class Column < ::IceNine::Freezer::ObjectWithExclussion
28
+ self.excluded_vars = %i[@active_record_class @column]
29
+ end
30
+
31
+ class Association < ::IceNine::Freezer::NoFreeze
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -18,11 +18,6 @@ if Rails.version < '5.0.0'
18
18
  end
19
19
  alias left_joins left_outer_joins
20
20
 
21
- def outer_joins(*args)
22
- ActiveSupport::Deprecation.warn 'use left_outer_joins or left_joins which is added to Rails 5.0.0'
23
- left_outer_joins(*args)
24
- end
25
-
26
21
  def left_outer_joins!(*args)
27
22
  self.joins_values += [''] # HACK: for using left_outer_joins in update_all/delete_all
28
23
  self.left_outer_joins_values += args
@@ -30,39 +25,19 @@ if Rails.version < '5.0.0'
30
25
  end
31
26
  alias left_joins! left_outer_joins!
32
27
 
33
- def outer_joins!(*args)
34
- ActiveSupport::Deprecation.warn 'use left_outer_joins! or left_joins! which is added to Rails 5.0.0'
35
- left_outer_joins!(*args)
36
- end
37
-
38
- if Rails.version < '4.1'
39
- def build_arel
40
- if left_outer_joins_values.empty?
41
- super
42
- else
43
- relation = except(:left_outer_joins)
44
- join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, left_outer_joins_values, [])
45
- join_dependency.join_associations.each do |association|
46
- relation = association.join_relation(relation)
47
- end
48
- relation.build_arel
49
- end
50
- end
51
- else
52
- def build_arel
53
- if left_outer_joins_values.empty?
54
- super
55
- else
56
- relation = except(:left_outer_joins)
57
- relation.joins! ActiveRecord::Associations::JoinDependency.new(@klass, left_outer_joins_values, [])
58
- relation.build_arel
59
- end
28
+ def build_arel
29
+ if left_outer_joins_values.empty?
30
+ super
31
+ else
32
+ relation = except(:left_outer_joins)
33
+ relation.joins! ActiveRecord::Associations::JoinDependency.new(@klass, left_outer_joins_values, [])
34
+ relation.build_arel
60
35
  end
61
36
  end
62
37
  end
63
38
  end
64
39
  ActiveRecord::Relation.send :include, ActiveScaffold::OuterJoins
65
40
  module ActiveRecord::Querying
66
- delegate :left_outer_joins, :left_joins, :outer_joins, :to => :all
41
+ delegate :left_outer_joins, :left_joins, :to => :all
67
42
  end
68
43
  end
@@ -1,7 +1,9 @@
1
1
  class Object
2
2
  def as_(key, options = {})
3
3
  if key.present?
4
- text = I18n.translate(key.to_s, {:scope => [:active_scaffold, *options.delete(:scope)], :default => key.is_a?(String) ? key : key.to_s.titleize}.merge(options)).html_safe
4
+ scope = [:active_scaffold, *options.delete(:scope)]
5
+ options = options.reverse_merge(:scope => scope, :default => key.is_a?(String) ? key : key.to_s.titleize)
6
+ text = I18n.translate(key.to_s, **options).html_safe # rubocop:disable Rails/OutputSafety
5
7
  # text = nil if text.include?('translation missing:')
6
8
  end
7
9
  text || key
@@ -16,9 +16,9 @@ module ActiveScaffold
16
16
 
17
17
  def get_actions(actions_hash, options)
18
18
  default_actions = default_actions(actions_hash)
19
- if only = options[:only]
19
+ if (only = options[:only])
20
20
  Array(only).map(&:to_sym)
21
- elsif except = options[:except]
21
+ elsif (except = options[:except])
22
22
  default_actions - Array(except).map(&:to_sym)
23
23
  else
24
24
  default_actions
@@ -29,13 +29,13 @@ module ActiveScaffold
29
29
  actions = get_actions(ACTIVE_SCAFFOLD_ASSOCIATION_ROUTING, options)
30
30
 
31
31
  mapper.collection do
32
- ActionDispatch::Routing::ACTIVE_SCAFFOLD_ASSOCIATION_ROUTING[:collection].each do |name, type|
32
+ ActiveScaffold::Routing::ACTIVE_SCAFFOLD_ASSOCIATION_ROUTING[:collection].each do |name, type|
33
33
  mapper.match(name, via: type) if actions.include? name
34
34
  end
35
35
  end
36
36
 
37
37
  mapper.member do
38
- ActionDispatch::Routing::ACTIVE_SCAFFOLD_ASSOCIATION_ROUTING[:member].each do |name, type|
38
+ ActiveScaffold::Routing::ACTIVE_SCAFFOLD_ASSOCIATION_ROUTING[:member].each do |name, type|
39
39
  mapper.match(name, via: type) if actions.include? name
40
40
  end
41
41
  end
@@ -52,13 +52,13 @@ module ActiveScaffold
52
52
  actions = get_actions(ACTIVE_SCAFFOLD_CORE_ROUTING, options)
53
53
 
54
54
  mapper.collection do
55
- ActionDispatch::Routing::ACTIVE_SCAFFOLD_CORE_ROUTING[:collection].each do |name, type|
55
+ ActiveScaffold::Routing::ACTIVE_SCAFFOLD_CORE_ROUTING[:collection].each do |name, type|
56
56
  mapper.match(name, via: type) if actions.include? name
57
57
  end
58
58
  end
59
59
 
60
60
  mapper.member do
61
- ActionDispatch::Routing::ACTIVE_SCAFFOLD_CORE_ROUTING[:member].each do |name, type|
61
+ ActiveScaffold::Routing::ACTIVE_SCAFFOLD_CORE_ROUTING[:member].each do |name, type|
62
62
  mapper.match(name, via: type) if actions.include? name
63
63
  end
64
64
  mapper.get 'list', action: :index if mapper.send(:parent_resource).actions.include? :index
@@ -72,47 +72,8 @@ end
72
72
 
73
73
  module ActionDispatch
74
74
  module Routing
75
- ACTIVE_SCAFFOLD_CORE_ROUTING = ActiveScaffold::Routing::ACTIVE_SCAFFOLD_CORE_ROUTING
76
- ACTIVE_SCAFFOLD_ASSOCIATION_ROUTING = ActiveScaffold::Routing::ACTIVE_SCAFFOLD_ASSOCIATION_ROUTING
77
-
78
75
  class Mapper
79
76
  module Resources
80
- def parent_options
81
- opts = parent_resource.instance_variable_get(:@options)
82
- if Rails.version >= '5.0.0'
83
- opts.merge(
84
- only: parent_resource.instance_variable_get(:@only),
85
- except: parent_resource.instance_variable_get(:@except)
86
- )
87
- end
88
- opts
89
- end
90
-
91
- def define_active_scaffold_concern
92
- ActiveSupport::Deprecation.warn 'Add concern :active_scaffold, ActiveScaffold::Routing::Basic.new(association: true) to your routes file.'
93
- concern :active_scaffold, ActiveScaffold::Routing::Basic.new(association: true)
94
- end
95
-
96
- def define_active_scaffold_association_concern
97
- ActiveSupport::Deprecation.warn 'Add concern :active_scaffold_association, ActiveScaffold::Routing::Association.new to your routes file.'
98
- concern :active_scaffold_association, ActiveScaffold::Routing::Association.new
99
- end
100
-
101
- def as_routes(opts = {association: true})
102
- define_active_scaffold_concern unless @concerns[:active_scaffold]
103
- if opts[:association] && !@concerns[:active_scaffold_association]
104
- define_active_scaffold_association_concern
105
- end
106
- ActiveSupport::Deprecation.warn 'Use concerns: :active_scaffold in resources instead of as_routes, or concerns :active_scaffold in resources block if want to disable association routes or restrict routes with only or except options.'
107
- concerns :active_scaffold, parent_options.merge(association: opts[:association])
108
- end
109
-
110
- def as_association_routes
111
- define_active_scaffold_association_concern unless @concerns[:active_scaffold_association]
112
- ActiveSupport::Deprecation.warn 'Use concerns: :active_scaffold_association in resources instead of as_association_routes, or concerns :active_scaffold_association in resources block if want to restrict routes with only or except options.'
113
- concerns :active_scaffold_association, parent_options
114
- end
115
-
116
77
  def as_nested_resources(*resources)
117
78
  options = resources.extract_options!
118
79
  nested_options = options.merge(parent_scaffold: parent_scaffold)
@@ -1,8 +1,9 @@
1
1
  # the ever-useful to_label method
2
2
  class ActiveRecord::Base
3
3
  def to_label
4
- %i[name label title to_s].each do |attribute|
5
- return send(attribute).to_s if respond_to?(attribute)
4
+ to_label_method = ActiveScaffold::Registry.cache :to_label, self.class.name do
5
+ %i[name label title to_s].find { |attribute| respond_to?(attribute) }
6
6
  end
7
+ send(to_label_method).to_s if to_label_method
7
8
  end
8
9
  end
@@ -18,10 +18,8 @@ module ActiveScaffold::UnsavedRecord
18
18
  def keeping_errors
19
19
  old_errors = errors.dup if errors.present?
20
20
  result = yield
21
- if old_errors
22
- old_errors.each do |attr|
23
- old_errors[attr].each { |msg| errors.add(attr, msg) unless errors.added?(attr, msg) }
24
- end
21
+ old_errors&.each do |attr|
22
+ old_errors[attr].each { |msg| errors.add(attr, msg) unless errors.added?(attr, msg) }
25
23
  end
26
24
  result && old_errors.blank?
27
25
  end
@@ -1,7 +1,7 @@
1
1
  module ActiveScaffold
2
2
  module Finder
3
3
  def self.like_operator
4
- @@like_operator ||= ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' ? 'ILIKE' : 'LIKE'
4
+ @@like_operator ||= ::ActiveRecord::Base.connection.adapter_name.in?(%w[PostgreSQL PostGIS]) ? 'ILIKE' : 'LIKE'
5
5
  end
6
6
 
7
7
  module ClassMethods
@@ -68,7 +68,9 @@ module ActiveScaffold
68
68
  value = columns_token[column.name]
69
69
  value = /#{value}/ if column.text?
70
70
  column.search_sql.map do |search_sql|
71
- {search_sql => value}
71
+ # call .to_s so String is returned from CowProxy::String in threadsafe mode
72
+ # in other case, or method from Mongoid would fail
73
+ {search_sql.to_s => value}
72
74
  end
73
75
  end.flatten
74
76
  active_scaffold_config.model.or(token_conditions).selector
@@ -95,18 +97,16 @@ module ActiveScaffold
95
97
  if respond_to?("condition_for_#{column.name}_column")
96
98
  return send("condition_for_#{column.name}_column", column, value, like_pattern)
97
99
  end
98
- return unless column && column.search_sql && value.present?
100
+ return unless column&.search_sql && value.present?
99
101
  search_ui = column.search_ui || column.column_type
100
102
  begin
101
103
  sql, *values =
102
104
  if search_ui && respond_to?("condition_for_#{search_ui}_type")
103
105
  send("condition_for_#{search_ui}_type", column, value, like_pattern)
106
+ elsif column.search_sql.instance_of? Proc
107
+ column.search_sql.call(value)
104
108
  else
105
- if column.search_sql.instance_of? Proc
106
- column.search_sql.call(value)
107
- else
108
- condition_for_search_ui(column, value, like_pattern, search_ui)
109
- end
109
+ condition_for_search_ui(column, value, like_pattern, search_ui)
110
110
  end
111
111
  return nil unless sql
112
112
 
@@ -122,7 +122,7 @@ module ActiveScaffold
122
122
  def condition_for_search_ui(column, value, like_pattern, search_ui)
123
123
  case search_ui
124
124
  when :boolean, :checkbox
125
- ['%{search_sql} = ?', column.column ? ActiveScaffold::Core.column_type_cast(value, column.column) : value]
125
+ ['%<search_sql>s = ?', column.column ? ActiveScaffold::Core.column_type_cast(value, column.column) : value]
126
126
  when :integer, :decimal, :float
127
127
  condition_for_numeric(column, value)
128
128
  when :string, :range
@@ -131,57 +131,59 @@ module ActiveScaffold
131
131
  condition_for_datetime(column, value)
132
132
  when :select, :multi_select, :country, :usa_state, :chosen, :multi_chosen
133
133
  values = Array(value).select(&:present?)
134
- ['%{search_sql} in (?)', values] if values.present?
134
+ ['%<search_sql>s in (?)', values] if values.present?
135
135
  else
136
136
  if column.text?
137
- ["%{search_sql} #{ActiveScaffold::Finder.like_operator} ?", like_pattern.sub('?', value)]
137
+ ["%<search_sql>s #{ActiveScaffold::Finder.like_operator} ?", like_pattern.sub('?', value)]
138
138
  else
139
- ['%{search_sql} = ?', ActiveScaffold::Core.column_type_cast(value, column.column)]
139
+ ['%<search_sql>s = ?', ActiveScaffold::Core.column_type_cast(value, column.column)]
140
140
  end
141
141
  end
142
142
  end
143
143
 
144
144
  def condition_for_numeric(column, value)
145
145
  if !value.is_a?(Hash)
146
- ['%{search_sql} = ?', condition_value_for_numeric(column, value)]
146
+ ['%<search_sql>s = ?', condition_value_for_numeric(column, value)]
147
147
  elsif ActiveScaffold::Finder::NULL_COMPARATORS.include?(value[:opt])
148
148
  condition_for_null_type(column, value[:opt])
149
149
  elsif value[:from].blank? || !ActiveScaffold::Finder::NUMERIC_COMPARATORS.include?(value[:opt])
150
150
  nil
151
151
  elsif value[:opt] == 'BETWEEN'
152
- ['(%{search_sql} BETWEEN ? AND ?)', condition_value_for_numeric(column, value[:from]), condition_value_for_numeric(column, value[:to])]
152
+ ['(%<search_sql>s BETWEEN ? AND ?)', condition_value_for_numeric(column, value[:from]), condition_value_for_numeric(column, value[:to])]
153
153
  else
154
- ["%{search_sql} #{value[:opt]} ?", condition_value_for_numeric(column, value[:from])]
154
+ ["%<search_sql>s #{value[:opt]} ?", condition_value_for_numeric(column, value[:from])]
155
155
  end
156
156
  end
157
157
 
158
158
  def condition_for_range(column, value, like_pattern = nil)
159
159
  if !value.is_a?(Hash)
160
160
  if column.text?
161
- ["%{search_sql} #{ActiveScaffold::Finder.like_operator} ?", like_pattern.sub('?', value)]
161
+ ["%<search_sql>s #{ActiveScaffold::Finder.like_operator} ?", like_pattern.sub('?', value)]
162
162
  else
163
- ['%{search_sql} = ?', ActiveScaffold::Core.column_type_cast(value, column.column)]
163
+ ['%<search_sql>s = ?', ActiveScaffold::Core.column_type_cast(value, column.column)]
164
164
  end
165
165
  elsif ActiveScaffold::Finder::NULL_COMPARATORS.include?(value[:opt])
166
166
  condition_for_null_type(column, value[:opt], like_pattern)
167
167
  elsif value[:from].blank?
168
168
  nil
169
169
  elsif ActiveScaffold::Finder::STRING_COMPARATORS.values.include?(value[:opt])
170
- ["%{search_sql} #{ActiveScaffold::Finder.like_operator} ?", value[:opt].sub('?', value[:from])]
170
+ ["%<search_sql>s #{ActiveScaffold::Finder.like_operator} ?", value[:opt].sub('?', value[:from])]
171
171
  elsif value[:opt] == 'BETWEEN'
172
- ['(%{search_sql} BETWEEN ? AND ?)', value[:from], value[:to]]
172
+ ['(%<search_sql>s BETWEEN ? AND ?)', value[:from], value[:to]]
173
173
  elsif ActiveScaffold::Finder::NUMERIC_COMPARATORS.include?(value[:opt])
174
- ["%{search_sql} #{value[:opt]} ?", value[:from]]
174
+ ["%<search_sql>s #{value[:opt]} ?", value[:from]]
175
175
  end
176
176
  end
177
177
 
178
178
  def tables_for_translating_days_and_months(format)
179
+ # rubocop:disable Style/FormatStringToken
179
180
  keys = {
180
181
  '%A' => 'date.day_names',
181
182
  '%a' => 'date.abbr_day_names',
182
183
  '%B' => 'date.month_names',
183
184
  '%b' => 'date.abbr_month_names'
184
185
  }
186
+ # rubocop:enable Style/FormatStringToken
185
187
  key_index = keys.keys.map { |key| [key, format.index(key)] }.to_h
186
188
  keys.select! { |k, _| key_index[k] }
187
189
  keys.sort_by { |k, _| key_index[k] }.map do |_, k|
@@ -226,37 +228,60 @@ module ActiveScaffold
226
228
  [format, parts[:offset]]
227
229
  end
228
230
 
231
+ def local_time_from_hash(value, conversion = :to_time)
232
+ time = Time.zone.local(*%i[year month day hour minute second].collect { |part| value[part].to_i })
233
+ time.send(conversion)
234
+ rescue StandardError => e
235
+ message = "Error creating time from #{value.inspect}:"
236
+ Rails.logger.warn "#{message}\n#{e.message}\n#{e.backtrace.join("\n")}"
237
+ nil
238
+ end
239
+
240
+ def parse_date_with_format(value, format_name)
241
+ format = I18n.t("date.formats.#{format_name || :default}")
242
+ format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m
243
+ en_value = I18n.locale == :en ? value : translate_days_and_months(value, format)
244
+ Date.strptime(en_value, format)
245
+ rescue StandardError => e
246
+ message = "Error parsing date from #{en_value}"
247
+ message << " (#{value})" if en_value != value
248
+ message << ", with format #{format}" if format
249
+ Rails.logger.warn "#{message}:\n#{e.message}\n#{e.backtrace.join("\n")}"
250
+ nil
251
+ end
252
+
253
+ def parse_time_with_format(value, format, offset)
254
+ format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m
255
+ en_value = I18n.locale == :en ? value : translate_days_and_months(value, format)
256
+ time = Time.strptime(en_value, format)
257
+ offset ? time : Time.zone.local_to_utc(time).in_time_zone
258
+ rescue StandardError => e
259
+ message = "Error parsing time from #{en_value}"
260
+ message << " (#{value})" if en_value != value
261
+ message << ", with format #{format}" if format
262
+ Rails.logger.warn "#{message}:\n#{e.message}\n#{e.backtrace.join("\n")}"
263
+ nil
264
+ end
265
+
229
266
  def condition_value_for_datetime(column, value, conversion = :to_time)
230
- unless value.nil? || value.blank?
231
- if value.is_a? Hash
232
- time = Time.zone.local(*%i[year month day hour minute second].collect { |part| value[part].to_i }) rescue nil
233
- time.send(conversion) if time
234
- elsif value.respond_to?(:strftime)
235
- if conversion == :to_time
236
- # Explicitly get the current zone, because TimeWithZone#to_time in rails 3.2.3 returns UTC.
237
- # https://github.com/rails/rails/pull/2453
238
- value.to_time.in_time_zone
239
- else
240
- value.send(conversion)
241
- end
242
- elsif conversion == :to_date
243
- format = I18n.t("date.formats.#{column.options[:format] || :default}")
244
- format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m
245
- value = translate_days_and_months(value, format) if I18n.locale != :en
246
- Date.strptime(value, format) rescue nil
247
- elsif value.include?('T')
248
- Time.zone.parse(value)
249
- else # datetime
250
- format, offset = format_for_datetime(column, value)
251
- format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m
252
- value = translate_days_and_months(value, format) if I18n.locale != :en
253
- time = DateTime.strptime(value, format) rescue nil
254
- if time
255
- time = Time.zone.local_to_utc(time).in_time_zone unless offset
256
- time = time.send(conversion) unless conversion == :to_time
257
- end
258
- time
267
+ return if value.nil? || value.blank?
268
+ if value.is_a? Hash
269
+ local_time_from_hash(value, conversion)
270
+ elsif value.respond_to?(:strftime)
271
+ if conversion == :to_time
272
+ # Explicitly get the current zone, because TimeWithZone#to_time in rails 3.2.3 returns UTC.
273
+ # https://github.com/rails/rails/pull/2453
274
+ value.to_time.in_time_zone
275
+ else
276
+ value.send(conversion)
259
277
  end
278
+ elsif conversion == :to_date
279
+ parse_date_with_format(value, column.options[:format])
280
+ elsif value.include?('T')
281
+ Time.zone.parse(value)
282
+ else # datetime
283
+ time = parse_time_with_format(value, *format_for_datetime(column, value))
284
+ conversion == :to_time ? time : time.send(conversion)
260
285
  end
261
286
  end
262
287
 
@@ -264,14 +289,15 @@ module ActiveScaffold
264
289
  return value if value.nil?
265
290
  value = column.number_to_native(value) if column.options[:format] && column.search_ui != :number
266
291
  case (column.search_ui || column.column.type)
267
- when :integer then value.to_i rescue value ? 1 : 0
268
- when :float then value.to_f
269
- when :decimal
270
- if Rails.version >= '4.2.0'
271
- ::ActiveRecord::Type::Decimal.new.type_cast_from_user(value)
292
+ when :integer then
293
+ if value.is_a?(TrueClass) || value.is_a?(FalseClass)
294
+ value ? 1 : 0
272
295
  else
273
- ::ActiveRecord::ConnectionAdapters::Column.value_to_decimal(value)
296
+ value.to_i
274
297
  end
298
+ when :float then value.to_f
299
+ when :decimal
300
+ ::ActiveRecord::Type::Decimal.new.type_cast_from_user(value)
275
301
  else
276
302
  value
277
303
  end
@@ -293,28 +319,28 @@ module ActiveScaffold
293
319
  if from_value.nil? && to_value.nil?
294
320
  nil
295
321
  elsif !from_value
296
- ['%{search_sql} <= ?', to_value.to_s(:db)]
322
+ ['%<search_sql>s <= ?', to_value.to_s(:db)]
297
323
  elsif !to_value
298
- ['%{search_sql} >= ?', from_value.to_s(:db)]
324
+ ['%<search_sql>s >= ?', from_value.to_s(:db)]
299
325
  else
300
- ['%{search_sql} BETWEEN ? AND ?', from_value.to_s(:db), to_value.to_s(:db)]
326
+ ['%<search_sql>s BETWEEN ? AND ?', from_value.to_s(:db), to_value.to_s(:db)]
301
327
  end
302
328
  end
303
329
 
304
330
  def condition_for_record_select_type(column, value, like_pattern = nil)
305
331
  if value.is_a?(Array)
306
- ['%{search_sql} IN (?)', value]
332
+ ['%<search_sql>s IN (?)', value]
307
333
  else
308
- ['%{search_sql} = ?', value]
334
+ ['%<search_sql>s = ?', value]
309
335
  end
310
336
  end
311
337
 
312
338
  def condition_for_null_type(column, value, like_pattern = nil)
313
339
  case value.to_s
314
340
  when 'null'
315
- ['%{search_sql} is null', []]
341
+ ['%<search_sql>s is null', []]
316
342
  when 'not_null'
317
- ['%{search_sql} is not null', []]
343
+ ['%<search_sql>s is not null', []]
318
344
  end
319
345
  end
320
346
  end
@@ -366,10 +392,12 @@ module ActiveScaffold
366
392
  @active_scaffold_references ||= []
367
393
  end
368
394
 
369
- # Override this method on your controller to define conditions to be used when querying a recordset (e.g. for List). The return of this method should be any format compatible with the :conditions clause of ActiveRecord::Base's find.
395
+ # Override this method on your controller to define conditions to be used when querying a recordset (e.g. for List).
396
+ # The return of this method should be any format compatible with the :conditions clause of ActiveRecord::Base's find.
370
397
  def conditions_for_collection; end
371
398
 
372
- # Override this method on your controller to define joins to be used when querying a recordset (e.g. for List). The return of this method should be any format compatible with the :joins clause of ActiveRecord::Base's find.
399
+ # Override this method on your controller to define joins to be used when querying a recordset (e.g. for List).
400
+ # The return of this method should be any format compatible with the :joins clause of ActiveRecord::Base's find.
373
401
  def joins_for_collection; end
374
402
 
375
403
  # Override this method on your controller to provide custom finder options to the find() call. The return of this method should be a hash.
@@ -381,9 +409,9 @@ module ActiveScaffold
381
409
  params_hash active_scaffold_embedded_params[:conditions]
382
410
  end
383
411
 
384
- def all_conditions
412
+ def all_conditions(include_id_condition = true)
385
413
  [
386
- id_condition, # for list with id (e.g. /users/:id/index)
414
+ (id_condition if include_id_condition), # for list with id (e.g. /users/:id/index)
387
415
  active_scaffold_conditions, # from the search modules
388
416
  conditions_for_collection, # from the dev
389
417
  conditions_from_params, # from the parameters (e.g. /users/list?first_name=Fred)
@@ -407,15 +435,20 @@ module ActiveScaffold
407
435
  end
408
436
 
409
437
  # valid options may include:
410
- # * :sorting - a Sorting DataStructure (basically an array of hashes of field => direction, e.g. [{:field1 => 'asc'}, {:field2 => 'desc'}]). please note that multi-column sorting has some limitations: if any column in a multi-field sort uses method-based sorting, it will be ignored. method sorting only works for single-column sorting.
438
+ # * :sorting - a Sorting DataStructure (basically an array of hashes of field => direction,
439
+ # e.g. [{:field1 => 'asc'}, {:field2 => 'desc'}]).
440
+ # please note that multi-column sorting has some limitations: if any column in a multi-field
441
+ # sort uses method-based sorting, it will be ignored. method sorting only works for single-column sorting.
411
442
  # * :per_page
412
443
  # * :page
413
444
  def finder_options(options = {})
414
445
  search_conditions = all_conditions
415
446
 
447
+ sorting = options[:sorting]&.clause((grouped_columns_calculations if grouped_search?))
448
+ sorting = sorting.map(&Arel.method(:sql)) if sorting && active_scaffold_config.active_record?
416
449
  # create a general-use options array that's compatible with Rails finders
417
450
  finder_options = {
418
- :reorder => options[:sorting].try(:clause, (grouped_columns_calculations if grouped_search?)),
451
+ :reorder => sorting,
419
452
  :conditions => search_conditions
420
453
  }
421
454
  if active_scaffold_config.mongoid?
@@ -466,7 +499,7 @@ module ActiveScaffold
466
499
 
467
500
  query = append_to_query(query, find_options)
468
501
  # we build the paginator differently for method- and sql-based sorting
469
- pager = if options[:sorting] && options[:sorting].sorts_by_method?
502
+ pager = if options[:sorting]&.sorts_by_method?
470
503
  ::Paginator.new(count, options[:per_page]) do |offset, per_page|
471
504
  calculate_last_modified(query)
472
505
  sorted_collection = sort_collection_by_column(query.to_a, *options[:sorting].first)
@@ -484,12 +517,12 @@ module ActiveScaffold
484
517
  end
485
518
 
486
519
  def calculate_last_modified(query)
487
- return unless conditional_get_support? && query.klass.columns_hash['updated_at']
520
+ return unless conditional_get_support? && ActiveScaffold::OrmChecks.columns_hash(query.klass)['updated_at']
488
521
  @last_modified = query.maximum(:updated_at)
489
522
  end
490
523
 
491
- def calculate_query
492
- conditions = all_conditions
524
+ def calculate_query(id_condition = true)
525
+ conditions = all_conditions(id_condition)
493
526
  includes = active_scaffold_config.list.count_includes
494
527
  includes ||= active_scaffold_references if conditions.present?
495
528
  left_joins = active_scaffold_outer_joins
@@ -501,13 +534,13 @@ module ActiveScaffold
501
534
  end
502
535
 
503
536
  def append_to_query(relation, options)
504
- options.assert_valid_keys :where, :select, :having, :group, :reorder, :order, :limit, :offset, :joins, :left_joins, :left_outer_joins, :includes, :lock, :readonly, :from, :conditions, :preload, :references
537
+ options.assert_valid_keys :where, :select, :having, :group, :reorder, :order, :limit, :offset,
538
+ :joins, :left_joins, :left_outer_joins, :includes, :lock, :readonly,
539
+ :from, :conditions, :preload, :references
505
540
  relation = options.reject { |_, v| v.blank? }.inject(relation) do |rel, (k, v)|
506
541
  k == :conditions ? apply_conditions(rel, *v) : rel.send(k, v)
507
542
  end
508
- if options[:left_outer_joins].present? || options[:left_joins].present?
509
- relation.distinct_value = true
510
- end
543
+ relation.distinct_value = true if options[:left_outer_joins].present? || options[:left_joins].present?
511
544
  relation
512
545
  end
513
546
 
@@ -536,7 +569,7 @@ module ActiveScaffold
536
569
  def sort_collection_by_column(collection, column, order)
537
570
  sorter = column.sort[:method]
538
571
  collection = collection.sort_by do |record|
539
- value = (sorter.is_a? Proc) ? record.instance_eval(&sorter) : record.instance_eval(sorter.to_s)
572
+ value = sorter.is_a?(Proc) ? record.instance_eval(&sorter) : record.instance_eval(sorter.to_s)
540
573
  value = '' if value.nil?
541
574
  value
542
575
  end