active_scaffold 3.5.3 → 3.6.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (184) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGELOG → CHANGELOG.rdoc} +73 -0
  3. data/README.md +17 -7
  4. data/app/assets/javascripts/active_scaffold.js.erb +0 -1
  5. data/app/assets/javascripts/jquery/active_scaffold.js +97 -6
  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 +1 -0
  63. data/lib/active_scaffold/bridges/bitfields/bitfields_bridge.rb +12 -15
  64. data/lib/active_scaffold/bridges/calendar_date_select/as_cds_bridge.rb +1 -1
  65. data/lib/active_scaffold/bridges/cancan/cancan_bridge.rb +9 -12
  66. data/lib/active_scaffold/bridges/carrierwave/carrierwave_bridge.rb +1 -1
  67. data/lib/active_scaffold/bridges/carrierwave/list_ui.rb +2 -2
  68. data/lib/active_scaffold/bridges/chosen/helpers.rb +11 -9
  69. data/lib/active_scaffold/bridges/date_picker/ext.rb +0 -13
  70. data/lib/active_scaffold/bridges/date_picker/helper.rb +49 -44
  71. data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +1 -1
  72. data/lib/active_scaffold/bridges/file_column/as_file_column_bridge.rb +1 -1
  73. data/lib/active_scaffold/bridges/file_column/file_column_helpers.rb +3 -3
  74. data/lib/active_scaffold/bridges/file_column/form_ui.rb +3 -3
  75. data/lib/active_scaffold/bridges/file_column/test/functional/file_column_keep_test.rb +10 -7
  76. data/lib/active_scaffold/bridges/paper_trail.rb +1 -1
  77. data/lib/active_scaffold/bridges/paper_trail/actions.rb +3 -1
  78. data/lib/active_scaffold/bridges/paperclip/list_ui.rb +1 -1
  79. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge.rb +1 -1
  80. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge_helpers.rb +2 -2
  81. data/lib/active_scaffold/bridges/record_select/helpers.rb +15 -17
  82. data/lib/active_scaffold/bridges/shared/date_bridge.rb +20 -19
  83. data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +3 -1
  84. data/lib/active_scaffold/bridges/usa_state_select/usa_state_select_helper.rb +21 -4
  85. data/lib/active_scaffold/config/base.rb +133 -41
  86. data/lib/active_scaffold/config/core.rb +146 -18
  87. data/lib/active_scaffold/config/delete.rb +14 -1
  88. data/lib/active_scaffold/config/field_search.rb +7 -1
  89. data/lib/active_scaffold/config/form.rb +10 -1
  90. data/lib/active_scaffold/config/list.rb +39 -13
  91. data/lib/active_scaffold/config/mark.rb +4 -2
  92. data/lib/active_scaffold/config/nested.rb +16 -17
  93. data/lib/active_scaffold/config/search.rb +9 -0
  94. data/lib/active_scaffold/config/show.rb +4 -0
  95. data/lib/active_scaffold/config/update.rb +4 -0
  96. data/lib/active_scaffold/configurable.rb +14 -7
  97. data/lib/active_scaffold/constraints.rb +22 -20
  98. data/lib/active_scaffold/core.rb +67 -28
  99. data/lib/active_scaffold/data_structures/action_columns.rb +50 -59
  100. data/lib/active_scaffold/data_structures/action_link.rb +50 -20
  101. data/lib/active_scaffold/data_structures/action_links.rb +15 -13
  102. data/lib/active_scaffold/data_structures/association/abstract.rb +38 -15
  103. data/lib/active_scaffold/data_structures/association/active_mongoid.rb +2 -6
  104. data/lib/active_scaffold/data_structures/association/active_record.rb +6 -2
  105. data/lib/active_scaffold/data_structures/association/mongoid.rb +0 -3
  106. data/lib/active_scaffold/data_structures/column.rb +75 -66
  107. data/lib/active_scaffold/data_structures/columns.rb +3 -2
  108. data/lib/active_scaffold/data_structures/nested_info.rb +33 -19
  109. data/lib/active_scaffold/data_structures/set.rb +8 -0
  110. data/lib/active_scaffold/data_structures/sorting.rb +10 -2
  111. data/lib/active_scaffold/delayed_setup.rb +16 -5
  112. data/lib/active_scaffold/extensions/action_controller_rendering.rb +3 -2
  113. data/lib/active_scaffold/extensions/action_view_rendering.rb +34 -14
  114. data/lib/active_scaffold/extensions/cow_proxy.rb +95 -0
  115. data/lib/active_scaffold/extensions/ice_nine.rb +36 -0
  116. data/lib/active_scaffold/extensions/left_outer_joins.rb +8 -33
  117. data/lib/active_scaffold/extensions/localize.rb +3 -1
  118. data/lib/active_scaffold/extensions/routing_mapper.rb +6 -45
  119. data/lib/active_scaffold/extensions/to_label.rb +3 -2
  120. data/lib/active_scaffold/extensions/unsaved_record.rb +2 -4
  121. data/lib/active_scaffold/finder.rb +110 -77
  122. data/lib/active_scaffold/helpers/action_link_helpers.rb +62 -36
  123. data/lib/active_scaffold/helpers/association_helpers.rb +21 -19
  124. data/lib/active_scaffold/helpers/controller_helpers.rb +34 -10
  125. data/lib/active_scaffold/helpers/form_column_helpers.rb +196 -124
  126. data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -1
  127. data/lib/active_scaffold/helpers/id_helpers.rb +6 -2
  128. data/lib/active_scaffold/helpers/list_column_helpers.rb +86 -57
  129. data/lib/active_scaffold/helpers/pagination_helpers.rb +2 -2
  130. data/lib/active_scaffold/helpers/search_column_helpers.rb +29 -34
  131. data/lib/active_scaffold/helpers/show_column_helpers.rb +3 -5
  132. data/lib/active_scaffold/helpers/view_helpers.rb +38 -35
  133. data/lib/active_scaffold/marked_model.rb +2 -2
  134. data/lib/active_scaffold/orm_checks.rb +3 -7
  135. data/lib/active_scaffold/paginator.rb +7 -7
  136. data/lib/active_scaffold/registry.rb +33 -0
  137. data/lib/active_scaffold/responds_to_parent.rb +8 -11
  138. data/lib/active_scaffold/tableless.rb +67 -65
  139. data/lib/active_scaffold/version.rb +2 -2
  140. data/lib/generators/active_scaffold/controller_generator.rb +2 -2
  141. data/lib/generators/active_scaffold/install_generator.rb +1 -1
  142. data/lib/generators/active_scaffold/resource_generator.rb +2 -2
  143. data/shoulda_macros/macros.rb +3 -1
  144. data/test/bridges/date_picker_test.rb +1 -2
  145. data/test/bridges/paperclip_test.rb +6 -6
  146. data/test/class_with_finder.rb +2 -2
  147. data/test/company.rb +4 -4
  148. data/test/config/create_test.rb +4 -2
  149. data/test/config/nested_test.rb +1 -1
  150. data/test/config/show_test.rb +1 -1
  151. data/test/config/update_test.rb +7 -6
  152. data/test/data_structures/action_columns_test.rb +2 -2
  153. data/test/data_structures/action_links_test.rb +1 -1
  154. data/test/data_structures/column_test.rb +3 -6
  155. data/test/data_structures/columns_test.rb +2 -2
  156. data/test/data_structures/sorting_test.rb +7 -0
  157. data/test/extensions/active_record_test.rb +4 -4
  158. data/test/extensions/routing_mapper_test.rb +2 -2
  159. data/test/helpers/list_column_helpers_test.rb +3 -1
  160. data/test/misc/active_record_permissions_test.rb +3 -11
  161. data/test/misc/attribute_params_test.rb +12 -8
  162. data/test/misc/calculation_test.rb +1 -1
  163. data/test/misc/configurable_test.rb +10 -10
  164. data/test/misc/constraints_test.rb +2 -2
  165. data/test/misc/convert_numbers_format_test.rb +7 -3
  166. data/test/misc/lang_test.rb +1 -1
  167. data/test/misc/parse_datetime_test.rb +3 -4
  168. data/test/misc/tableless_test.rb +6 -0
  169. data/test/mock_app/Rakefile +1 -1
  170. data/test/mock_app/app/assets/config/manifest.js +0 -0
  171. data/test/mock_app/app/controllers/cars_controller.rb +1 -0
  172. data/test/mock_app/app/controllers/people_controller.rb +3 -1
  173. data/test/mock_app/config/application.rb +2 -1
  174. data/test/mock_app/config/boot.rb +1 -1
  175. data/test/mock_app/config/environment.rb +2 -2
  176. data/test/mock_app/config/routes.rb +4 -1
  177. data/test/mock_app/db/schema.rb +2 -0
  178. data/test/performance/list_cars_performance_test.rb +34 -0
  179. data/test/performance/list_people_performance_test.rb +31 -0
  180. data/test/performance_test_help.rb +3 -0
  181. data/test/test_helper.rb +10 -2
  182. metadata +55 -20
  183. data/app/assets/javascripts/prototype/rico_corner.js +0 -370
  184. data/lib/active_scaffold/bridges/file_column/test/test_helper.rb +0 -7
