active_scaffold 3.3.3 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +39 -0
  3. data/README.md +5 -3
  4. data/app/assets/images/active_scaffold/refresh.png +0 -0
  5. data/app/assets/javascripts/jquery/active_scaffold.js +182 -91
  6. data/app/assets/javascripts/jquery/date_picker_bridge.js.erb +14 -16
  7. data/app/assets/javascripts/jquery/draggable_lists.js +33 -26
  8. data/app/assets/javascripts/jquery/jquery.editinplace.js +3 -3
  9. data/app/assets/javascripts/prototype/active_scaffold.js +61 -19
  10. data/app/assets/stylesheets/active_scaffold_colors.css.scss +4 -0
  11. data/app/assets/stylesheets/active_scaffold_images.css.scss +3 -0
  12. data/app/assets/stylesheets/active_scaffold_layout.css +23 -2
  13. data/app/views/active_scaffold_overrides/_add_existing_form.html.erb +1 -3
  14. data/app/views/active_scaffold_overrides/_base_form.html.erb +7 -5
  15. data/app/views/active_scaffold_overrides/_field_search.html.erb +1 -2
  16. data/app/views/active_scaffold_overrides/_form.html.erb +6 -4
  17. data/app/views/active_scaffold_overrides/_form_association.html.erb +4 -3
  18. data/app/views/active_scaffold_overrides/_form_association_footer.html.erb +5 -5
  19. data/app/views/active_scaffold_overrides/_form_association_record.html.erb +8 -6
  20. data/app/views/active_scaffold_overrides/_horizontal_subform_header.html.erb +3 -2
  21. data/app/views/active_scaffold_overrides/_list.html.erb +8 -6
  22. data/app/views/active_scaffold_overrides/_list_column_headings.html.erb +1 -4
  23. data/app/views/active_scaffold_overrides/_list_pagination.html.erb +4 -4
  24. data/app/views/active_scaffold_overrides/_list_pagination_links.html.erb +1 -1
  25. data/app/views/active_scaffold_overrides/_list_record.html.erb +3 -3
  26. data/app/views/active_scaffold_overrides/_refresh_list.js.erb +8 -1
  27. data/app/views/active_scaffold_overrides/_search.html.erb +7 -13
  28. data/app/views/active_scaffold_overrides/_show_columns.html.erb +1 -1
  29. data/app/views/active_scaffold_overrides/on_create.js.erb +4 -4
  30. data/app/views/active_scaffold_overrides/render_field_inplace.html.erb +1 -1
  31. data/app/views/active_scaffold_overrides/row.js.erb +1 -1
  32. data/config/locales/de.yml +106 -95
  33. data/config/locales/en.yml +108 -97
  34. data/config/locales/es.yml +109 -98
  35. data/config/locales/fr.yml +108 -97
  36. data/config/locales/hu.yml +109 -98
  37. data/config/locales/ja.yml +100 -89
  38. data/config/locales/ru.yml +115 -104
  39. data/lib/active_scaffold.rb +18 -294
  40. data/lib/active_scaffold/actions/common_search.rb +50 -17
  41. data/lib/active_scaffold/actions/core.rb +93 -22
  42. data/lib/active_scaffold/actions/create.rb +15 -6
  43. data/lib/active_scaffold/actions/field_search.rb +68 -60
  44. data/lib/active_scaffold/actions/list.rb +49 -28
  45. data/lib/active_scaffold/actions/nested.rb +14 -6
  46. data/lib/active_scaffold/actions/search.rb +36 -35
  47. data/lib/active_scaffold/actions/show.rb +9 -4
  48. data/lib/active_scaffold/actions/subform.rb +1 -1
  49. data/lib/active_scaffold/actions/update.rb +22 -7
  50. data/lib/active_scaffold/active_record_permissions.rb +125 -118
  51. data/lib/active_scaffold/attribute_params.rb +84 -66
  52. data/lib/active_scaffold/bridges.rb +3 -3
  53. data/lib/active_scaffold/bridges/ancestry/ancestry_bridge.rb +10 -5
  54. data/lib/active_scaffold/bridges/cancan.rb +2 -1
  55. data/lib/active_scaffold/bridges/cancan/cancan_bridge.rb +13 -2
  56. data/lib/active_scaffold/bridges/carrierwave/form_ui.rb +11 -6
  57. data/lib/active_scaffold/bridges/chosen/helpers.rb +2 -2
  58. data/lib/active_scaffold/bridges/country_helper/country_helper_bridge.rb +45 -29
  59. data/lib/active_scaffold/bridges/date_picker/ext.rb +11 -6
  60. data/lib/active_scaffold/bridges/date_picker/helper.rb +5 -1
  61. data/lib/active_scaffold/bridges/dragonfly/form_ui.rb +10 -5
  62. data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +6 -1
  63. data/lib/active_scaffold/bridges/file_column/form_ui.rb +12 -11
  64. data/lib/active_scaffold/bridges/paperclip/form_ui.rb +14 -6
  65. data/lib/active_scaffold/bridges/paperclip/list_ui.rb +1 -1
  66. data/lib/active_scaffold/bridges/record_select/helpers.rb +15 -12
  67. data/lib/active_scaffold/bridges/shared/date_bridge.rb +7 -8
  68. data/lib/active_scaffold/bridges/tiny_mce.rb +5 -3
  69. data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +4 -5
  70. data/lib/active_scaffold/config/base.rb +4 -0
  71. data/lib/active_scaffold/config/core.rb +12 -5
  72. data/lib/active_scaffold/config/delete.rb +0 -2
  73. data/lib/active_scaffold/config/field_search.rb +1 -4
  74. data/lib/active_scaffold/config/form.rb +0 -2
  75. data/lib/active_scaffold/config/list.rb +31 -1
  76. data/lib/active_scaffold/config/search.rb +0 -3
  77. data/lib/active_scaffold/config/show.rb +0 -6
  78. data/lib/active_scaffold/config/subform.rb +1 -0
  79. data/lib/active_scaffold/configurable.rb +2 -2
  80. data/lib/active_scaffold/constraints.rb +11 -14
  81. data/lib/active_scaffold/core.rb +277 -0
  82. data/lib/active_scaffold/data_structures/action_columns.rb +18 -2
  83. data/lib/active_scaffold/data_structures/action_link.rb +25 -6
  84. data/lib/active_scaffold/data_structures/action_links.rb +9 -4
  85. data/lib/active_scaffold/data_structures/actions.rb +1 -1
  86. data/lib/active_scaffold/data_structures/column.rb +6 -6
  87. data/lib/active_scaffold/data_structures/columns.rb +2 -2
  88. data/lib/active_scaffold/data_structures/nested_info.rb +5 -1
  89. data/lib/active_scaffold/data_structures/sorting.rb +15 -5
  90. data/lib/active_scaffold/delayed_setup.rb +30 -0
  91. data/lib/active_scaffold/engine.rb +25 -0
  92. data/lib/active_scaffold/extensions/action_view_rendering.rb +1 -1
  93. data/lib/active_scaffold/extensions/left_outer_joins.rb +61 -21
  94. data/lib/active_scaffold/extensions/localize.rb +1 -1
  95. data/lib/active_scaffold/extensions/name_option_for_datetime.rb +13 -8
  96. data/lib/active_scaffold/extensions/paginator_extensions.rb +5 -1
  97. data/lib/active_scaffold/extensions/reverse_associations.rb +1 -0
  98. data/lib/active_scaffold/extensions/routing_mapper.rb +1 -1
  99. data/lib/active_scaffold/extensions/unsaved_record.rb +4 -6
  100. data/lib/active_scaffold/finder.rb +79 -27
  101. data/lib/active_scaffold/helpers/association_helpers.rb +48 -18
  102. data/lib/active_scaffold/helpers/controller_helpers.rb +19 -10
  103. data/lib/active_scaffold/helpers/form_column_helpers.rb +185 -87
  104. data/lib/active_scaffold/helpers/human_condition_helpers.rb +2 -1
  105. data/lib/active_scaffold/helpers/id_helpers.rb +14 -8
  106. data/lib/active_scaffold/helpers/list_column_helpers.rb +65 -56
  107. data/lib/active_scaffold/helpers/pagination_helpers.rb +5 -1
  108. data/lib/active_scaffold/helpers/search_column_helpers.rb +21 -18
  109. data/lib/active_scaffold/helpers/view_helpers.rb +102 -64
  110. data/lib/active_scaffold/responds_to_parent.rb +39 -64
  111. data/lib/active_scaffold/tableless.rb +129 -10
  112. data/lib/active_scaffold/version.rb +2 -2
  113. data/test/bridges/bridge_test.rb +1 -1
  114. data/test/bridges/date_picker_test.rb +2 -2
  115. data/test/bridges/paperclip_test.rb +10 -8
  116. data/test/bridges/tiny_mce_test.rb +2 -2
  117. data/test/company.rb +22 -10
  118. data/test/config/base_test.rb +1 -1
  119. data/test/config/core_test.rb +8 -6
  120. data/test/config/create_test.rb +6 -6
  121. data/test/config/delete_test.rb +4 -4
  122. data/test/config/field_search_test.rb +6 -6
  123. data/test/config/list_test.rb +7 -7
  124. data/test/config/nested_test.rb +8 -7
  125. data/test/config/search_test.rb +7 -7
  126. data/test/config/show_test.rb +5 -5
  127. data/test/config/subform_test.rb +1 -1
  128. data/test/config/update_test.rb +5 -4
  129. data/test/data_structures/action_columns_test.rb +15 -16
  130. data/test/data_structures/action_link_test.rb +10 -10
  131. data/test/data_structures/action_links_test.rb +6 -6
  132. data/test/data_structures/actions_test.rb +4 -4
  133. data/test/data_structures/association_column_test.rb +4 -4
  134. data/test/data_structures/column_test.rb +9 -9
  135. data/test/data_structures/columns_test.rb +7 -7
  136. data/test/data_structures/error_message_test.rb +2 -4
  137. data/test/data_structures/set_test.rb +13 -13
  138. data/test/data_structures/sorting_test.rb +8 -8
  139. data/test/data_structures/standard_column_test.rb +2 -2
  140. data/test/data_structures/validation_reflection_test.rb +8 -8
  141. data/test/data_structures/virtual_column_test.rb +5 -5
  142. data/test/extensions/active_record_test.rb +1 -1
  143. data/test/helpers/form_column_helpers_test.rb +5 -5
  144. data/test/helpers/list_column_helpers_test.rb +2 -1
  145. data/test/helpers/pagination_helpers_test.rb +1 -1
  146. data/test/misc/active_record_permissions_test.rb +23 -4
  147. data/test/misc/attribute_params_test.rb +304 -136
  148. data/test/misc/calculation_test.rb +55 -0
  149. data/test/misc/configurable_test.rb +22 -21
  150. data/test/misc/constraints_test.rb +10 -7
  151. data/test/misc/convert_numbers_format_test.rb +149 -0
  152. data/test/misc/finder_test.rb +17 -13
  153. data/test/misc/lang_test.rb +1 -1
  154. data/test/misc/tableless_test.rb +18 -0
  155. data/test/mock_app/app/controllers/addresses_controller.rb +4 -0
  156. data/test/mock_app/app/controllers/buildings_controller.rb +4 -0
  157. data/test/mock_app/app/controllers/cars_controller.rb +4 -0
  158. data/test/mock_app/app/controllers/contacts_controller.rb +4 -0
  159. data/test/mock_app/app/controllers/floors_controller.rb +6 -0
  160. data/test/mock_app/app/controllers/people_controller.rb +4 -0
  161. data/test/mock_app/app/models/address.rb +3 -0
  162. data/test/mock_app/app/models/building.rb +8 -0
  163. data/test/mock_app/app/models/car.rb +3 -0
  164. data/test/mock_app/app/models/contact.rb +3 -0
  165. data/test/mock_app/app/models/file_model.rb +19 -0
  166. data/test/mock_app/app/models/floor.rb +8 -0
  167. data/test/mock_app/app/models/person.rb +11 -0
  168. data/test/mock_app/config/application.rb +2 -0
  169. data/test/mock_app/config/environments/test.rb +1 -1
  170. data/test/mock_app/config/initializers/secret_token.rb +5 -1
  171. data/test/mock_app/config/routes.rb +1 -1
  172. data/test/mock_app/db/schema.rb +51 -0
  173. data/test/model_stub.rb +3 -3
  174. data/test/test_helper.rb +15 -12
  175. metadata +51 -50
  176. data/lib/active_scaffold/extensions/array.rb +0 -7
  177. data/lib/active_scaffold/extensions/cache_association.rb +0 -16
  178. data/lib/active_scaffold/extensions/usa_state.rb +0 -46
  179. data/lib/active_scaffold_env.rb +0 -13
  180. data/test/extensions/array_test.rb +0 -12
  181. data/test/mock_app/public/blank.html +0 -33
  182. data/test/mock_app/public/images/active_scaffold/DO_NOT_EDIT +0 -2
  183. data/test/mock_app/public/images/active_scaffold/default/add.gif +0 -0
  184. data/test/mock_app/public/images/active_scaffold/default/arrow_down.gif +0 -0
  185. data/test/mock_app/public/images/active_scaffold/default/arrow_up.gif +0 -0
  186. data/test/mock_app/public/images/active_scaffold/default/close.gif +0 -0
  187. data/test/mock_app/public/images/active_scaffold/default/cross.png +0 -0
  188. data/test/mock_app/public/images/active_scaffold/default/indicator-small.gif +0 -0
  189. data/test/mock_app/public/images/active_scaffold/default/indicator.gif +0 -0
  190. data/test/mock_app/public/images/active_scaffold/default/magnifier.png +0 -0
  191. data/test/mock_app/public/javascripts/active_scaffold/DO_NOT_EDIT +0 -2
  192. data/test/mock_app/public/javascripts/active_scaffold/default/active_scaffold.js +0 -532
  193. data/test/mock_app/public/javascripts/active_scaffold/default/dhtml_history.js +0 -867
  194. data/test/mock_app/public/javascripts/active_scaffold/default/form_enhancements.js +0 -117
  195. data/test/mock_app/public/javascripts/active_scaffold/default/rico_corner.js +0 -370
  196. data/test/mock_app/public/stylesheets/active_scaffold/DO_NOT_EDIT +0 -2
  197. data/test/mock_app/public/stylesheets/active_scaffold/default/stylesheet-ie.css +0 -35
  198. data/test/mock_app/public/stylesheets/active_scaffold/default/stylesheet.css +0 -848
