active_scaffold 3.3.3 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +39 -0
- data/README.md +5 -3
- data/app/assets/images/active_scaffold/refresh.png +0 -0
- data/app/assets/javascripts/jquery/active_scaffold.js +182 -91
- data/app/assets/javascripts/jquery/date_picker_bridge.js.erb +14 -16
- data/app/assets/javascripts/jquery/draggable_lists.js +33 -26
- data/app/assets/javascripts/jquery/jquery.editinplace.js +3 -3
- data/app/assets/javascripts/prototype/active_scaffold.js +61 -19
- data/app/assets/stylesheets/active_scaffold_colors.css.scss +4 -0
- data/app/assets/stylesheets/active_scaffold_images.css.scss +3 -0
- data/app/assets/stylesheets/active_scaffold_layout.css +23 -2
- data/app/views/active_scaffold_overrides/_add_existing_form.html.erb +1 -3
- data/app/views/active_scaffold_overrides/_base_form.html.erb +7 -5
- data/app/views/active_scaffold_overrides/_field_search.html.erb +1 -2
- data/app/views/active_scaffold_overrides/_form.html.erb +6 -4
- data/app/views/active_scaffold_overrides/_form_association.html.erb +4 -3
- data/app/views/active_scaffold_overrides/_form_association_footer.html.erb +5 -5
- data/app/views/active_scaffold_overrides/_form_association_record.html.erb +8 -6
- data/app/views/active_scaffold_overrides/_horizontal_subform_header.html.erb +3 -2
- data/app/views/active_scaffold_overrides/_list.html.erb +8 -6
- data/app/views/active_scaffold_overrides/_list_column_headings.html.erb +1 -4
- data/app/views/active_scaffold_overrides/_list_pagination.html.erb +4 -4
- data/app/views/active_scaffold_overrides/_list_pagination_links.html.erb +1 -1
- data/app/views/active_scaffold_overrides/_list_record.html.erb +3 -3
- data/app/views/active_scaffold_overrides/_refresh_list.js.erb +8 -1
- data/app/views/active_scaffold_overrides/_search.html.erb +7 -13
- data/app/views/active_scaffold_overrides/_show_columns.html.erb +1 -1
- data/app/views/active_scaffold_overrides/on_create.js.erb +4 -4
- data/app/views/active_scaffold_overrides/render_field_inplace.html.erb +1 -1
- data/app/views/active_scaffold_overrides/row.js.erb +1 -1
- data/config/locales/de.yml +106 -95
- data/config/locales/en.yml +108 -97
- data/config/locales/es.yml +109 -98
- data/config/locales/fr.yml +108 -97
- data/config/locales/hu.yml +109 -98
- data/config/locales/ja.yml +100 -89
- data/config/locales/ru.yml +115 -104
- data/lib/active_scaffold.rb +18 -294
- data/lib/active_scaffold/actions/common_search.rb +50 -17
- data/lib/active_scaffold/actions/core.rb +93 -22
- data/lib/active_scaffold/actions/create.rb +15 -6
- data/lib/active_scaffold/actions/field_search.rb +68 -60
- data/lib/active_scaffold/actions/list.rb +49 -28
- data/lib/active_scaffold/actions/nested.rb +14 -6
- data/lib/active_scaffold/actions/search.rb +36 -35
- data/lib/active_scaffold/actions/show.rb +9 -4
- data/lib/active_scaffold/actions/subform.rb +1 -1
- data/lib/active_scaffold/actions/update.rb +22 -7
- data/lib/active_scaffold/active_record_permissions.rb +125 -118
- data/lib/active_scaffold/attribute_params.rb +84 -66
- data/lib/active_scaffold/bridges.rb +3 -3
- data/lib/active_scaffold/bridges/ancestry/ancestry_bridge.rb +10 -5
- data/lib/active_scaffold/bridges/cancan.rb +2 -1
- data/lib/active_scaffold/bridges/cancan/cancan_bridge.rb +13 -2
- data/lib/active_scaffold/bridges/carrierwave/form_ui.rb +11 -6
- data/lib/active_scaffold/bridges/chosen/helpers.rb +2 -2
- data/lib/active_scaffold/bridges/country_helper/country_helper_bridge.rb +45 -29
- data/lib/active_scaffold/bridges/date_picker/ext.rb +11 -6
- data/lib/active_scaffold/bridges/date_picker/helper.rb +5 -1
- data/lib/active_scaffold/bridges/dragonfly/form_ui.rb +10 -5
- data/lib/active_scaffold/bridges/dragonfly/list_ui.rb +6 -1
- data/lib/active_scaffold/bridges/file_column/form_ui.rb +12 -11
- data/lib/active_scaffold/bridges/paperclip/form_ui.rb +14 -6
- data/lib/active_scaffold/bridges/paperclip/list_ui.rb +1 -1
- data/lib/active_scaffold/bridges/record_select/helpers.rb +15 -12
- data/lib/active_scaffold/bridges/shared/date_bridge.rb +7 -8
- data/lib/active_scaffold/bridges/tiny_mce.rb +5 -3
- data/lib/active_scaffold/bridges/tiny_mce/helpers.rb +4 -5
- data/lib/active_scaffold/config/base.rb +4 -0
- data/lib/active_scaffold/config/core.rb +12 -5
- data/lib/active_scaffold/config/delete.rb +0 -2
- data/lib/active_scaffold/config/field_search.rb +1 -4
- data/lib/active_scaffold/config/form.rb +0 -2
- data/lib/active_scaffold/config/list.rb +31 -1
- data/lib/active_scaffold/config/search.rb +0 -3
- data/lib/active_scaffold/config/show.rb +0 -6
- data/lib/active_scaffold/config/subform.rb +1 -0
- data/lib/active_scaffold/configurable.rb +2 -2
- data/lib/active_scaffold/constraints.rb +11 -14
- data/lib/active_scaffold/core.rb +277 -0
- data/lib/active_scaffold/data_structures/action_columns.rb +18 -2
- data/lib/active_scaffold/data_structures/action_link.rb +25 -6
- data/lib/active_scaffold/data_structures/action_links.rb +9 -4
- data/lib/active_scaffold/data_structures/actions.rb +1 -1
- data/lib/active_scaffold/data_structures/column.rb +6 -6
- data/lib/active_scaffold/data_structures/columns.rb +2 -2
- data/lib/active_scaffold/data_structures/nested_info.rb +5 -1
- data/lib/active_scaffold/data_structures/sorting.rb +15 -5
- data/lib/active_scaffold/delayed_setup.rb +30 -0
- data/lib/active_scaffold/engine.rb +25 -0
- data/lib/active_scaffold/extensions/action_view_rendering.rb +1 -1
- data/lib/active_scaffold/extensions/left_outer_joins.rb +61 -21
- data/lib/active_scaffold/extensions/localize.rb +1 -1
- data/lib/active_scaffold/extensions/name_option_for_datetime.rb +13 -8
- data/lib/active_scaffold/extensions/paginator_extensions.rb +5 -1
- data/lib/active_scaffold/extensions/reverse_associations.rb +1 -0
- data/lib/active_scaffold/extensions/routing_mapper.rb +1 -1
- data/lib/active_scaffold/extensions/unsaved_record.rb +4 -6
- data/lib/active_scaffold/finder.rb +79 -27
- data/lib/active_scaffold/helpers/association_helpers.rb +48 -18
- data/lib/active_scaffold/helpers/controller_helpers.rb +19 -10
- data/lib/active_scaffold/helpers/form_column_helpers.rb +185 -87
- data/lib/active_scaffold/helpers/human_condition_helpers.rb +2 -1
- data/lib/active_scaffold/helpers/id_helpers.rb +14 -8
- data/lib/active_scaffold/helpers/list_column_helpers.rb +65 -56
- data/lib/active_scaffold/helpers/pagination_helpers.rb +5 -1
- data/lib/active_scaffold/helpers/search_column_helpers.rb +21 -18
- data/lib/active_scaffold/helpers/view_helpers.rb +102 -64
- data/lib/active_scaffold/responds_to_parent.rb +39 -64
- data/lib/active_scaffold/tableless.rb +129 -10
- data/lib/active_scaffold/version.rb +2 -2
- data/test/bridges/bridge_test.rb +1 -1
- data/test/bridges/date_picker_test.rb +2 -2
- data/test/bridges/paperclip_test.rb +10 -8
- data/test/bridges/tiny_mce_test.rb +2 -2
- data/test/company.rb +22 -10
- data/test/config/base_test.rb +1 -1
- data/test/config/core_test.rb +8 -6
- data/test/config/create_test.rb +6 -6
- data/test/config/delete_test.rb +4 -4
- data/test/config/field_search_test.rb +6 -6
- data/test/config/list_test.rb +7 -7
- data/test/config/nested_test.rb +8 -7
- data/test/config/search_test.rb +7 -7
- data/test/config/show_test.rb +5 -5
- data/test/config/subform_test.rb +1 -1
- data/test/config/update_test.rb +5 -4
- data/test/data_structures/action_columns_test.rb +15 -16
- data/test/data_structures/action_link_test.rb +10 -10
- data/test/data_structures/action_links_test.rb +6 -6
- data/test/data_structures/actions_test.rb +4 -4
- data/test/data_structures/association_column_test.rb +4 -4
- data/test/data_structures/column_test.rb +9 -9
- data/test/data_structures/columns_test.rb +7 -7
- data/test/data_structures/error_message_test.rb +2 -4
- data/test/data_structures/set_test.rb +13 -13
- data/test/data_structures/sorting_test.rb +8 -8
- data/test/data_structures/standard_column_test.rb +2 -2
- data/test/data_structures/validation_reflection_test.rb +8 -8
- data/test/data_structures/virtual_column_test.rb +5 -5
- data/test/extensions/active_record_test.rb +1 -1
- data/test/helpers/form_column_helpers_test.rb +5 -5
- data/test/helpers/list_column_helpers_test.rb +2 -1
- data/test/helpers/pagination_helpers_test.rb +1 -1
- data/test/misc/active_record_permissions_test.rb +23 -4
- data/test/misc/attribute_params_test.rb +304 -136
- data/test/misc/calculation_test.rb +55 -0
- data/test/misc/configurable_test.rb +22 -21
- data/test/misc/constraints_test.rb +10 -7
- data/test/misc/convert_numbers_format_test.rb +149 -0
- data/test/misc/finder_test.rb +17 -13
- data/test/misc/lang_test.rb +1 -1
- data/test/misc/tableless_test.rb +18 -0
- data/test/mock_app/app/controllers/addresses_controller.rb +4 -0
- data/test/mock_app/app/controllers/buildings_controller.rb +4 -0
- data/test/mock_app/app/controllers/cars_controller.rb +4 -0
- data/test/mock_app/app/controllers/contacts_controller.rb +4 -0
- data/test/mock_app/app/controllers/floors_controller.rb +6 -0
- data/test/mock_app/app/controllers/people_controller.rb +4 -0
- data/test/mock_app/app/models/address.rb +3 -0
- data/test/mock_app/app/models/building.rb +8 -0
- data/test/mock_app/app/models/car.rb +3 -0
- data/test/mock_app/app/models/contact.rb +3 -0
- data/test/mock_app/app/models/file_model.rb +19 -0
- data/test/mock_app/app/models/floor.rb +8 -0
- data/test/mock_app/app/models/person.rb +11 -0
- data/test/mock_app/config/application.rb +2 -0
- data/test/mock_app/config/environments/test.rb +1 -1
- data/test/mock_app/config/initializers/secret_token.rb +5 -1
- data/test/mock_app/config/routes.rb +1 -1
- data/test/mock_app/db/schema.rb +51 -0
- data/test/model_stub.rb +3 -3
- data/test/test_helper.rb +15 -12
- metadata +51 -50
- data/lib/active_scaffold/extensions/array.rb +0 -7
- data/lib/active_scaffold/extensions/cache_association.rb +0 -16
- data/lib/active_scaffold/extensions/usa_state.rb +0 -46
- data/lib/active_scaffold_env.rb +0 -13
- data/test/extensions/array_test.rb +0 -12
- data/test/mock_app/public/blank.html +0 -33
- data/test/mock_app/public/images/active_scaffold/DO_NOT_EDIT +0 -2
- data/test/mock_app/public/images/active_scaffold/default/add.gif +0 -0
- data/test/mock_app/public/images/active_scaffold/default/arrow_down.gif +0 -0
- data/test/mock_app/public/images/active_scaffold/default/arrow_up.gif +0 -0
- data/test/mock_app/public/images/active_scaffold/default/close.gif +0 -0
- data/test/mock_app/public/images/active_scaffold/default/cross.png +0 -0
- data/test/mock_app/public/images/active_scaffold/default/indicator-small.gif +0 -0
- data/test/mock_app/public/images/active_scaffold/default/indicator.gif +0 -0
- data/test/mock_app/public/images/active_scaffold/default/magnifier.png +0 -0
- data/test/mock_app/public/javascripts/active_scaffold/DO_NOT_EDIT +0 -2
- data/test/mock_app/public/javascripts/active_scaffold/default/active_scaffold.js +0 -532
- data/test/mock_app/public/javascripts/active_scaffold/default/dhtml_history.js +0 -867
- data/test/mock_app/public/javascripts/active_scaffold/default/form_enhancements.js +0 -117
- data/test/mock_app/public/javascripts/active_scaffold/default/rico_corner.js +0 -370
- data/test/mock_app/public/stylesheets/active_scaffold/DO_NOT_EDIT +0 -2
- data/test/mock_app/public/stylesheets/active_scaffold/default/stylesheet-ie.css +0 -35
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
44
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
module
|
|
29
|
-
|
|
30
|
-
base
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
module Model
|
|
43
|
+
def self.included(base)
|
|
44
|
+
base.extend ClassMethods
|
|
45
|
+
base.send :include, ActiveRecordPermissions::Permissions
|
|
46
|
+
end
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
#
|
|
63
|
+
# Instance-level access to the current user
|
|
52
64
|
def current_user
|
|
53
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
def column_security_method(column)
|
|
142
|
+
"#{column}_authorized?" if column
|
|
143
|
+
end
|
|
138
144
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
145
|
+
def crud_type_security_method(crud_type)
|
|
146
|
+
"authorized_for_#{crud_type}?" if crud_type
|
|
147
|
+
end
|
|
142
148
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
149
|
+
def action_security_method(action)
|
|
150
|
+
"authorized_for_#{action}?" if action
|
|
151
|
+
end
|
|
146
152
|
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
column.params.
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
172
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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)
|