@@ -19,7 +19,7 @@ module ActiveScaffold::Actions
19
19
  def do_search
20
20
  if search_params.is_a?(String) && search_params.present?
21
21
  query = search_params.to_s.strip
22
- columns = active_scaffold_config.search.columns
22
+ columns = active_scaffold_config.search.columns.visible_columns
23
23
  text_search = active_scaffold_config.search.text_search
24
24
  query = query.split(active_scaffold_config.search.split_terms) if active_scaffold_config.search.split_terms
25
25
  search_conditions = self.class.conditions_for_columns(query, columns, text_search)
@@ -19,11 +19,11 @@ module ActiveScaffold::Actions
19
19
  protected
20
20
 
21
21
  def show_respond_to_json
22
- render :json => response_object, :only => show_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(show_columns_names), :methods => virtual_columns(show_columns_names), :status => response_status
22
+ response_to_api(:json, show_columns_names)
23
23
  end
24
24
 
25
25
  def show_respond_to_xml
26
- render :xml => response_object, :only => show_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(show_columns_names), :methods => virtual_columns(show_columns_names), :status => response_status
26
+ response_to_api(:xml, show_columns_names)
27
27
  end
28
28
 
29
29
  def show_respond_to_js
@@ -35,7 +35,7 @@ module ActiveScaffold::Actions
35
35
  end
36
36
 
