# frozen_string_literal: truerequire"active_support/core_ext/hash/slice"require"active_support/core_ext/hash/except"require"active_support/core_ext/module/anonymous"require"action_dispatch/http/mime_type"moduleActionController# = Action Controller Params Wrapper## Wraps the parameters hash into a nested hash. This will allow clients to# submit requests without having to specify any root elements.## This functionality is enabled by default for JSON, and can be customized by# setting the format array:## class ApplicationController < ActionController::Base# wrap_parameters format: [:json, :xml]# end## You could also turn it on per controller:## class UsersController < ApplicationController# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]# end## If you enable +ParamsWrapper+ for +:json+ format, instead of having to# send JSON parameters like this:## {"user": {"name": "Konata"}}## You can send parameters like this:## {"name": "Konata"}## And it will be wrapped into a nested hash with the key name matching the# controller's name. For example, if you're posting to +UsersController+,# your new +params+ hash will look like this:## {"name" => "Konata", "user" => {"name" => "Konata"}}## You can also specify the key in which the parameters should be wrapped to,# and also the list of attributes it should wrap by using either +:include+ or# +:exclude+ options like this:## class UsersController < ApplicationController# wrap_parameters :person, include: [:username, :password]# end## On Active Record models with no +:include+ or +:exclude+ option set,# it will only wrap the parameters returned by the class method# <tt>attribute_names</tt>.## If you're going to pass the parameters to an +ActiveModel+ object (such as# <tt>User.new(params[:user])</tt>), you might consider passing the model class to# the method instead. The +ParamsWrapper+ will actually try to determine the# list of attribute names from the model and only wrap those attributes:## class UsersController < ApplicationController# wrap_parameters Person# end## You still could pass +:include+ and +:exclude+ to set the list of attributes# you want to wrap.## By default, if you don't specify the key in which the parameters would be# wrapped to, +ParamsWrapper+ will actually try to determine if there's# a model related to it or not. This controller, for example:## class Admin::UsersController < ApplicationController# end## will try to check if +Admin::User+ or +User+ model exists, and use it to# determine the wrapper key respectively. If both models don't exist,# it will then fall back to use +user+ as the key.## To disable this functionality for a controller:## class UsersController < ApplicationController# wrap_parameters false# endmoduleParamsWrapperextendActiveSupport::ConcernEXCLUDE_PARAMETERS=%w(authenticity_token _method utf8)require"mutex_m"classOptions<Struct.new(:name,:format,:include,:exclude,:klass,:model)# :nodoc:includeMutex_mdefself.from_hash(hash)name=hash[:name]format=Array(hash[:format])include=hash[:include]&&Array(hash[:include]).collect(&:to_s)exclude=hash[:exclude]&&Array(hash[:exclude]).collect(&:to_s)newname,format,include,exclude,nil,nilenddefinitialize(name,format,include,exclude,klass,model)# :nodoc:super@include_set=include@name_set=nameenddefmodelsuper||self.model=_default_wrap_modelenddefincludereturnsuperif@include_setm=modelsynchronizedoreturnsuperif@include_set@include_set=trueunlesssuper||excludeifm.respond_to?(:attribute_names)&&m.attribute_names.any?self.include=m.attribute_namesifm.respond_to?(:stored_attributes)&&!m.stored_attributes.empty?self.include+=m.stored_attributes.values.flatten.map(&:to_s)endifm.respond_to?(:attribute_aliases)&&m.attribute_aliases.any?self.include+=m.attribute_aliases.keysendifm.respond_to?(:nested_attributes_options)&&m.nested_attributes_options.keys.any?self.include+=m.nested_attributes_options.keys.mapdo|key|(+key.to_s).concat("_attributes")endendself.includeendendendenddefnamereturnsuperif@name_setm=modelsynchronizedoreturnsuperif@name_set@name_set=trueunlesssuper||klass.anonymous?self.name=m?m.to_s.demodulize.underscore:klass.controller_name.singularizeendendendprivate# Determine the wrapper model from the controller's name. By convention,# this could be done by trying to find the defined model that has the# same singular name as the controller. For example, +UsersController+# will try to find if the +User+ model exists.## This method also does namespace lookup. Foo::Bar::UsersController will# try to find Foo::Bar::User, Foo::User and finally User.def_default_wrap_modelreturnnilifklass.anonymous?model_name=klass.name.delete_suffix("Controller").classifybeginifmodel_klass=model_name.safe_constantizemodel_klasselsenamespaces=model_name.split("::")namespaces.delete_at(-2)breakifnamespaces.last==model_namemodel_name=namespaces.join("::")endenduntilmodel_klassmodel_klassendendincludeddoclass_attribute:_wrapper_options,default: Options.from_hash(format: [])endmoduleClassMethodsdef_set_wrapper_options(options)self._wrapper_options=Options.from_hash(options)end# Sets the name of the wrapper key, or the model which +ParamsWrapper+# would use to determine the attribute names from.## ==== Examples# wrap_parameters format: :xml# # enables the parameter wrapper for XML format## wrap_parameters :person# # wraps parameters into +params[:person]+ hash## wrap_parameters Person# # wraps parameters by determining the wrapper key from Person class# # (+person+, in this case) and the list of attribute names## wrap_parameters include: [:username, :title]# # wraps only +:username+ and +:title+ attributes from parameters.## wrap_parameters false# # disables parameters wrapping for this controller altogether.## ==== Options# * <tt>:format</tt> - The list of formats in which the parameters wrapper# will be enabled.# * <tt>:include</tt> - The list of attribute names which parameters wrapper# will wrap into a nested hash.# * <tt>:exclude</tt> - The list of attribute names which parameters wrapper# will exclude from a nested hash.defwrap_parameters(name_or_model_or_options,options={})model=nilcasename_or_model_or_optionswhenHashoptions=name_or_model_or_optionswhenfalseoptions=options.merge(format: [])whenSymbol,Stringoptions=options.merge(name: name_or_model_or_options)elsemodel=name_or_model_or_optionsendopts=Options.from_hash_wrapper_options.to_h.slice(:format).merge(options)opts.model=modelopts.klass=selfself._wrapper_options=optsend# Sets the default wrapper key or model which will be used to determine# wrapper key and attribute names. Called automatically when the# module is inherited.definherited(klass)ifklass._wrapper_options.format.any?params=klass._wrapper_options.dupparams.klass=klassklass._wrapper_options=paramsendsuperendendprivate# Performs parameters wrapping upon the request. Called automatically# by the metal call stack.defprocess_action(*)_perform_parameter_wrappingif_wrapper_enabled?superend# Returns the wrapper key which will be used to store wrapped parameters.def_wrapper_key_wrapper_options.nameend# Returns the list of enabled formats.def_wrapper_formats_wrapper_options.formatend# Returns the list of parameters which will be selected for wrapped.def_wrap_parameters(parameters){_wrapper_key=>_extract_parameters(parameters)}enddef_extract_parameters(parameters)ifinclude_only=_wrapper_options.includeparameters.slice(*include_only)elsif_wrapper_options.excludeexclude=_wrapper_options.exclude+EXCLUDE_PARAMETERSparameters.except(*exclude)elseparameters.except(*EXCLUDE_PARAMETERS)endend# Checks if we should perform parameters wrapping.def_wrapper_enabled?returnfalseunlessrequest.has_content_type?ref=request.content_mime_type.ref_wrapper_formats.include?(ref)&&_wrapper_key&&!request.parameters.key?(_wrapper_key)rescueActionDispatch::Http::Parameters::ParseErrorfalseenddef_perform_parameter_wrappingwrapped_hash=_wrap_parametersrequest.request_parameterswrapped_keys=request.request_parameters.keyswrapped_filtered_hash=_wrap_parametersrequest.filtered_parameters.slice(*wrapped_keys)# This will make the wrapped hash accessible from controller and view.request.parameters.merge!wrapped_hashrequest.request_parameters.merge!wrapped_hash# This will display the wrapped hash in the log file.request.filtered_parameters.merge!wrapped_filtered_hashendendend