48

I'm receiving a JSON package like:

{
  "point_code" : { "guid" : "f6a0805a-3404-403c-8af3-bfddf9d334f2" }
}

I would like to tell Rails that both point_code and guid are required, not just permitted.

This code seems to work but I don't think it's good practice since it returns a string, not the full object:

params.require(:point_code).require(:guid)

Any ideas how I could do this?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Rob
  • 7,028
  • 15
  • 63
  • 95

7 Answers7

34

I had a similar need and what I did was

def point_code_params
  params.require(:point_code).require(:guid) # for check require params
  params.require(:point_code).permit(:guid) # for using where hash needed
end

Example:

def create
  @point_code = PointCode.new(point_code_params)
end
purezen
  • 612
  • 12
  • 29
sarunw
  • 8,036
  • 11
  • 48
  • 84
  • 1
    This is the correct answer! Worked for me (though I did have to separate the `require`s on the first line into two `params.require()` calls – FloatingRock Jul 24 '14 at 18:08
  • 9
    Doesn't work as it leads to the error: private method `require' called for "xxxxxxxx":String – zuba Jun 02 '15 at 13:05
  • 2
    @FloatingRock: In my case request body is like { "id": 123, "name": "foo", "bar": "foobar" } how to require all params ? – Praveen_Shukla Feb 02 '16 at 05:45
  • 14
    The first line of your method doesn't do anything. It doesn't mutate state or anything, the return value is what matters, and it just vanishes since you don't assign it to anything. You should just need the second line `params.require(:point_code).permit(:guid)` – Vox Oct 06 '16 at 17:58
  • 7
    The api for strong params is aweful – GN. Nov 16 '18 at 22:54
  • This is a clever solution - and indeed it works. I still coming to terms with the strong params API but @Vox it is correct - the require().require() forces the params to be checked and then the permit() actually returns values that can be used to update the model. So it's like a meta validation for presence, followed by useful data – mr_than Apr 10 '20 at 13:51
  • @Vox the first line raises an `ActionController::ParameterMissing` exception if the parameter `guid` is missing or blank. So yes, it does serve its purpose. – MetalElf0 Jul 22 '22 at 10:35
  • This seems like a really strange solution. Would someone explain what is happening? – LondonAppDev Oct 18 '22 at 13:30
30

As of 2015 (RoR 5.0+) you can pass in an array of keys to the require method in rails:

params.require([:point_code, :guid])

http://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-require

raveren
  • 17,799
  • 12
  • 70
  • 83
  • 10
    Unmentioned in your answer is that the result is an array. I'm warning anyone else who just took your response and ran with it (without clicking the link, mind). – Derrell Durrett Feb 12 '19 at 20:06
  • 1
    How is this supposed to work? Since .require returns an array of keys and not a hash... .permit can't work with it, nor .new. ? – Dogweather Mar 25 '19 at 11:09
  • 1
    This does not work for nested params, as the question was asked. This is to require multiple keys at the same level. – Dudo Apr 17 '20 at 22:06
12

The solution proposed in Rails documentation (https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-require) is:

def point_code
  params.require(:point_code).permit(:guid).tap do |point_code_params|
    point_code_params.require(:guid) # SAFER
  end
end
Pere Joan Martorell
  • 2,608
  • 30
  • 29
8

OK, ain't pretty but should do the trick. Assume you have params :foo, :bar and :baf you'd like to require all for a Thing. You could say

def thing_params
  [:foo, :bar, :baf].each_with_object(params) do |key, obj|
    obj.require(key)
  end
end

each_with_object returns obj, which is initialized to be params. Using the same params obj you require each of those keys in turn, and return finally the object. Not pretty, but works for me.

EdvardM
  • 2,934
  • 1
  • 21
  • 20
  • 4
    This code is equivalent to `def thing_params; params.require(:baf); end`. See Vox's [comment above](https://stackoverflow.com/questions/22487878/strong-parameters-require-multiple#comment67090809_24411532). – James Dec 01 '17 at 00:20
  • This answer squeezes the most juice for when you want a top layer of keys required and a hash like object returned, not an array of values. – RudyOnRails May 12 '21 at 02:08
1

require takes one parameter. So there is no way you can pass in multiple keys unless you override the require method. You can achieve what you want with some additional logic in your action:

def action
  raise ActionController::ParameterMissing.new("param not found: point_code") if point_params[:point_code].blank?
  raise ActionController::ParameterMissing.new("param not found: guid") if point_params[:point_code][:guid].blank?

  <do your stuff>
end

def point_params
  params.permit(point_code: :guid)
end
a14m
  • 7,808
  • 8
  • 50
  • 67
usha
  • 28,973
  • 5
  • 72
  • 93
1

This question came up in my google search for a different case ie, when using a "multiple: true" as in:

<%= form.file_field :asset, multiple: true %>

Which is a totally different case than the question. However, in the interest of helping out here is a working example in Rails 5+ of that:

form_params = params.require(:my_profile).permit({:my_photos => []})
Nate Flink
  • 3,934
  • 2
  • 30
  • 18
1

Although a lot of these answers show you how to get what you're asking for, I would like to add to these answers and suggest that you use a before_action. If the required params are missing, require will automatically return a 400 error

class SomeController < ApplicationController
  before_action :ensure_valid_params, only: :index

  def index
    # do your stuff
  end

private

  def ensure_valid_params
    params.require(:some_required_param)
  end
end
Ethan Dowler
  • 127
  • 2
  • 4