active_scaffold 3.6.0.pre → 3.6.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGELOG → CHANGELOG.rdoc} +39 -0
  3. data/app/assets/javascripts/active_scaffold.js.erb +0 -1
  4. data/app/assets/javascripts/jquery/active_scaffold.js +35 -4
  5. data/app/assets/stylesheets/active_scaffold_colors.scss +1 -1
  6. data/app/assets/stylesheets/active_scaffold_layout.css +52 -29
  7. data/app/views/active_scaffold_overrides/_list_header.html.erb +5 -7
  8. data/app/views/active_scaffold_overrides/_list_record.html.erb +4 -5
  9. data/app/views/active_scaffold_overrides/_list_with_header.html.erb +1 -1
  10. data/app/views/active_scaffold_overrides/_refresh_list.js.erb +4 -0
  11. data/config/locales/de.yml +2 -1
  12. data/config/locales/en.yml +1 -0
  13. data/config/locales/es.yml +1 -0
  14. data/config/locales/fr.yml +2 -1
  15. data/config/locales/hu.yml +1 -0
  16. data/config/locales/ja.yml +1 -0
  17. data/config/locales/ru.yml +1 -0
  18. data/lib/active_scaffold.rb +8 -3
  19. data/lib/active_scaffold/actions/common_search.rb +11 -8
  20. data/lib/active_scaffold/actions/core.rb +79 -51
  21. data/lib/active_scaffold/actions/create.rb +27 -27
  22. data/lib/active_scaffold/actions/delete.rb +1 -1
  23. data/lib/active_scaffold/actions/field_search.rb +52 -42
  24. data/lib/active_scaffold/actions/list.rb +106 -23
  25. data/lib/active_scaffold/actions/nested.rb +59 -42
  26. data/lib/active_scaffold/actions/show.rb +3 -3
  27. data/lib/active_scaffold/actions/subform.rb +9 -16
  28. data/lib/active_scaffold/actions/update.rb +95 -77
  29. data/lib/active_scaffold/attribute_params.rb +93 -68
  30. data/lib/active_scaffold/bridges/active_storage.rb +6 -0
  31. data/lib/active_scaffold/bridges/active_storage/active_storage_bridge.rb +33 -0
  32. data/lib/active_scaffold/bridges/active_storage/active_storage_helpers.rb +54 -0
  33. data/lib/active_scaffold/bridges/active_storage/form_ui.rb +22 -0
  34. data/lib/active_scaffold/bridges/active_storage/list_ui.rb +36 -0
  35. data/lib/active_scaffold/bridges/bitfields.rb +1 -0
  36. data/lib/active_scaffold/bridges/bitfields/bitfields_bridge.rb +12 -15
  37. data/lib/active_scaffold/bridges/cancan/cancan_bridge.rb +6 -0
  38. data/lib/active_scaffold/bridges/carrierwave/list_ui.rb +2 -2
  39. data/lib/active_scaffold/bridges/date_picker/helper.rb +46 -41
  40. data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +1 -1
  41. data/lib/active_scaffold/bridges/file_column/file_column_helpers.rb +2 -2
  42. data/lib/active_scaffold/bridges/file_column/form_ui.rb +3 -3
  43. data/lib/active_scaffold/bridges/file_column/test/functional/file_column_keep_test.rb +3 -1
  44. data/lib/active_scaffold/bridges/paperclip/paperclip_bridge_helpers.rb +2 -2
  45. data/lib/active_scaffold/bridges/record_select/helpers.rb +3 -7
  46. data/lib/active_scaffold/bridges/shared/date_bridge.rb +19 -18
  47. data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +3 -1
  48. data/lib/active_scaffold/bridges/usa_state_select/usa_state_select_helper.rb +20 -3
  49. data/lib/active_scaffold/config/base.rb +58 -34
  50. data/lib/active_scaffold/config/core.rb +31 -12
  51. data/lib/active_scaffold/config/delete.rb +12 -1
  52. data/lib/active_scaffold/config/list.rb +17 -7
  53. data/lib/active_scaffold/config/mark.rb +1 -1
  54. data/lib/active_scaffold/configurable.rb +5 -3
  55. data/lib/active_scaffold/constraints.rb +21 -19
  56. data/lib/active_scaffold/core.rb +35 -26
  57. data/lib/active_scaffold/data_structures/action_columns.rb +1 -1
  58. data/lib/active_scaffold/data_structures/action_link.rb +34 -16
  59. data/lib/active_scaffold/data_structures/action_links.rb +9 -11
  60. data/lib/active_scaffold/data_structures/association/abstract.rb +35 -13
  61. data/lib/active_scaffold/data_structures/association/active_mongoid.rb +2 -6
  62. data/lib/active_scaffold/data_structures/association/active_record.rb +5 -1
  63. data/lib/active_scaffold/data_structures/association/mongoid.rb +0 -3
  64. data/lib/active_scaffold/data_structures/column.rb +49 -58
  65. data/lib/active_scaffold/data_structures/columns.rb +3 -2
  66. data/lib/active_scaffold/data_structures/nested_info.rb +20 -18
  67. data/lib/active_scaffold/data_structures/sorting.rb +5 -0
  68. data/lib/active_scaffold/delayed_setup.rb +16 -6
  69. data/lib/active_scaffold/extensions/action_controller_rendering.rb +1 -1
  70. data/lib/active_scaffold/extensions/action_view_rendering.rb +34 -14
  71. data/lib/active_scaffold/extensions/cow_proxy.rb +50 -2
  72. data/lib/active_scaffold/extensions/localize.rb +3 -1
  73. data/lib/active_scaffold/extensions/routing_mapper.rb +2 -2
  74. data/lib/active_scaffold/extensions/to_label.rb +3 -2
  75. data/lib/active_scaffold/finder.rb +81 -46
  76. data/lib/active_scaffold/helpers/action_link_helpers.rb +47 -21
  77. data/lib/active_scaffold/helpers/association_helpers.rb +13 -11
  78. data/lib/active_scaffold/helpers/controller_helpers.rb +14 -11
  79. data/lib/active_scaffold/helpers/form_column_helpers.rb +133 -99
  80. data/lib/active_scaffold/helpers/human_condition_helpers.rb +1 -1
  81. data/lib/active_scaffold/helpers/id_helpers.rb +4 -0
  82. data/lib/active_scaffold/helpers/list_column_helpers.rb +76 -49
  83. data/lib/active_scaffold/helpers/pagination_helpers.rb +2 -2
  84. data/lib/active_scaffold/helpers/search_column_helpers.rb +25 -30
  85. data/lib/active_scaffold/helpers/show_column_helpers.rb +3 -5
  86. data/lib/active_scaffold/helpers/view_helpers.rb +31 -22
  87. data/lib/active_scaffold/orm_checks.rb +2 -2
  88. data/lib/active_scaffold/paginator.rb +1 -3
  89. data/lib/active_scaffold/registry.rb +11 -0
  90. data/lib/active_scaffold/responds_to_parent.rb +6 -5
  91. data/lib/active_scaffold/tableless.rb +6 -8
  92. data/lib/active_scaffold/version.rb +1 -1
  93. data/shoulda_macros/macros.rb +3 -1
  94. data/test/bridges/paperclip_test.rb +1 -1
  95. data/test/company.rb +2 -2
  96. data/test/data_structures/action_columns_test.rb +2 -2
  97. data/test/data_structures/column_test.rb +3 -6
  98. data/test/data_structures/columns_test.rb +2 -2
  99. data/test/extensions/active_record_test.rb +4 -4
  100. data/test/extensions/routing_mapper_test.rb +2 -2
  101. data/test/helpers/list_column_helpers_test.rb +3 -1
  102. data/test/misc/active_record_permissions_test.rb +2 -2
  103. data/test/misc/attribute_params_test.rb +4 -0
  104. data/test/misc/configurable_test.rb +10 -10
  105. data/test/misc/convert_numbers_format_test.rb +4 -0
  106. data/test/mock_app/app/assets/config/manifest.js +0 -0
  107. data/test/mock_app/app/controllers/cars_controller.rb +1 -0
  108. data/test/mock_app/app/controllers/people_controller.rb +3 -1
  109. data/test/mock_app/config/application.rb +1 -0
  110. data/test/mock_app/config/routes.rb +4 -1
  111. data/test/mock_app/db/schema.rb +2 -0
  112. data/test/performance/list_cars_performance_test.rb +34 -0
  113. data/test/performance/list_people_performance_test.rb +31 -0
  114. data/test/performance_test_help.rb +3 -0
  115. data/test/test_helper.rb +2 -1
  116. metadata +22 -12
  117. data/app/assets/javascripts/prototype/rico_corner.js +0 -370
  118. data/lib/active_scaffold/bridges/file_column/test/test_helper.rb +0 -5