37
37
  def show_columns_names
38
- active_scaffold_config.show.columns.names
38
+ active_scaffold_config.show.columns.visible_columns_names
39
39
  end
40
40
 
41
41
  # A simple method to retrieve and prepare a record for showing.
@@ -58,7 +58,7 @@ module ActiveScaffold::Actions
58
58
  private
59
59
 
60
60
  def show_authorized_filter
61
- link = active_scaffold_config.show.link || active_scaffold_config.show.class.link
61
+ link = active_scaffold_config.show.link || self.class.active_scaffold_config.show.class.link
62
62
  raise ActiveScaffold::ActionNotAllowed unless Array(send(link.security_method))[0]
63
63
  end
64
64
 
@@ -2,37 +2,38 @@ module ActiveScaffold::Actions
2
2
  module Subform
3
3
  def edit_associated
4
4
  do_edit_associated
5
- render :action => 'edit_associated', :formats => [:js], :readonly => @column.association.readonly?
5
+ respond_to do |format|
6
+ format.js { render :action => 'edit_associated', :formats => [:js], :readonly => @column.association.readonly? }
7
+ end
6
8
  end
7
9
 
8
10
  protected
9
11
 
10
- def do_edit_associated
11
- @parent_record = params[:id].nil? ? new_model : find_if_allowed(params[:id], :update)
12
- if @parent_record.new_record?
13
- apply_constraints_to_record @parent_record
14
- create_association_with_parent @parent_record if nested?
12
+ def new_parent_record
13
+ parent_record = new_model
14
+ # don't apply if scope, subform inside subform, because constraints won't apply to parent_record
15
+ apply_constraints_to_record parent_record unless @scope
16
+ if nested? && nested.match_model?(active_scaffold_config.model)
17
+ create_association_with_parent parent_record
15
18
  end
19
+ parent_record
20
+ end
16
21
 
17
- generate_temporary_id(@parent_record, params[:generated_id]) if @parent_record.new_record? && params[:generated_id]
22
+ def do_edit_associated
23
+ @scope = params[:scope]
24
+ @parent_record = params[:id].nil? ? new_parent_record : find_if_allowed(params[:id], :update)
25
+
26
+ cache_generated_id(@parent_record, params[:generated_id]) if @parent_record.new_record?
18
27
  @column = active_scaffold_config.columns[params[:child_association]]
19
28
 
20
- # NOTE: we don't check whether the user is allowed to update this record, because if not, we'll still let them associate the record. we'll just refuse to do more than associate, is all.
21
- if params[:associated_id]
22
- @record = @column.association.klass.find(params[:associated_id])
23
- if (reverse = @column.association.reverse_association)
24
- if reverse.collection?
25
- @record.send(reverse.name) << @parent_record
26
- elsif @column.association.belongs_to?
27
- @parent_record.send("#{@column.name}=", @record)
28
- else
29
- @record.send("#{reverse.name}=", @parent_record)
30
- end
31
- end
32
- else
33
- @record = build_associated(@column.association, @parent_record)
29
+ @record = find_associated_record if params[:associated_id]
30
+ @record ||= build_associated(@column.association, @parent_record)
31
+ end
32
+
33
+ def find_associated_record
34
+ @column.association.klass.find(params[:associated_id]).tap do |record|
35
+ save_record_to_association(record, @column.association.reverse_association, @parent_record, @column.association)
34
36
  end
35
- @scope = params[:scope]
36
37
  end
37
38
  end
38
39
  end
@@ -35,54 +35,63 @@ module ActiveScaffold::Actions
35
35
  render(:partial => 'update_form')
36
36
  end
37
37
 
38
+ def update_respond_on_iframe
39
+ do_refresh_list if successful? && active_scaffold_config.update.refresh_list && !render_parent?
40
+ responds_to_parent do
41
+ render :action => 'on_update', :formats => [:js], :layout => false
42
+ end
43
+ end
44
+
38
45
  def update_respond_to_html
39
- if params[:iframe] == 'true' # was this an iframe post ?
40
- do_refresh_list if successful? && active_scaffold_config.update.refresh_list && !render_parent?
41
- responds_to_parent do
42
- render :action => 'on_update', :formats => [:js], :layout => false
43
- end
44
- else # just a regular post
45
- if successful?
46
- message = as_(:updated_model, :model => ERB::Util.h(@record.to_label))
47
- if params[:dont_close]
48
- flash.now[:info] = message
49
- render(:action => 'update')
50
- else
51
- flash[:info] = message
52
- return_to_main
53
- end
54
- else
46
+ if successful? # just a regular post
47
+ message = as_(:updated_model, :model => ERB::Util.h(@record.to_label))
48
+ if params[:dont_close]
49
+ flash.now[:info] = message
55
50
  render(:action => 'update')
51
+ else
52
+ flash[:info] = message
53
+ return_to_main
56
54
  end
55
+ else
56
+ render(:action => 'update')
57
57
  end
58
58
  end
59
59
 
60
+ def record_to_refresh_on_update
61
+ if update_refresh_list?
62
+ do_refresh_list
63
+ else
64
+ reload_record_on_update
65
+ end
66
+ end
67
+
68
+ def reload_record_on_update
69
+ @updated_record = @record
70
+ # get_row so associations are cached like in list action
71
+ # if record doesn't fullfil current conditions remove it from list
72
+ @record = get_row
73
+ rescue ActiveRecord::RecordNotFound
74
+ nil
75
+ end
76
+
60
77
  def update_respond_to_js
