# frozen_string_literal: true# :markup: markdownrequire"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 `attribute_names`.## If you're going to pass the parameters to an `ActiveModel` object (such as# `User.new(params[:user])`), 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)classOptions<Struct.new(:name,:format,:include,:exclude,:klass,:model)# :nodoc:defself.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@mutex=Mutex.new@include_set=include@name_set=nameenddefmodelsuper||self.model=_default_wrap_modelenddefincludereturnsuperif@include_setm=model@mutex.synchronizedoreturnsuperif@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=model@mutex.synchronizedoreturnsuperif@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# * `:format` - The list of formats in which the parameters wrapper will be# enabled.# * `:include` - The list of attribute names which parameters wrapper will# wrap into a nested hash.# * `:exclude` - 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