@@ -19,15 +19,15 @@ module ActiveScaffold::Actions
19
19
  protected
20
20
 
21
21
  def show_respond_to_json
22
- render :text => response_object.to_json(:only => active_scaffold_config.show.columns.names), :content_type => Mime::JSON, :status => response_status
22
+ render :text => response_object.to_json(:only => show_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(show_columns_names), :methods => virtual_columns(show_columns_names)), :content_type => Mime::JSON, :status => response_status
23
23
  end
24
24
 
25
25
  def show_respond_to_yaml
26
- render :text => Hash.from_xml(response_object.to_xml(:only => active_scaffold_config.show.columns.names)).to_yaml, :content_type => Mime::YAML, :status => response_status
26
+ render :text => Hash.from_xml(response_object.to_xml(:only => show_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(show_columns_names), :methods => virtual_columns(show_columns_names))).to_yaml, :content_type => Mime::YAML, :status => response_status
27
27
  end
28
28
 
29
29
  def show_respond_to_xml
30
- render :xml => response_object.to_xml(:only => active_scaffold_config.show.columns.names), :content_type => Mime::XML, :status => response_status
30
+ render :xml => response_object.to_xml(:only => show_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(show_columns_names), :methods => virtual_columns(show_columns_names)), :content_type => Mime::XML, :status => response_status
31
31
  end
