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.
- 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)
|