61
78
  if successful?
62
- if !render_parent? && active_scaffold_config.actions.include?(:list)
63
- if update_refresh_list?
64
- do_refresh_list
65
- else
66
- @updated_record = @record
67
- # get_row so associations are cached like in list action
68
- @record = get_row rescue nil # if record doesn't fullfil current conditions remove it from list
69
- end
70
- end
79
+ record_to_refresh_on_update if !render_parent? && active_scaffold_config.actions.include?(:list)
71
80
  flash.now[:info] = as_(:updated_model, :model => ERB::Util.h((@updated_record || @record).to_label)) if active_scaffold_config.update.persistent
72
81
  end
73
82
  render :action => 'on_update'
74
83
  end
75
84
 
76
85
  def update_respond_to_xml
77
- render :xml => response_object, :only => update_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(update_columns_names), :methods => virtual_columns(update_columns_names), :status => response_status
86
+ response_to_api(:xml, update_columns_names)
78
87
  end
79
88
 
80
89
  def update_respond_to_json
81
- render :json => response_object, :only => update_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(update_columns_names), :methods => virtual_columns(update_columns_names), :status => response_status
90
+ response_to_api(:json, update_columns_names)
82
91
  end
83
92
 
84
93
  def update_columns_names
85
- active_scaffold_config.update.columns.names
94
+ active_scaffold_config.update.columns.visible_columns_names
86
95
  end
87
96
 
88
97
  # A simple method to find and prepare a record for editing
@@ -91,45 +100,47 @@ module ActiveScaffold::Actions
91
100
  @record = find_if_allowed(params[:id], :update)
92
101
  end
93
102
 
94
- # A complex method to update a record. The complexity comes from the support for subforms, and saving associated records.
103
+ # A complex method to update a record. The complexity comes from the support for subforms,
104
+ # and saving associated records.
95
105
  # If you want to customize this algorithm, consider using the +before_update_save+ callback
96
106
  def do_update
97
107
  do_edit
98
108
  update_save
99
109
  end
100
110
 
101
- def update_save(options = {})
102
- attributes = options[:attributes] || params[:record]
103
- begin
104
- active_scaffold_config.model.transaction do
105
- @record = update_record_from_params(@record, active_scaffold_config.update.columns, attributes) unless options[:no_record_param_update]
106
- before_update_save(@record)
107
- # errors to @record can be added by update_record_from_params when association fails to set and ActiveRecord::RecordNotSaved is raised
108
- self.successful = [@record.keeping_errors { @record.valid? }, @record.associated_valid?].all? # this syntax avoids a short-circuit
109
- if successful?
110
- @record.save! && @record.save_associated!
111
- after_update_save(@record)
112
- else
113
- # some associations such as habtm are saved before saved is called on parent object
114
- # we have to revert these changes if validation fails
115
- raise ActiveRecord::Rollback, "don't save habtm associations unless record is valid"
116
- end
111
+ def update_save(attributes: params[:record], no_record_param_update: false)
112
+ active_scaffold_config.model.transaction do
113
+ unless no_record_param_update
114
+ @record = update_record_from_params(@record, active_scaffold_config.update.columns, attributes)
117
115
  end
118
- rescue ActiveRecord::StaleObjectError
119
- @record.errors.add(:base, as_(:version_inconsistency))
120
- self.successful = false
121
- rescue ActiveRecord::RecordNotSaved => exception
122
- logger.warn do
123
- "\n\n#{exception.class} (#{exception.message}):\n " +
124
- Rails.backtrace_cleaner.clean(exception.backtrace).join("\n ") +
125
- "\n\n"
116
+ before_update_save(@record)
117
+ # errors to @record can be added by update_record_from_params when association fails
118
+ # to set and ActiveRecord::RecordNotSaved is raised
119
+ # this syntax avoids a short-circuit, so we run validations on record and associations
120
+ self.successful = [@record.keeping_errors { @record.valid? }, @record.associated_valid?].all?
121
+
122
+ unless successful?
123
+ # some associations such as habtm are saved before saved is called on parent object
124
+ # we have to revert these changes if validation fails
125
+ raise ActiveRecord::Rollback, "don't save habtm associations unless record is valid"
126
126
  end
127
- @record.errors.add(:base, as_(:record_not_saved)) if @record.errors.empty?
128
- self.successful = false
129
- rescue ActiveRecord::ActiveRecordError => ex
130
- flash[:error] = ex.message
131
- self.successful = false
127
+ @record.save! && @record.save_associated!
128
+ after_update_save(@record)
132
129
  end
130
+ rescue ActiveRecord::StaleObjectError
131
+ @record.errors.add(:base, as_(:version_inconsistency))
132
+ self.successful = false
133
+ rescue ActiveRecord::RecordNotSaved => exception
134
+ logger.warn do
135
+ "\n\n#{exception.class} (#{exception.message}):\n " +
136
+ Rails.backtrace_cleaner.clean(exception.backtrace).join("\n ") +
137
+ "\n\n"
138
+ end
139
+ @record.errors.add(:base, as_(:record_not_saved)) if @record.errors.empty?
140
+ self.successful = false
141
+ rescue ActiveRecord::ActiveRecordError => ex
142
+ flash[:error] = ex.message
143
+ self.successful = false
133
144
  end