@@ -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
@@ -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
 
@@ -16,25 +16,18 @@ module ActiveScaffold::Actions
16
16
  create_association_with_parent @parent_record if nested?
17
17
  end
18
18
 
19
- generate_temporary_id(@parent_record, params[:generated_id]) if @parent_record.new_record? && params[:generated_id]
19
+ cache_generated_id(@parent_record, params[:generated_id]) if @parent_record.new_record?
20
20
  @column = active_scaffold_config.columns[params[:child_association]]
21
21
 
22
- # 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.
23
- if params[:associated_id]
24
- @record = @column.association.klass.find(params[:associated_id])
25
- if (reverse = @column.association.reverse_association)
26
- if reverse.collection?
27
- @record.send(reverse.name) << @parent_record
28
- elsif @column.association.belongs_to?
29
- @parent_record.send("#{@column.name}=", @record)
30
- else
31
- @record.send("#{reverse.name}=", @parent_record)
32
- end
33
- end
34
- else
35
- @record = build_associated(@column.association, @parent_record)
36
- end
22
+ @record = find_associated_record if params[:associated_id]
23
+ @record ||= build_associated(@column.association, @parent_record)
37
24
  @scope = params[:scope]
38
25
  end
26
+
27
+ def find_associated_record
28
+ @column.association.klass.find(params[:associated_id]).tap do |record|
29
+ save_record_to_association(record, @column.association.reverse_association, @parent_record, @column.association)
30
+ end
31
+ end
39
32
  end