32
32
 
33
33
  def show_respond_to_js
@@ -37,11 +37,16 @@ module ActiveScaffold::Actions
37
37
  def show_respond_to_html
38
38
  render :action => 'show'
39
39
  end
40
+
41
+ def show_columns_names
42
+ active_scaffold_config.show.columns.names
43
+ end
44
+
40
45
  # A simple method to retrieve and prepare a record for showing.
41
46
  # May be overridden to customize show routine
42
47
  def do_show
43
48
  set_includes_for_columns(:show) if active_scaffold_config.actions.include? :list
44
- klass = beginning_of_chain.includes(active_scaffold_includes)
49
+ klass = beginning_of_chain.preload(active_scaffold_preload)
45
50
  @record = find_if_allowed(params[:id], :read, klass)
46
51
  end
47
52
 
@@ -14,7 +14,7 @@ module ActiveScaffold::Actions
14
14
 
15
15
  # 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.
16
16
  @record = @column.association.klass.find(params[:associated_id]) if params[:associated_id]
17
- @record ||= build_associated(@column, @parent_record)
17
+ @record ||= build_associated(@column.association, @parent_record)
18
18
  @scope = params[:scope]
19
19
  end
20
20
 
@@ -34,14 +34,20 @@ module ActiveScaffold::Actions
34
34
  end