134
145
 
135
146
  def do_update_column
@@ -139,25 +150,10 @@ module ActiveScaffold::Actions
139
150
  params.delete(:original_html)
140
151
  params.delete(:original_value)
141
152
  @column = active_scaffold_config.columns[column]
142
- @record = value_record = find_if_allowed(params[:id], :read)
143
- return unless @record.authorized_for?(:crud_type => :update, :column => column)
144
- if @column.delegated_association
145
- value_record = @record.send(@column.delegated_association.name)
146
- value_record ||= @record.association(@column.delegated_association.name).build
147
- return unless value_record.authorized_for?(:crud_type => :update, :column => column)
148
- end
149
-
150
- value ||=
151
- unless @column.column.nil? || @column.column.null
152
- default_val = @column.column.default
153
- default_val = ActiveScaffold::Core.column_type_cast default_val, @column.column if Rails.version >= '4.2.0'
154
- default_val == true ? false : default_val
155
- end
156
- unless @column.nil?
157
- value = column_value_from_param_value(value_record, @column, value)
158
- value = [] if value.nil? && @column.form_ui && @column.association.try(:collection?)
159
- end
153
+ value_record = record_for_update_column
154
+ return unless value_record
160
155
 
156
+ value = value_for_update_column(value, @column, value_record)
161
157
  value_record.send("#{@column.name}=", value)
162
158
  before_update_save(@record)
163
159
  self.successful = value_record.save
@@ -174,6 +170,29 @@ module ActiveScaffold::Actions
174
170
  after_update_save(@record)
175
171
  end
176
172
 
173
+ def record_for_update_column
174
+ @record = find_if_allowed(params[:id], :read)
175
+ return unless @record.authorized_for?(:crud_type => :update, :column => @column.name)
176
+
177
+ if @column.delegated_association
178
+ value_record = @record.send(@column.delegated_association.name)
179
+ value_record ||= @record.association(@column.delegated_association.name).build
180
+ value_record if value_record.authorized_for?(:crud_type => :update, :column => @column.name)
181
+ else
182
+ @record
183
+ end
184
+ end
185
+
186
+ def value_for_update_column(param_value, column, record)
187
+ unless param_value
188
+ param_value = ActiveScaffold::Core.column_type_cast @column.default_for_empty_value, @column.column
189
+ param_value = false if param_value == true
190
+ end
191
+ value = column_value_from_param_value(record, @column, param_value)
192
+ value = [] if value.nil? && @column.form_ui && @column.association&.collection?
193
+ value
194
+ end
195
+
177
196
  # override this method if you want to inject data in the record (or its associated objects) before the save
178
197
  def before_update_save(record); end
179
198
 
@@ -198,7 +217,7 @@ module ActiveScaffold::Actions
198
217
  private
199
218
 
200
219
  def update_authorized_filter
201
- link = active_scaffold_config.update.link || active_scaffold_config.update.class.link
220
+ link = active_scaffold_config.update.link || self.class.active_scaffold_config.update.class.link
202
221
  raise ActiveScaffold::ActionNotAllowed unless Array(send(link.security_method))[0]
203
222
  end
204
223
 
@@ -37,7 +37,7 @@ module ActiveScaffold
37
37
  # because the object may change (someone may log in or out). So we give ActiveRecord a proc that ties to the
38
38
  # current_user_method on this ApplicationController.
39
39
  def assign_current_user_to_models
40
- ActiveRecord::Base.current_user_proc = proc { send(ActiveRecordPermissions.current_user_method) }
40
+ ActiveScaffold::Registry.current_user_proc = proc { send(ActiveRecordPermissions.current_user_method) }
41
41
  end
42
42
  end
43
43
 
@@ -48,18 +48,9 @@ module ActiveScaffold
48
48
  end
49
49
 
50
50
  module ClassMethods
51
- # The proc to call that retrieves the current_user from the ApplicationController.
52
- def current_user_proc
53
- Thread.current[:current_user_proc]
54
- end
55
-
56
- def current_user_proc=(proc_value)
57
- Thread.current[:current_user_proc] = proc_value
58
- end
59
-
60
51
  # Class-level access to the current user
61
52
  def current_user
62
- ActiveRecord::Base.current_user_proc.call if ActiveRecord::Base.current_user_proc
53
+ ActiveScaffold::Registry.current_user_proc&.call
63
54
  end
64
55
  end
65
56
 
@@ -41,13 +41,8 @@ module ActiveScaffold
41
41
  difference = value.select(&:persisted?).size - parent_record.send(counter_attr)
42
42
 
43
43
  if parent_record.new_record?
44
- if Rails.version >= '4.2.0'
45
- parent_record.send "#{column.name}=", value
46
- parent_record.send "#{counter_attr}_will_change!"
47
- else # < 4.2
48
- parent_record.send "#{counter_attr}=", difference
49
- parent_record.send "#{column.name}=", value
50
- end
44
+ parent_record.send "#{column.name}=", value
45
+ parent_record.send "#{counter_attr}_will_change!"
51
46
  else
52
47
  # don't decrement counter for deleted records, on destroy they will update counter
53
48
  difference += (parent_record.send(column.name) - value).size
@@ -57,7 +52,7 @@ module ActiveScaffold
57
52
  # update counters on old parents if belongs_to is changed