40
33
  end
@@ -35,51 +35,59 @@ 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
- # if record doesn't fullfil current conditions remove it from list
69
- @record = get_row rescue nil # rubocop:disable Style/RescueModifier
70
- end
71
- end
79
+ record_to_refresh_on_update if !render_parent? && active_scaffold_config.actions.include?(:list)
72
80
  flash.now[:info] = as_(:updated_model, :model => ERB::Util.h((@updated_record || @record).to_label)) if active_scaffold_config.update.persistent
73
81
  end
74
82
  render :action => 'on_update'
75
83
  end
76
84
 
77
85
  def update_respond_to_xml
78
- 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)
79
87
  end
80
88
 
81
89
  def update_respond_to_json
82
- 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)
83
91
  end
84
92
 
85
93
  def update_columns_names
@@ -92,45 +100,47 @@ module ActiveScaffold::Actions
92
100
  @record = find_if_allowed(params[:id], :update)
93
101
  end
94
102
 
95
- # 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.
96
105
  # If you want to customize this algorithm, consider using the +before_update_save+ callback
97
106
  def do_update
98
107
  do_edit
99
108
  update_save
100
109
  end
101
110
 
102
- def update_save(options = {})
103
- attributes = options[:attributes] || params[:record]
104
- begin
105
- active_scaffold_config.model.transaction do
106
- @record = update_record_from_params(@record, active_scaffold_config.update.columns, attributes) unless options[:no_record_param_update]
107
- before_update_save(@record)
108
- # errors to @record can be added by update_record_from_params when association fails to set and ActiveRecord::RecordNotSaved is raised
109
- self.successful = [@record.keeping_errors { @record.valid? }, @record.associated_valid?].all? # this syntax avoids a short-circuit
110
- if successful?
111
- @record.save! && @record.save_associated!
112
- after_update_save(@record)
113
- else
114
- # some associations such as habtm are saved before saved is called on parent object
115
- # we have to revert these changes if validation fails
116
- raise ActiveRecord::Rollback, "don't save habtm associations unless record is valid"
117
- 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)
118
115
  end