35
35
  def update_respond_to_html
36
36
  if params[:iframe]=='true' # was this an iframe post ?
37
- do_refresh_list if successful? && active_scaffold_config.create.refresh_list && !render_parent?
37
+ do_refresh_list if successful? && active_scaffold_config.update.refresh_list && !render_parent?
38
38
  responds_to_parent do
39
39
  render :action => 'on_update', :formats => [:js], :layout => false
40
40
  end
41
41
  else # just a regular post
42
42
  if successful?
43
- flash[:info] = as_(:updated_model, :model => @record.to_label)
44
- return_to_main
43
+ message = as_(:updated_model, :model => @record.to_label)
44
+ if params[:dont_close]
45
+ flash.now[:info] = message
46
+ render(:action => 'update')
47
+ else
48
+ flash[:info] = message
49
+ return_to_main
50
+ end
45
51
  else
46
52
  render(:action => 'update')
47
53
  end
@@ -63,13 +69,17 @@ module ActiveScaffold::Actions
63
69
  render :action => 'on_update'
64
70
  end
65
71
  def update_respond_to_xml
66
- render :xml => response_object.to_xml(:only => active_scaffold_config.update.columns.names), :content_type => Mime::XML, :status => response_status
72
+ render :xml => response_object.to_xml(:only => update_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(update_columns_names), :methods => virtual_columns(update_columns_names)), :content_type => Mime::XML, :status => response_status
67
73
  end
68
74
  def update_respond_to_json
69
- render :text => response_object.to_json(:only => active_scaffold_config.update.columns.names), :content_type => Mime::JSON, :status => response_status
75
+ render :text => response_object.to_json(:only => update_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(update_columns_names), :methods => virtual_columns(update_columns_names)), :content_type => Mime::JSON, :status => response_status
70
76
  end
71
77
  def update_respond_to_yaml
72
- render :text => Hash.from_xml(response_object.to_xml(:only => active_scaffold_config.update.columns.names)).to_yaml, :content_type => Mime::YAML, :status => response_status
78
+ render :text => Hash.from_xml(response_object.to_xml(:only => update_columns_names + [active_scaffold_config.model.primary_key], :include => association_columns(update_columns_names), :methods => virtual_columns(update_columns_names))).to_yaml, :content_type => Mime::YAML, :status => response_status
79
+ end
80
+
81
+ def update_columns_names
82
+ active_scaffold_config.update.columns.names
73
83
  end
74
84
 
75
85
  # A simple method to find and prepare a record for editing
@@ -104,7 +114,12 @@ module ActiveScaffold::Actions
104
114
  rescue ActiveRecord::StaleObjectError
105
115
  @record.errors.add(:base, as_(:version_inconsistency))
106
116
  self.successful = false
107
- rescue ActiveRecord::RecordNotSaved
117
+ rescue ActiveRecord::RecordNotSaved => exception
118
+ logger.warn {
119
+ "\n\n#{exception.class} (#{exception.message}):\n " +
120
+ Rails.backtrace_cleaner.clean(exception.backtrace).join("\n ") +
121
+ "\n\n"
122
+ }
108
123
  @record.errors.add(:base, as_(:record_not_saved)) if @record.errors.empty?
109
124
  self.successful = false
110
125
  rescue ActiveRecord::ActiveRecordError => ex
@@ -9,143 +9,150 @@
9
9
  # Your methods should allow for the following special cases:
10
10
  # * cron scripts
11
11
  # * guest users (or nil current_user objects)
