Structuring Multipart FormData with Rails Naming Conventions

Strong Params

When using strong params in Rails, we permit.require(:controller_name), with :controller_name being the singular name of the controller that we are in.

post '/users', to: 'users#create'
# In UsersController
def create
puts params
end

# Server log, upon receiving the fetch and routing to user#create:
=> {"name"=>"Bryan", "controller"=>"users", "action"=>"create", "user"=>{"name"=>"Bryan""}}
def create
user = User.create(user_params)
render json: user
end
privatedef user_params
params.require(:user).permit(:name)
end

Strong Presumptions

Having always used similarly constructed fetches, I expected similar behavior from multipart FormData.

# In UserController
def create
puts params
puts user_params
end
privatedef user_params
params.require(:user).permit(:file, :name)
end
# Server log:Error occurred while parsing request parameters.Contents:------WebKitFormBoundaryFwYm7IV8PzeE6F6EContent-Disposition: form-data; name="file"; filename="CookieMonster.jpg"Content-Type: image/jpeg????JFIF??Compressed by jpeg-recompress???# Followed by pages of scrambled text...
# Server log:=> <ActionController::Parameters {"file"=>#<ActionDispatch::Http::UploadedFile:0x00007fdca8487c40 @tempfile=#<Tempfile:/var/folders/hk/sgkbgpq57_37rq3l1gfjsb940000gn/T/RackMultipart20200909-42995-13a9put.jpg>, @original_filename="CookieMonster.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"file\"; filename=\"CookieMonster.jpg\"\r\nContent-Type: image/jpeg\r\n">, "name"=>"Bryan", "controller"=>"users", "action"=>"create"} permitted: false>ActionController::ParameterMissing: param is missing or the value is empty: user
# Server log:#params
=> <ActionController::Parameters {"user"=>{"file"=>#<ActionDispatch::Http::UploadedFile:0x00007fe628706e60 @tempfile=#<Tempfile:/var/folders/hk/sgkbgpq57_37rq3l1gfjsb940000gn/T/RackMultipart20200909-43265-1yopi5p.jpg>, @original_filename="CookieMonster.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"user[file]\"; filename=\"CookieMonster.jpg\"\r\nContent-Type: image/jpeg\r\n">, "name"=>"Bryan"}, "controller"=>"users", "action"=>"create"} permitted: false>
#user_params
<ActionController::Parameters {"file"=>#<ActionDispatch::Http::UploadedFile:0x00007fe628706e60 @tempfile=#<Tempfile:/var/folders/hk/sgkbgpq57_37rq3l1gfjsb940000gn/T/RackMultipart20200909-43265-1yopi5p.jpg>, @original_filename="CookieMonster.jpg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"user[file]\"; filename=\"CookieMonster.jpg\"\r\nContent-Type: image/jpeg\r\n">, "name"=>"Bryan"} permitted: true>

Arrays

FormData is able to store arrays of values. To do so, we iteratively attach an item with the same name.

# In UserController
def create
puts params
puts user_params
end
privatedef user_params
params.require(:user).permit(tag_ids: [])
end
# Server log:#params
=> <ActionController::Parameters {"user"=>{"tag_ids"=>"5"}, "controller"=>"users", "action"=>"create"} permitted: false>
#user_params
<ActionController::Parameters {} permitted: true>
# Server log:#params
=> <ActionController::Parameters {"user"=>{"tag_ids"=>["2", "3", "5"]}, "controller"=>"user", "action"=>"create"} permitted: false>
#user_params
<ActionController::Parameters {"tag_ids"=>["2", "3", "5"]} permitted: true>

Empty Arrays with Strong Params

Mass assignment with an empty array is a useful way to delete associations. For instance, to remove all associated tags from a user:

User.first.update(tag_ids: [])
# Server log:#params
=> <ActionController::Parameters {"user"=>{"tag_ids"=>["null"]}, "controller"=>"users", "action"=>"create"} permitted: false>
#user_params
<ActionController::Parameters {"tag_ids"=>["null"]} permitted: true>
User.first.update(tags: ["null"])User Load (0.3ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]](0.1ms)  BEGINTag Load (0.2ms)  SELECT "tags".* FROM "tags" WHERE "tags"."id" = $1  [["id", 0]](0.1ms)  ROLLBACKTraceback (most recent call last):1: from (irb):8ActiveRecord::RecordNotFound (Couldn't find Tag with 'id'=[0])
# Server Log#params
=> <ActionController::Parameters {"user"=>{"tags"=>[""]}, "controller"=>"users", "action"=>"create"} permitted: false>
#user_params
<ActionController::Parameters {"tags"=>[""]} permitted: true>
User.first.update(tag_ids: [""])User Load (0.3ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]](0.1ms)  BEGINTag Load (0.2ms)  SELECT "tags".* FROM "tags" WHERE 1=0Tag Load (0.3ms)  SELECT "tags".* FROM "tags" INNER JOIN "user_tags" ON "tags"."id" = "user_tags"."tag_id" WHERE "user_tags"."user_id" = $1  [["user_id", 1]]UserTag Destroy (0.3ms)  DELETE FROM "user_tags" WHERE "user_tags"."user_id" = $1 AND "user_tags"."tag_id" IN ($2, $3, $4, $5, $6, $7, $8, $9)  [["user_id", 1], ["tag_id", 1], ["tag_id", 2], ["tag_id", 8], ["tag_id", 11], ["tag_id", 12], ["tag_id", 14], ["tag_id", 27], ["tag_id", 31]](1.1ms)  COMMIT=> true

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Bryan Haney

Bryan Haney

Full stack web developer with a passion for number theory and algorithms.