119
- rescue ActiveRecord::StaleObjectError
120
- @record.errors.add(:base, as_(:version_inconsistency))
121
- self.successful = false
122
- rescue ActiveRecord::RecordNotSaved => exception
123
- logger.warn do
124
- "\n\n#{exception.class} (#{exception.message}):\n " +
125
- Rails.backtrace_cleaner.clean(exception.backtrace).join("\n ") +
126
- "\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"
127
126
  end
128
- @record.errors.add(:base, as_(:record_not_saved)) if @record.errors.empty?
129
- self.successful = false
130
- rescue ActiveRecord::ActiveRecordError => ex
131
- flash[:error] = ex.message
132
- self.successful = false
127
+ @record.save! && @record.save_associated!
128
+ after_update_save(@record)
133
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
134
144
  end
135
145
 
136
146
  def do_update_column
@@ -140,25 +150,10 @@ module ActiveScaffold::Actions
140
150
  params.delete(:original_html)
141
151
  params.delete(:original_value)
142
152
  @column = active_scaffold_config.columns[column]
143
- @record = value_record = find_if_allowed(params[:id], :read)
144
- return unless @record.authorized_for?(:crud_type => :update, :column => column)
145
- if @column.delegated_association
146
- value_record = @record.send(@column.delegated_association.name)
147
- value_record ||= @record.association(@column.delegated_association.name).build
148
- return unless value_record.authorized_for?(:crud_type => :update, :column => column)
149
- end
150
-
151
- value ||=
152
- unless @column.column.nil? || @column.column.null
153
- default_val = @column.column.default
154
- default_val = ActiveScaffold::Core.column_type_cast default_val, @column.column
155
- default_val == true ? false : default_val
156
- end
157
- unless @column.nil?
158
- value = column_value_from_param_value(value_record, @column, value)
159
- value = [] if value.nil? && @column.form_ui && @column.association&.collection?
160
- end
153
+ value_record = record_for_update_column
154
+ return unless value_record
161
155
 
156
+ value = value_for_update_column(value, @column, value_record)
162
157
  value_record.send("#{@column.name}=", value)
163
158
  before_update_save(@record)
164
159
  self.successful = value_record.save
@@ -175,6 +170,29 @@ module ActiveScaffold::Actions
175
170
  after_update_save(@record)
176
171
  end
177
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
+
178
196
  # override this method if you want to inject data in the record (or its associated objects) before the save
179
197
  def before_update_save(record); end
180
198
 
@@ -199,7 +217,7 @@ module ActiveScaffold::Actions
199
217
  private
200
218
 
201
219
  def update_authorized_filter
202
- 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
203
221
  raise ActiveScaffold::ActionNotAllowed unless Array(send(link.security_method))[0]
204
222
  end
205
223
 
@@ -71,6 +71,15 @@ module ActiveScaffold
71
71
  !params_hash?(value) && association.belongs_to? && association.counter_cache_hack?
72
72
  end
73
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
+
74
83
  # Takes attributes (as from params[:record]) and applies them to the parent_record. Also looks for
75
84
  # association attributes and attempts to instantiate them as associated objects.
76
85
  #
@@ -80,28 +89,22 @@ module ActiveScaffold
80
89
  crud_type = parent_record.new_record? ? :create : :update
81
90
  return parent_record unless parent_record.authorized_for?(:crud_type => crud_type)
82
91
 
83
- multi_parameter_attributes = {}
84
- attributes.each do |k, v|
85
- next unless k.include? '('
86
- column_name = k.split('(').first
87
- multi_parameter_attributes[column_name] ||= []
88
- multi_parameter_attributes[column_name] << [k, v]
89
- end
92
+ multi_parameter_attrs = multi_parameter_attributes(attributes)
90
93
 
91
94
  columns.each_column(for: parent_record, crud_type: crud_type, flatten: true) do |column|
92
95
  begin
93
96
  # Set any passthrough parameters that may be associated with this column (ie, file column "keep" and "temp" attributes)
94
- unless column.params.empty?
95
- column.params.each { |p| parent_record.send("#{p}=", attributes[p]) if attributes.key? p }
96
- end
97
+ column.params.select { |p| attributes.key? p }.each { |p| parent_record.send("#{p}=", attributes[p]) }
97
98
 
98
- if multi_parameter_attributes.key? column.name.to_s
99
- 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])
100
101
  elsif attributes.key? column.name
101
- 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)
102
103
  end