12
- module ActiveRecordPermissions
13
- # ActiveRecordPermissions needs to know what method on your ApplicationController will return the current user,
14
- # if available. This defaults to the :current_user method. You may configure this in your environment.rb if you
15
- # have a different setup.
16
- def self.current_user_method=(v); @@current_user_method = v; end
17
- def self.current_user_method; @@current_user_method; end
18
- @@current_user_method = :current_user
19
-
20
- # Whether the default permission is permissive or not
21
- # If set to true, then everything's allowed until configured otherwise
22
- def self.default_permission=(v); @@default_permission = v; end
23
- def self.default_permission; @@default_permission; end
24
- @@default_permission = true
25
-
26
- # This is a module aimed at making the current_user available to ActiveRecord models for permissions.
27
- module ModelUserAccess
28
- module Controller
29
- def self.included(base)
30
- base.prepend_before_filter :assign_current_user_to_models
31
- end
12
+ module ActiveScaffold
13
+ module ActiveRecordPermissions
14
+ # ActiveRecordPermissions needs to know what method on your ApplicationController will return the current user,
15
+ # if available. This defaults to the :current_user method. You may configure this in your environment.rb if you
16
+ # have a different setup.
17
+ def self.current_user_method=(v); @@current_user_method = v; end
18
+ def self.current_user_method; @@current_user_method; end
19
+ @@current_user_method = :current_user
20
+
21
+ # Whether the default permission is permissive or not
22
+ # If set to true, then everything's allowed until configured otherwise
23
+ def self.default_permission=(v); @@default_permission = v; end
24
+ def self.default_permission; @@default_permission; end
25
+ @@default_permission = true
26
+
27
+ # This is a module aimed at making the current_user available to ActiveRecord models for permissions.
28
+ module ModelUserAccess
29
+ module Controller
30
+ def self.included(base)
31
+ base.prepend_before_filter :assign_current_user_to_models
32
+ end
32
33
 
33
- # We need to give the ActiveRecord classes a handle to the current user. We don't want to just pass the object,
34
- # because the object may change (someone may log in or out). So we give ActiveRecord a proc that ties to the
35
- # current_user_method on this ApplicationController.
36
- def assign_current_user_to_models
37
- ActiveRecord::Base.current_user_proc = proc {send(ActiveRecordPermissions.current_user_method)}
34
+ # We need to give the ActiveRecord classes a handle to the current user. We don't want to just pass the object,
35
+ # because the object may change (someone may log in or out). So we give ActiveRecord a proc that ties to the
36
+ # current_user_method on this ApplicationController.
37
+ def assign_current_user_to_models
38
+ ActiveRecord::Base.current_user_proc = proc {send(ActiveRecordPermissions.current_user_method)}
39
+ end
38
40
  end
39
- end
40
41
 
41
- module Model
42
- def self.included(base)
43
- base.extend ClassMethods
44
- base.send :include, ActiveRecordPermissions::Permissions
45
- end
42
+ module Model
43
+ def self.included(base)
44
+ base.extend ClassMethods
45
+ base.send :include, ActiveRecordPermissions::Permissions
46
+ end
46
47
 
47
- module ClassMethods
48
- # The proc to call that retrieves the current_user from the ApplicationController.
49
- attr_accessor :current_user_proc
48
+ module ClassMethods
49
+ # The proc to call that retrieves the current_user from the ApplicationController.
50
+ def current_user_proc
51
+ Thread.current[:current_user_proc]
52
+ end
53
+ def current_user_proc=(proc_value)
54
+ Thread.current[:current_user_proc] = proc_value
55
+ end
56
+
57
+ # Class-level access to the current user
58
+ def current_user
59
+ ActiveRecord::Base.current_user_proc.call if ActiveRecord::Base.current_user_proc
60
+ end
61
+ end
50
62
 
51
- # Class-level access to the current user
63
+ # Instance-level access to the current user
52
64
  def current_user
53
- ActiveRecord::Base.current_user_proc.call if ActiveRecord::Base.current_user_proc
65
+ self.class.current_user
54
66
  end
55
67
  end
56
-
57
- # Instance-level access to the current user
58
- def current_user
59
- self.class.current_user
60
- end
61
68
  end
62
- end
63
69
 
64
- module Permissions
65
- def self.included(base)
66
- base.extend SecurityMethods
67
- base.send :include, SecurityMethods
68
- class << base
69
- attr_accessor :class_security_methods
70
- attr_accessor :instance_security_methods
70
+ module Permissions
71
+ def self.included(base)
72
+ base.extend SecurityMethods
73
+ base.send :include, SecurityMethods
74
+ class << base
75
+ attr_accessor :class_security_methods
76
+ attr_accessor :instance_security_methods
77
+ end
71
78
  end
72
- end
73
79
 
74
- # Because any class-level queries get delegated to the instance level via a new record,
75
- # it's useful to know when the authorization query is meant for a specific record or not.
76
- # But using new_record? is confusing, even though accurate. So this is basically just a wrapper.
77
- def existing_record_check?
78
- !new_record?
79
- end
80
-
81
- module SecurityMethods
82
- # A generic authorization query. This is what will be called programatically, since
83
- # the actual permission methods can't be guaranteed to exist. And because we want to
84
- # intelligently combine multiple applicable methods.
85
- #
86
- # options[:crud_type] should be a CRUD verb (:create, :read, :update, :destroy)
87
- # options[:column] should be the name of a model attribute
88
- # options[:action] is the name of a method
89
- def authorized_for?(options = {})
90
- raise ArgumentError, "unknown crud type #{options[:crud_type]}" if options[:crud_type] and ![:create, :read, :update, :delete].include?(options[:crud_type])
91
-
92
- # collect other possibly-related methods that actually exist
93
- methods = cached_authorized_for_methods(options)
94
- return ActiveRecordPermissions.default_permission if methods.empty?
95
- return send(methods.first) if methods.one?
96
-
97
- # if any method returns false, then return false
98
- return false if methods.any? {|m| !send(m)}
99
- true
80
+ # Because any class-level queries get delegated to the instance level via a new record,
81
+ # it's useful to know when the authorization query is meant for a specific record or not.
82
+ # But using new_record? is confusing, even though accurate. So this is basically just a wrapper.
83
+ def existing_record_check?
84
+ !new_record?
100
85
  end