58
53
  value.select(&:persisted?).each do |record|
59
54
  key = record.send(column.association.foreign_key)
60
- parent_record.class.decrement_counter counter_attr, key if key && key != parent_record.id
55
+ parent_record.class.decrement_counter counter_attr, key if key && key != parent_record.id # rubocop:disable Rails/SkipsModelValidations
61
56
  end
62
57
  parent_record.send "#{column.name}=", value if parent_record.persisted?
63
58
  end
@@ -76,6 +71,15 @@ module ActiveScaffold
76
71
  !params_hash?(value) && association.belongs_to? && association.counter_cache_hack?
77
72
  end
78
73
 
74
+ def multi_parameter_attributes(attributes)
75
+ params_hash(attributes).each_with_object({}) do |(k, v), result|
76
+ next unless k.include? '('
77
+ column_name = k.split('(').first
78
+ result[column_name] ||= []
79
+ result[column_name] << [k, v]
80
+ end
81
+ end
82
+
79
83
  # Takes attributes (as from params[:record]) and applies them to the parent_record. Also looks for
80
84
  # association attributes and attempts to instantiate them as associated objects.
81
85
  #
@@ -85,28 +89,22 @@ module ActiveScaffold
85
89
  crud_type = parent_record.new_record? ? :create : :update
86
90
  return parent_record unless parent_record.authorized_for?(:crud_type => crud_type)
87
91
 
88
- multi_parameter_attributes = {}
89
- attributes.each do |k, v|
90
- next unless k.include? '('
91
- column_name = k.split('(').first
92
- multi_parameter_attributes[column_name] ||= []
93
- multi_parameter_attributes[column_name] << [k, v]
94
- end
92
+ multi_parameter_attrs = multi_parameter_attributes(attributes)
95
93
 
96
- columns.each :for => parent_record, :crud_type => crud_type, :flatten => true do |column|
94
+ columns.each_column(for: parent_record, crud_type: crud_type, flatten: true) do |column|
97
95
  begin
98
96
  # Set any passthrough parameters that may be associated with this column (ie, file column "keep" and "temp" attributes)
99
- unless column.params.empty?
100
- column.params.each { |p| parent_record.send("#{p}=", attributes[p]) if attributes.key? p }
101
- end
97
+ column.params.select { |p| attributes.key? p }.each { |p| parent_record.send("#{p}=", attributes[p]) }
102
98
 
103
- if multi_parameter_attributes.key? column.name.to_s
104
- parent_record.send(:assign_multiparameter_attributes, multi_parameter_attributes[column.name.to_s])
99
+ if multi_parameter_attrs.key? column.name.to_s
100
+ parent_record.send(:assign_multiparameter_attributes, multi_parameter_attrs[column.name.to_s])
105
101
  elsif attributes.key? column.name
106
- value = update_column_from_params(parent_record, column, attributes[column.name], avoid_changes)
102
+ update_column_from_params(parent_record, column, attributes[column.name], avoid_changes)
107
103
  end
108
- rescue => e
109
- Rails.logger.error "#{e.class.name}: #{e.message} -- on the ActiveScaffold column = :#{column.name} for #{parent_record.inspect}#{" with value #{value}" if value}"
104
+ rescue StandardError => e
105
+ message = "on the ActiveScaffold column = :#{column.name} for #{parent_record.inspect} "\
106
+ "(value from params #{attributes[column.name].inspect})"
107
+ Rails.logger.error "#{e.class.name}: #{e.message} -- #{message}"
110
108
  raise
111
109
  end
112
110
  end
@@ -116,38 +114,45 @@ module ActiveScaffold
116
114
 
117
115
  def update_column_from_params(parent_record, column, attribute, avoid_changes = false)
118
116
  value = column_value_from_param_value(parent_record, column, attribute, avoid_changes)
119
- if avoid_changes && column.association
120
- parent_record.association(column.name).target = value
121
- parent_record.send("#{column.association.foreign_key}=", value.try(:id)) if column.association.belongs_to?
122
- elsif column.association && counter_cache_hack?(column.association, attribute)
123
- parent_record.send "#{column.association.foreign_key}=", value.try(:id)
117
+ if column.association
118
+ if avoid_changes
119
+ parent_record.association(column.name).target = value
120
+ parent_record.send("#{column.association.foreign_key}=", value&.id) if column.association.belongs_to?
121
+ else
122
+ update_column_association(parent_record, column, attribute, value)
123
+ end
124
+ else
125
+ parent_record.send "#{column.name}=", value
126
+ end
127
+ # needed? probably done on find_or_create_for_params, need more testing
128
+ if column.association&.reverse_association&.belongs_to?
129
+ Array(value).each { |v| v.send("#{column.association.reverse}=", parent_record) if v.new_record? }
130
+ end
131
+ value
132
+ end
133
+
134
+ def update_column_association(parent_record, column, attribute, value)
135
+ if counter_cache_hack?(column.association, attribute)
136
+ parent_record.send "#{column.association.foreign_key}=", value&.id
124
137
  parent_record.association(column.name).target = value
125
- elsif column.association.try(:collection?) && column.association.through? && !column.association.through_reflection.collection?
138
+ elsif column.association.collection? && column.association.through_singular?
126
139
  through = column.association.through_reflection.name