103
104
  rescue StandardError => e
104
- Rails.logger.error "#{e.class.name}: #{e.message} -- on the ActiveScaffold column = :#{column.name} for #{parent_record.inspect}#{" with value #{value}" if value}"
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}"
105
108
  raise
106
109
  end
107
110
  end
@@ -111,33 +114,40 @@ module ActiveScaffold
111
114
 
112
115
  def update_column_from_params(parent_record, column, attribute, avoid_changes = false)
113
116
  value = column_value_from_param_value(parent_record, column, attribute, avoid_changes)
114
- if avoid_changes && column.association
115
- parent_record.association(column.name).target = value
116
- parent_record.send("#{column.association.foreign_key}=", value&.id) if column.association.belongs_to?
117
- elsif column.association && counter_cache_hack?(column.association, attribute)
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)
118
136
  parent_record.send "#{column.association.foreign_key}=", value&.id
119
137
  parent_record.association(column.name).target = value
120
- elsif column.association&.collection? && column.association.through? && !column.association.through_reflection.collection?
138
+ elsif column.association.collection? && column.association.through_singular?
121
139
  through = column.association.through_reflection.name
122
140
  through_record = parent_record.send(through)
123
141
  through_record ||= parent_record.send "build_#{through}"
124
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)
125
145
  else
126
- begin
127
- if column.association && hack_for_has_many_counter_cache?(parent_record, column)
128
- hack_for_has_many_counter_cache(parent_record, column, value)
129
- else
130
- parent_record.send "#{column.name}=", value
131
- end
132
- rescue ActiveRecord::RecordNotSaved
133
- parent_record.errors.add column.name, :invalid
134
- parent_record.association(column.name).target = value if column.association
135
- end
136
- end
137
- if column.association&.reverse_association&.belongs_to?
138
- Array(value).each { |v| v.send("#{column.association.reverse}=", parent_record) if v.new_record? }
146
+ parent_record.send "#{column.name}=", value
139
147
  end
140
- value
148
+ rescue ActiveRecord::RecordNotSaved
149
+ parent_record.errors.add column.name, :invalid
150
+ parent_record.association(column.name).target = value
141
151
  end
142
152
 
143
153
  def column_value_from_param_value(parent_record, column, value, avoid_changes = false)
@@ -172,27 +182,24 @@ module ActiveScaffold
172
182
  Date.parse("#{value}-01")
173
183
  end
174
184
 
175
- def column_value_from_param_simple_value(parent_record, column, value)
176
- if column.association&.singular?
177
- if value.present?
178
- if column.association.polymorphic?
179
- class_name = parent_record.send(column.association.foreign_type)
180
- class_name.constantize.find(value) if class_name.present?
181
- else
182
- # it's a single id
183
- column.association.klass.find(value)
184
- end
185
- end
186
- elsif column.association&.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?
187
189
  column_plural_assocation_value_from_value(column, Array(value))
188
- 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?
189
197
  column.number_to_native(value)
190
- else
198
+ elsif value.is_a?(String) && value.empty? && !column.virtual?
191
199
  # convert empty strings into nil. this works better with 'null => true' columns (and validations),
192
200
  # for 'null => false' columns is just converted to default value from column
193
- if value.is_a?(String) && value.empty? && !column.column.nil?
194
- value = column.column.null ? nil : column.column.default
195
- end
201
+ column.default_for_empty_value
202
+ else
196
203
  value
197
204
  end
198
205
  end
@@ -248,17 +255,22 @@ module ActiveScaffold
248
255
  if params.key? klass.primary_key
249
256
  record_from_current_or_find(klass, params[klass.primary_key], current)
250
257
  elsif klass.authorized_for?(:crud_type => :create)
251
- parent_column.association.klass.new
258
+ association = parent_column.association
259
+ record = association.klass.new
260
+ if association.reverse_association&.belongs_to? && (association.collection? || current.nil?)
261
+ record.send("#{parent_column.association.reverse}=", parent_record)
262
+ end
263
+ record
252
264
  end
253
265
  end
254
266
 
255
267
  # Attempts to find an instance of klass (which must be an ActiveRecord object) with id primary key
256
268
  # Returns record from current if it's included or find from DB
257
269
  def record_from_current_or_find(klass, id, current)