101
-
102
- def cached_authorized_for_methods(options)
103
- key = "#{options[:crud_type]}##{options[:column]}##{options[:action]}"
104
- if self.is_a? Class
105
- self.class_security_methods ||= {}
106
- self.class_security_methods[key] ||= authorized_for_methods(options)
107
- else
108
- self.class.instance_security_methods ||= {}
109
- self.class.instance_security_methods[key] ||= authorized_for_methods(options)
86
+
87
+ module SecurityMethods
88
+ # A generic authorization query. This is what will be called programatically, since
89
+ # the actual permission methods can't be guaranteed to exist. And because we want to
90
+ # intelligently combine multiple applicable methods.
91
+ #
92
+ # options[:crud_type] should be a CRUD verb (:create, :read, :update, :destroy)
93
+ # options[:column] should be the name of a model attribute
94
+ # options[:action] is the name of a method
95
+ def authorized_for?(options = {})
96
+ raise ArgumentError, "unknown crud type #{options[:crud_type]}" if options[:crud_type] and ![:create, :read, :update, :delete].include?(options[:crud_type])
97
+
98
+ # collect other possibly-related methods that actually exist
99
+ methods = cached_authorized_for_methods(options)
100
+ return ActiveRecordPermissions.default_permission if methods.empty?
101
+ return send(methods.first) if methods.one?
102
+
103
+ # if any method returns false, then return false
104
+ return false if methods.any? {|m| !send(m)}
105
+ true
106
+ end
107
+
108
+ def cached_authorized_for_methods(options)
109
+ key = "#{options[:crud_type]}##{options[:column]}##{options[:action]}"
110
+ if self.is_a? Class
111
+ self.class_security_methods ||= {}
112
+ self.class_security_methods[key] ||= authorized_for_methods(options)
113
+ else
114
+ self.class.instance_security_methods ||= {}
115
+ self.class.instance_security_methods[key] ||= authorized_for_methods(options)
116
+ end
110
117
  end
111
- end
112
118
 
113
- def authorized_for_methods(options)
114
- # column_authorized_for_crud_type? has the highest priority over other methods,
115
- # you can disable a crud verb and enable that verb for a column
116
- # (for example, disable update and enable inplace_edit in a column)
117
- method = column_and_crud_type_security_method(options[:column], options[:crud_type])
118
- return [method] if method and respond_to?(method)
119
-
120
- # authorized_for_action? has higher priority than other methods,
121
- # you can disable a crud verb and enable an action with that crud verb
122
- # (for example, disable update and enable an action with update as crud type)
123
- method = action_security_method(options[:action])
124
- return [method] if method and respond_to?(method)
125
-
126
- # collect other possibly-related methods that actually exist
127
- methods = [
128
- column_security_method(options[:column]),
129
- crud_type_security_method(options[:crud_type]),
130
- ].compact.select {|m| respond_to?(m)}
131
- end
132
-
133
- private
119
+ def authorized_for_methods(options)
120
+ # column_authorized_for_crud_type? has the highest priority over other methods,
121
+ # you can disable a crud verb and enable that verb for a column
122
+ # (for example, disable update and enable inplace_edit in a column)
123
+ method = column_and_crud_type_security_method(options[:column], options[:crud_type])
124
+ return [method] if method and respond_to?(method)
125
+
126
+ # authorized_for_action? has higher priority than other methods,
127
+ # you can disable a crud verb and enable an action with that crud verb
128
+ # (for example, disable update and enable an action with update as crud type)
129
+ method = action_security_method(options[:action])
130
+ return [method] if method and respond_to?(method)
131
+
132
+ # collect other possibly-related methods that actually exist
133
+ methods = [
134
+ column_security_method(options[:column]),
135
+ crud_type_security_method(options[:crud_type]),
136
+ ].compact.select {|m| respond_to?(m)}
137
+ end
138
+
139
+ private
134
140
 
135
- def column_security_method(column)
136
- "#{column}_authorized?" if column
137
- end
141
+ def column_security_method(column)
142
+ "#{column}_authorized?" if column
143
+ end
138
144
 
139
- def crud_type_security_method(crud_type)
140
- "authorized_for_#{crud_type}?" if crud_type
141
- end
145
+ def crud_type_security_method(crud_type)
146
+ "authorized_for_#{crud_type}?" if crud_type
147
+ end
142
148
 
143
- def action_security_method(action)
144
- "authorized_for_#{action}?" if action
145
- end
149
+ def action_security_method(action)
150
+ "authorized_for_#{action}?" if action
151
+ end
146
152
 
147
- def column_and_crud_type_security_method(column, crud_type)
148
- "#{column}_authorized_for_#{crud_type}?" if column and crud_type
153
+ def column_and_crud_type_security_method(column, crud_type)
154
+ "#{column}_authorized_for_#{crud_type}?" if column and crud_type
155
+ end
149
156
  end