127
140
  through_record = parent_record.send(through)
128
141
  through_record ||= parent_record.send "build_#{through}"
129
142
  through_record.send "#{column.association.source_reflection.name}=", value
143
+ elsif hack_for_has_many_counter_cache?(parent_record, column)
144
+ hack_for_has_many_counter_cache(parent_record, column, value)
130
145
  else
131
- begin
132
- if column.association && hack_for_has_many_counter_cache?(parent_record, column)
133
- hack_for_has_many_counter_cache(parent_record, column, value)
134
- else
135
- parent_record.send "#{column.name}=", value
136
- end
137
- rescue ActiveRecord::RecordNotSaved
138
- parent_record.errors.add column.name, :invalid
139
- parent_record.association(column.name).target = value if column.association
140
- end
146
+ parent_record.send "#{column.name}=", value
141
147
  end
142
- if column.association.try(:reverse_association).try(:belongs_to?)
143
- Array(value).each { |v| v.send("#{column.association.reverse}=", parent_record) if v.new_record? }
144
- end
145
- value
148
+ rescue ActiveRecord::RecordNotSaved
149
+ parent_record.errors.add column.name, :invalid
150
+ parent_record.association(column.name).target = value
146
151
  end
147
152
 
148
153
  def column_value_from_param_value(parent_record, column, value, avoid_changes = false)
149
154
  # convert the value, possibly by instantiating associated objects
150
- form_ui = column.form_ui || column.column.try(:type)
155
+ form_ui = column.form_ui || column.column&.type
151
156
  if form_ui && respond_to?("column_value_for_#{form_ui}_type", true)
152
157
  send("column_value_for_#{form_ui}_type", parent_record, column, value)
153
158
  elsif params_hash? value
@@ -177,27 +182,24 @@ module ActiveScaffold
177
182
  Date.parse("#{value}-01")
178
183
  end
179
184
 
180
- def column_value_from_param_simple_value(parent_record, column, value)
181
- if column.association.try :singular?
182
- if value.present?
183
- if column.association.polymorphic?
184
- class_name = parent_record.send(column.association.foreign_type)
185
- class_name.constantize.find(value) if class_name.present?
186
- else
187
- # it's a single id
188
- column.association.klass.find(value)
189
- end
190
- end
191
- elsif column.association.try :collection?
185
+ def association_value_from_param_simple_value(parent_record, column, value)
186
+ if column.association.singular?
187
+ column.association.klass(parent_record)&.find(value) if value.present?
188
+ else # column.association.collection?
192
189
  column_plural_assocation_value_from_value(column, Array(value))
193
- elsif column.number? && column.options[:format] && column.form_ui != :number
190
+ end
191
+ end
192
+
193
+ def column_value_from_param_simple_value(parent_record, column, value)
194
+ if column.association
195
+ association_value_from_param_simple_value(parent_record, column, value)
196
+ elsif column.convert_to_native?
194
197
  column.number_to_native(value)
195
- else
198
+ elsif value.is_a?(String) && value.empty? && !column.virtual?
196
199
  # convert empty strings into nil. this works better with 'null => true' columns (and validations),
197
200
  # for 'null => false' columns is just converted to default value from column
198
- if value.is_a?(String) && value.empty? && !column.column.nil?
199
- value = column.column.null ? nil : column.column.default
200
- end
201
+ column.default_for_empty_value
202
+ else
201
203
  value
202
204
  end
203
205
  end
@@ -213,9 +215,9 @@ module ActiveScaffold
213
215
  end
214
216
 
215
217
  def column_value_from_param_hash_value(parent_record, column, value, avoid_changes = false)
216
- if column.association.try :singular?
218
+ if column.association&.singular?
217
219
  manage_nested_record_from_params(parent_record, column, value, avoid_changes)
218
- elsif column.association.try :collection?
220
+ elsif column.association&.collection?
219
221
  # HACK: to be able to delete all associated records, hash will include "0" => ""
220
222
  values = value.values.reject(&:blank?)
221
223
  values.collect { |val| manage_nested_record_from_params(parent_record, column, val, avoid_changes) }.compact
@@ -228,7 +230,7 @@ module ActiveScaffold
228
230
  return nil unless build_record_from_params?(attributes, column, parent_record)
229
231
  record = find_or_create_for_params(attributes, column, parent_record)
230
232
  if record
231
- record_columns = active_scaffold_config_for(column.association.klass).subform.columns
233
+ record_columns = active_scaffold_config_for(record.class).subform.columns
232
234
  prev_constraints = record_columns.constraint_columns
233
235
  record_columns.constraint_columns = [column.association.reverse].compact
234
236
  update_record_from_params(record, record_columns, attributes, avoid_changes)
@@ -240,8 +242,9 @@ module ActiveScaffold
240
242
 
241
243
  def build_record_from_params?(params, column, record)
242
244
  current = record.send(column.name)
243
- klass = column.association.klass
244
- (column.association.collection? && !column.show_blank_record?(current)) || !attributes_hash_is_empty?(params, klass)
245
+ return true if column.association.collection? && !column.show_blank_record?(current)
246
+ klass = column.association.klass(record)
247
+ klass && !attributes_hash_is_empty?(params, klass)
245
248
  end
246
249
 
247
250
  # Attempts to create or find an instance of the klass of the association in parent_column from the