258
- if current && current.is_a?(ActiveRecord::Base) && current.id.to_s == id
270
+ if current.is_a?(ActiveRecord::Base) && current.id.to_s == id
259
271
  # modifying the current object of a singular association
260
272
  current
261
- elsif current && current.respond_to?(:any?) && current.any? { |o| o.id.to_s == id }
273
+ elsif current.respond_to?(:any?) && current.any? { |o| o.id.to_s == id }
262
274
  # modifying one of the current objects in a plural association
263
275
  current.detect { |o| o.id.to_s == id }
264
276
  else # attaching an existing but not-current object
@@ -266,10 +278,13 @@ module ActiveScaffold
266
278
  end
267
279
  end
268
280
 
269
- def save_record_to_association(record, association, value)
270
- if association&.collection?
281
+ def save_record_to_association(record, association, value, reverse = nil)
282
+ return unless association
283
+ if association.collection?
271
284
  record.send(association.name) << value
272
- elsif association
285
+ elsif reverse&.belongs_to?
286
+ value.send("#{reverse.name}=", record)
287
+ else
273
288
  record.send("#{association.name}=", value)
274
289
  end
275
290
  end
@@ -277,25 +292,17 @@ module ActiveScaffold
277
292
  # Determines whether the given attributes hash is "empty".
278
293
  # This isn't a literal emptiness - it's an attempt to discern whether the user intended it to be empty or not.
279
294
  def attributes_hash_is_empty?(hash, klass)
280
- # old style date form management... ignore them too
281
- part_ignore_column_types = [:datetime, :date, :time, Time, Date]
282
-
283
295
  hash.all? do |key, value|
284
296
  # convert any possible multi-parameter attributes like 'created_at(5i)' to simply 'created_at'
285
- parts = key.to_s.split('(')
286
- column_name = parts.first
287
- column = ActiveScaffold::OrmChecks.columns_hash(klass)[column_name]
288
- column_type = ActiveScaffold::OrmChecks.column_type(klass, column_name) if column
297
+ column_name = key.to_s.split('(', 2)[0]
289
298
 
290
299
  # datetimes will always have a value. so we ignore them when checking whether the hash is empty.
291
300
  # this could be a bad idea. but the current situation (excess record entry) seems worse.
292
- next true if column && parts.length > 1 && part_ignore_column_types.include?(column_type)
301
+ next true if mulitpart_ignored?(key, klass)
293
302
 
294
303
  # defaults are pre-filled on the form. we can't use them to determine if the user intends a new row.
295
304
  # booleans always have value, so they are ignored if not changed from default
296
- default_value = column_default_value(column_name, klass, column)
297
- casted_value = column ? ActiveScaffold::Core.column_type_cast(value, column) : value
298
- next true if casted_value == default_value
305
+ next true if default_value?(column_name, klass, value)
299
306
 
300
307
  if params_hash? value
301
308
  attributes_hash_is_empty?(value, klass)
@@ -307,7 +314,25 @@ module ActiveScaffold
307
314
  end
308
315
  end
309
316
 
310
- def column_default_value(column_name, klass, column)
317
+ # old style date form management... ignore them
318
+ MULTIPART_IGNORE_TYPES = [:datetime, :date, :time, Time, Date].freeze
319
+
320
+ def mulitpart_ignored?(param_name, klass)
321
+ column_name, multipart = param_name.to_s.split('(', 2)
322
+ return false unless multipart
323
+ column_type = ActiveScaffold::OrmChecks.column_type(klass, column_name)
324
+ MULTIPART_IGNORE_TYPES.include?(column_type) if column_type
325
+ end
326
+
327
+ def default_value?(column_name, klass, value)
328
+ column = ActiveScaffold::OrmChecks.columns_hash(klass)[column_name]
329
+ default_value = column_default_value(column_name, klass)
330
+ casted_value = ActiveScaffold::Core.column_type_cast(value, column)
331
+ casted_value == default_value
332
+ end
333
+
334
+ def column_default_value(column_name, klass)
335
+ column = ActiveScaffold::OrmChecks.columns_hash(klass)[column_name]
311
336
  return unless column
312
337
  if ActiveScaffold::OrmChecks.mongoid? klass
313
338
  column.default_val