150
157
  end
151
158
  end
@@ -8,6 +8,8 @@ module ActiveScaffold
8
8
  # 'name' => 'John',
9
9
  # # a plural association hash
10
10
  # 'roles' => {
11
+ # # hack to be able to clear roles
12
+ # '0' => ''
11
13
  # # associate with an existing role
12
14
  # '5' => {'id' => 5}
13
15
  # # associate with an existing role and edit it
@@ -35,59 +37,52 @@ module ActiveScaffold
35
37
  #
36
38
  # This is a secure way to apply params to a record, because it's based on a loop over the columns
37
39
  # set. The columns set will not yield unauthorized columns, and it will not yield unregistered columns.
38
- def update_record_from_params(parent_record, columns, attributes)
40
+ def update_record_from_params(parent_record, columns, attributes, avoid_changes = false)
39
41
  crud_type = parent_record.new_record? ? :create : :update
40
42
  return parent_record unless parent_record.authorized_for?(:crud_type => crud_type)
41
43
 
42
44
  multi_parameter_attributes = {}
43
45
  attributes.each do |k, v|
44
46
  next unless k.include? '('
45
- column_name = k.split('(').first.to_sym
47
+ column_name = k.split('(').first
46
48
  multi_parameter_attributes[column_name] ||= []
47
49
  multi_parameter_attributes[column_name] << [k, v]
48
50
  end
49
51
 
50
52
  columns.each :for => parent_record, :crud_type => crud_type, :flatten => true do |column|
51
- # Set any passthrough parameters that may be associated with this column (ie, file column "keep" and "temp" attributes)
52
- unless column.params.empty?
53
- column.params.each{|p| parent_record.send("#{p}=", attributes[p]) if attributes.has_key? p}
54
- end
53
+ begin
54
+ # Set any passthrough parameters that may be associated with this column (ie, file column "keep" and "temp" attributes)
55
+ unless column.params.empty?
56
+ column.params.each{|p| parent_record.send("#{p}=", attributes[p]) if attributes.has_key? p}
57
+ end
55
58
 
56
- if multi_parameter_attributes.has_key? column.name
57
- parent_record.send(:assign_multiparameter_attributes, multi_parameter_attributes[column.name])
58
- elsif attributes.has_key? column.name
59
- value = column_value_from_param_value(parent_record, column, attributes[column.name])
60
- parent_record.send("#{column.name}=", value)
59
+ if multi_parameter_attributes.has_key? column.name.to_s
60
+ parent_record.send(:assign_multiparameter_attributes, multi_parameter_attributes[column.name.to_s])
61
+ elsif attributes.has_key? column.name
62
+ value = column_value_from_param_value(parent_record, column, attributes[column.name])
63
+ if avoid_changes && column.plural_association?
64
+ parent_record.association(column.name).target = value
65
+ else
66
+ begin
67
+ parent_record.send "#{column.name}=", value
68
+ rescue ActiveRecord::RecordNotSaved
69
+ parent_record.association(column.name).target = value
70
+ end
71
+ end
72
+ if column.association && [:has_one, :has_many].include?(column.association.macro) && column.association.reverse
73
+ Array(value).each { |v| v.send("#{column.association.reverse}=", parent_record) if v.new_record? }
74
+ end
75
+ end
76
+ rescue
77
+ logger.error "#{$!.class.name}: #{$!.message} -- on the ActiveScaffold column = :#{column.name} for #{parent_record.inspect}#{" with value #{value}" if value}"
78
+ raise
61
79
  end
62
80
  end
63
81
 
64
- if parent_record.new_record?
65
- parent_record.class.reflect_on_all_associations.each do |a|
66
- next unless [:has_one, :has_many].include?(a.macro) and not (a.options[:through] || a.options[:finder_sql])
67
- next unless (association_proxy = parent_record.send(a.name)).present?
68
-
69
- raise ActiveScaffold::ReverseAssociationRequired, "Association #{a.name} in class #{parent_record.class.name}: In order to support :has_one and :has_many where the parent record is new and the child record(s) validate the presence of the parent, ActiveScaffold requires the reverse association (the belongs_to)." unless a.reverse
70
-
71
- association_proxy = [association_proxy] if a.macro == :has_one
72
- association_proxy.each { |record| record.send("#{a.reverse}=", parent_record) }
73
- end
74
- end
75
-
76
82
  flash[:warning] = parent_record.errors.to_a.join("\n") if parent_record.errors.present?
77
83
  parent_record
78
84
  end
79
-
80
- def manage_nested_record_from_params(parent_record, column, attributes)
81
- record = find_or_create_for_params(attributes, column, parent_record)
82
- if record
83
- record_columns = active_scaffold_config_for(column.association.klass).subform.columns
84
- record_columns.constraint_columns = [column.association.reverse]
85
- update_record_from_params(record, record_columns, attributes)
86
- record.unsaved = true
87
- end
88
- record
89
- end
90
-
85
+
91
86
  def column_value_from_param_value(parent_record, column, value)
92
87
  # convert the value, possibly by instantiating associated objects
93
88
  form_ui = column.form_ui || column.column.try(:type)
@@ -109,7 +104,7 @@ module ActiveScaffold
109
104
  end
110
105
 
111
106
  def column_value_for_datetime_type(parent_record, column, value)