@@ -249,21 +252,26 @@ module ActiveScaffold
249
252
  # otherwise it will build a new one.
250
253
  def find_or_create_for_params(params, parent_column, parent_record)
251
254
  current = parent_record.send(parent_column.name)
252
- klass = parent_column.association.klass
255
+ klass = parent_column.association.klass(parent_record)
253
256
  if params.key? klass.primary_key
254
257
  record_from_current_or_find(klass, params[klass.primary_key], current)
255
258
  elsif klass.authorized_for?(:crud_type => :create)
256
- parent_column.association.klass.new
259
+ association = parent_column.association
260
+ record = klass.new
261
+ if association.reverse_association&.belongs_to? && (association.collection? || current.nil?)
262
+ record.send("#{parent_column.association.reverse}=", parent_record)
263
+ end
264
+ record
257
265
  end
258
266
  end
259
267
 
260
268
  # Attempts to find an instance of klass (which must be an ActiveRecord object) with id primary key
261
269
  # Returns record from current if it's included or find from DB
262
270
  def record_from_current_or_find(klass, id, current)
263
- if current && current.is_a?(ActiveRecord::Base) && current.id.to_s == id
271
+ if current.is_a?(ActiveRecord::Base) && current.id.to_s == id
264
272
  # modifying the current object of a singular association
265
273
  current
266
- elsif current && current.respond_to?(:any?) && current.any? { |o| o.id.to_s == id }
274
+ elsif current.respond_to?(:any?) && current.any? { |o| o.id.to_s == id }
267
275
  # modifying one of the current objects in a plural association
268
276
  current.detect { |o| o.id.to_s == id }
269
277
  else # attaching an existing but not-current object
@@ -271,36 +279,20 @@ module ActiveScaffold
271
279
  end
272
280
  end
273
281
 
274
- def save_record_to_association(record, association, value)
275
- if association.try(:collection?)
276
- record.send(association.name) << value
277
- elsif association
278
- record.send("#{association.name}=", value)
279
- end
280
- end
281
-
282
282
  # Determines whether the given attributes hash is "empty".
283
283
  # This isn't a literal emptiness - it's an attempt to discern whether the user intended it to be empty or not.
284
284
  def attributes_hash_is_empty?(hash, klass)
285
- # old style date form management... ignore them too
286
- part_ignore_column_types = [:datetime, :date, :time, Time, Date]
287
-
288
285
  hash.all? do |key, value|
289
286
  # convert any possible multi-parameter attributes like 'created_at(5i)' to simply 'created_at'
290
- parts = key.to_s.split('(')
291
- column_name = parts.first
292
- column = ActiveScaffold::OrmChecks.columns_hash(klass)[column_name]
293
- column_type = ActiveScaffold::OrmChecks.column_type(klass, column_name) if column
287
+ column_name = key.to_s.split('(', 2)[0]
294
288
 
295
289
  # datetimes will always have a value. so we ignore them when checking whether the hash is empty.
296
290
  # this could be a bad idea. but the current situation (excess record entry) seems worse.
297
- next true if column && parts.length > 1 && part_ignore_column_types.include?(column_type)
291
+ next true if mulitpart_ignored?(key, klass)
298
292
 
299
293
  # defaults are pre-filled on the form. we can't use them to determine if the user intends a new row.
300
294
  # booleans always have value, so they are ignored if not changed from default
301
- default_value = column_default_value(column_name, klass, column)
302
- casted_value = column ? ActiveScaffold::Core.column_type_cast(value, column) : value
303
- next true if casted_value == default_value
295
+ next true if default_value?(column_name, klass, value)
304
296
 
305
297
  if params_hash? value
306
298
  attributes_hash_is_empty?(value, klass)
@@ -312,14 +304,30 @@ module ActiveScaffold
312
304
  end
313
305
  end
314
306
 
315
- def column_default_value(column_name, klass, column)
307
+ # old style date form management... ignore them
308
+ MULTIPART_IGNORE_TYPES = [:datetime, :date, :time, Time, Date].freeze
309
+
310
+ def mulitpart_ignored?(param_name, klass)
311
+ column_name, multipart = param_name.to_s.split('(', 2)
312
+ return false unless multipart
313
+ column_type = ActiveScaffold::OrmChecks.column_type(klass, column_name)
314
+ MULTIPART_IGNORE_TYPES.include?(column_type) if column_type
315
+ end
316
+
317
+ def default_value?(column_name, klass, value)
318
+ column = ActiveScaffold::OrmChecks.columns_hash(klass)[column_name]
319
+ default_value = column_default_value(column_name, klass)
320
+ casted_value = ActiveScaffold::Core.column_type_cast(value, column)
321
+ casted_value == default_value
322
+ end
323
+
324
+ def column_default_value(column_name, klass)
325
+ column = ActiveScaffold::OrmChecks.columns_hash(klass)[column_name]
316
326
  return unless column
317
327
  if ActiveScaffold::OrmChecks.mongoid? klass
318
328
  column.default_val
319
329
  elsif ActiveScaffold::OrmChecks.active_record? klass
320
- if Rails.version < '4.2'
321
- column.default
322
- elsif Rails.version < '5.0'
330
+ if Rails.version < '5.0'
323
331
  column.type_cast_from_database(column.default)
324
332
  else
325
333
  column_type = ActiveScaffold::OrmChecks.column_type(klass, column_name)