112
- new_value = self.class.condition_value_for_datetime(column, value, self.class.datetime_conversion_for_condition(column))
107
+ new_value = self.class.condition_value_for_datetime(column, value, self.datetime_conversion_for_value(column))
113
108
  if new_value.nil? && value.present?
114
109
  parent_record.errors.add column.name, :invalid
115
110
  end
@@ -145,54 +140,77 @@ module ActiveScaffold
145
140
  if value and not value.empty?
146
141
  ids = value.select {|id| id.present?}
147
142
  ids.empty? ? [] : column.association.klass.find(ids)
143
+ else
144
+ []
148
145
  end
149
146
  end
150
147
 
151
148
  def column_value_from_param_hash_value(parent_record, column, value)
152
- # this is just for backwards compatibility. we should clean this up in 2.0.
153
- if column.form_ui == :select
154
- ids = if column.singular_association?
155
- value[:id]
156
- else
157
- value.values.collect {|hash| hash[:id]}
158
- end
159
- (ids and not ids.empty?) ? column.association.klass.find(ids) : nil
160
-
161
- elsif column.singular_association?
149
+ if column.singular_association?
162
150
  manage_nested_record_from_params(parent_record, column, value)
163
151
  elsif column.plural_association?
152
+ value = value.sort if RUBY_VERSION < '1.9'
164
153
  # HACK to be able to delete all associated records, hash will include "0" => ""
165
- value.sort.collect {|key, value| manage_nested_record_from_params(parent_record, column, value) unless value == ""}.compact
154
+ value.collect {|key, value| manage_nested_record_from_params(parent_record, column, value) unless value == ""}.compact
166
155
  else
167
156
  value
168
157
  end
169
158
  end
159
+
160
+ def manage_nested_record_from_params(parent_record, column, attributes)
161
+ return nil unless build_record_from_params(attributes, column, parent_record)
162
+ record = find_or_create_for_params(attributes, column, parent_record)
163
+ if record
164
+ record_columns = active_scaffold_config_for(column.association.klass).subform.columns
165
+ record_columns.constraint_columns = [column.association.reverse]
166
+ update_record_from_params(record, record_columns, attributes)
167
+ record.unsaved = true
168
+ end
169
+ record
170
+ end
170
171
 
171
- # Attempts to create or find an instance of klass (which must be an ActiveRecord object) from the
172
- # request parameters given. If params[:id] exists it will attempt to find an existing object
172
+ def build_record_from_params(params, column, record)
173
+ current = record.send(column.name)
174
+ klass = column.association.klass
175
+ (column.plural_association? && !column.show_blank_record?(current)) || !attributes_hash_is_empty?(params, klass)
176
+ end
177
+
178
+ # Attempts to create or find an instance of the klass of the association in parent_column from the
179
+ # request parameters given. If params[primary_key] exists it will attempt to find an existing object
173
180
  # otherwise it will build a new one.
174
181
  def find_or_create_for_params(params, parent_column, parent_record)
175
182
  current = parent_record.send(parent_column.name)
176
183
  klass = parent_column.association.klass
177
- pk = klass.primary_key.to_sym
178
- return nil if parent_column.show_blank_record?(current) and attributes_hash_is_empty?(params, klass)
179
-
180
- if params.has_key? pk
181
- # modifying the current object of a singular association
182
- pk_val = params[pk]
183
- if current and current.is_a? ActiveRecord::Base and current.id.to_s == pk_val
184
- current
185
- # modifying one of the current objects in a plural association
186
- elsif current and current.respond_to?(:any?) and current.any? {|o| o.id.to_s == pk_val}
187
- current.detect {|o| o.id.to_s == pk_val}
188
- # attaching an existing but not-current object
189
- else
190
- klass.find(pk_val)
191
- end
192
- else
193
- build_associated(parent_column, parent_record) if klass.authorized_for?(:crud_type => :create)
184
+ if params.has_key? klass.primary_key
185
+ record_from_current_or_find(klass, params[klass.primary_key], current)
186
+ elsif klass.authorized_for?(:crud_type => :create)
187
+ parent_column.association.klass.new
188
+ end
189
+ end
190
+
191
+ # Attempts to find an instance of klass (which must be an ActiveRecord object) with id primary key
192
+ # Returns record from current if it's included or find from DB
193
+ def record_from_current_or_find(klass, id, current)
194
+ if current and current.is_a? ActiveRecord::Base and current.id.to_s == id
195
+ # modifying the current object of a singular association
196
+ current
197
+ elsif current and current.respond_to?(:any?) and current.any? {|o| o.id.to_s == id}
198
+ # modifying one of the current objects in a plural association
199
+ current.detect {|o| o.id.to_s == id}
200
+ else # attaching an existing but not-current object
201
+ klass.find(id)
202
+ end
203
+ end
204
+
205
+ def save_record_to_association(record, association_name, value)
206
+ association = record.class.reflect_on_association(association_name) if association_name
207
+ if association.try(:collection?)
208
+ record.send(association_name) << value
209
+ elsif association
210
+ record.send("#{association_name}=", value)
194
211
  end
195
212
  end
213
+
196
214
  # Determines whether the given attributes hash is "empty".
197
215
  # This isn't a literal emptiness - it's an attempt to discern whether the user intended it to be empty or not.
198
216
  def attributes_hash_is_empty?(hash, klass)