206

I'm using an IF statement in Ruby on Rails to try and test if request parameters are set. Regardless of whether or not both parameters are set, the first part of the following if block gets triggered. How can I make this part ONLY get triggered if both params[:one] and params[:two] is set?

if (defined? params[:one]) && (defined? params[:two])
 ... do something ...
elsif (defined? params[:one])
 ... do something ...
end
Darren
  • 3,049
  • 5
  • 21
  • 21
  • 10
    @Nakilon: Given that `params` is a Rails controller method (that happens to return a HashWithIndifferentAccess), it is about Rails. – mu is too short Jan 30 '14 at 22:48

15 Answers15

402

You want has_key?:

if(params.has_key?(:one) && params.has_key?(:two))

Just checking if(params[:one]) will get fooled by a "there but nil" and "there but false" value and you're asking about existence. You might need to differentiate:

  • Not there at all.
  • There but nil.
  • There but false.
  • There but an empty string.

as well. Hard to say without more details of your precise situation.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • If a param is `nil`, it's not there. – Jacob Relkin Apr 12 '11 at 01:46
  • 3
    @sawa It's not possible to pass a `nil` parameter. If the parameter exists, it will be an empty string. – Jacob Relkin Apr 12 '11 at 01:51
  • 6
    @Jacob: There's no guarantee that `params` hasn't been pre-processed before we get to where we're checking what's in it. Hence my list of possible special cases to check. Best to say exactly what you mean IMHO. – mu is too short Apr 12 '11 at 01:55
  • @Jacob: Consider a tool that allowed you to specify very specific pre-conditions for your controller methods. Instead of a simple `:id` in a route somewhere, you could say that `id` should be numeric and that it should be the ID number of a thing that the current user has write access to. Then, `params[:id]` would end up being that object or `nil` if the ID was given but it didn't pass the precondition rules. I've built argument preprocessors like this, they wipe out a huge class of bugs for little effort. – mu is too short Apr 12 '11 at 08:43
  • @mu is too short, I appreciate that you pointed out that `has_key?` is sometimes necessary. It's an important pitfall to know about. But when you know the values won't be false or nil (a common case), would you agree it's OK to shorten it, i.e. `if params[:one] && params[:two] ...`? – antinome Apr 16 '14 at 22:40
  • @antinome That's a matter of taste, I find explicit `false` and `nil` values in my `params` all the time (from decoded JSON or standardized input from a `before_filter` for example) so I prefer to be explicit and consistent. OTOH if you know that the values will be untouched strings (and you're certain that that will never change) then you can be lazy about how you handle them. I'd probably call that false laziness though. – mu is too short Apr 16 '14 at 22:49
  • @mu is too short, well said - I'm still thinking it over but you may have convinced me :-) – antinome Apr 16 '14 at 22:55
  • @antinome: These days I've been leaning towards preprocessing `params` as much as possible in filters (sort of Rails4-style strong params but more thorough) so that, for example, `'false'` or `'0'` in something that should be a boolean will be converted to `false` before the controller even sees it, similarly `params[:id]` would often be converted to an object (and access checked) before the the controller sees it. Still a bit ad-hoc so I haven't put out a strict-params-for-grownups gem yet :) – mu is too short Apr 16 '14 at 23:16
  • `has_key?` is now deprecated. Use `Hash.key?` instead – FloatingRock Nov 16 '14 at 07:54
  • @FloatingRock: Do you have a reference for the deprecation? Both methods have been around for a long time and the `key?` docs even use `has_key?` in their example code. – mu is too short Nov 16 '14 at 18:29
  • 1
    @muistooshort my bad, that was [Python](https://docs.python.org/2/library/stdtypes.html#dict.has_key) :( – FloatingRock Nov 17 '14 at 00:59
  • 2
    In Rails 5, the params object is no longer a hash, and I don't see the `key?` method. http://edgeapi.rubyonrails.org/classes/ActionController/Parameters.html – stephen.hanson Nov 08 '16 at 22:32
  • @steve.hanson But if you check [the source](https://github.com/rails/rails/blob/fe1f4b2ad56f010a4e9b93d547d63a15953d9dc2/actionpack/lib/action_controller/metal/strong_parameters.rb#L115) you'll see that `key?` is delegated to the contained `@parameters` instance variable so it still has a `key?` method. Unfortunately reading the source is often a part of dealing with Rails. I don't have a Rails5 setup handy to check though. – mu is too short Nov 08 '16 at 23:19
  • 2
    @muistooshort Oh, nice. I'm still a little hesitant to use methods that aren't part of the documented API, but I'm sure that's safe enough. I've been calling `params.to_h.key?` for now. – stephen.hanson Nov 09 '16 at 19:38
  • @steve.hanson Um, this is Rails, the "documented API" barely scratches the surface of how things work. You kinda' have to go beyond what's documented if you want to get anything useful done. – mu is too short Nov 09 '16 at 20:03
  • How can I check if params has a key set? I want to use in a method like this `def foo(params) return if no params set; do_something; end` – ltdev Nov 21 '17 at 16:06
  • Rubocop (Ruby linter) warns: `C: [Corrected] Style/PreferredHashMethods`: Use `Hash#key?` instead of `Hash#has_key?`. – Samet Gunaydin Jun 21 '19 at 21:56
107

I am a fan of

params[:one].present?

Just because it keeps the params[sym] form so it's easier to read.

Kick Buttowski
  • 6,709
  • 13
  • 37
  • 58
netricate
  • 1,708
  • 2
  • 12
  • 13
  • 1
    Better, 3 chars shorter, simpler. – Miguel Peniche Mar 17 '16 at 00:35
  • this one should be the best valid response! – jacktrade Apr 07 '16 at 19:31
  • 8
    Careful using present? with booleans (undefined method `present' for true:TrueClass). The accepted answer with has_key? works for all types. – StCleezy Dec 14 '16 at 06:26
  • 3
    To be clear this doesn't work in some cases as e.g. false.present? => false So won't work when e.g. passing parameters via json body as can pass booleans. – James Sep 29 '17 at 10:19
  • 1
    if you are using this logic in a view, present will fail as the parameter array is already being invoked and failing if nil. `has_key`instead gives you what you are looking for. – Jerome Jan 12 '18 at 10:44
  • Params should not be booleans. This was a bug in Rails 4.2 which has been corrected in Rails 5 onward. https://github.com/rails/rails/issues/26075#issuecomment-238527844 – James Sep 10 '19 at 21:01
  • I spun my wheels on this for a bit. If a string is specific as nil then this fails, even though the parameter is present. Better to use `params.has_key?()` – Keith Schacht Mar 14 '22 at 20:20
25

use blank? http://api.rubyonrails.org/classes/Object.html#method-i-blank-3F

unless params[:one].blank? && params[:two].blank?

will return true if its empty or nil

also... that will not work if you are testing boolean values.. since

>> false.blank?
=> true

in that case you could use

unless params[:one].to_s.blank? && params[:two].to_s.blank?
Orlando
  • 9,374
  • 3
  • 56
  • 53
  • 4
    Or `present?` which returns the opposite of `blank?`. So you could turn that `unless` into an `if` if you wanted to. – Inkling Jul 12 '14 at 05:38
  • @Inkling that works, but an inline `unless` it's ok. But yeah, I like better `if` with `present?` – Orlando Jul 15 '14 at 13:00
24

You can write it more succinctly like the following:

required = [:one, :two, :three]
if required.all? {|k| params.has_key? k}
  # here you know params has all the keys defined in required array
else
  ...
end
Zack Xu
  • 11,505
  • 9
  • 70
  • 78
  • 1
    I like how clean this is. I am new to Ruby though. Any pitfalls or missed edge cases I should know of? – Sean May 03 '17 at 03:13
10

Simple as pie:

if !params[:one].nil? and !params[:two].nil?
  #do something...
elsif !params[:one].nil?
  #do something else...
elsif !params[:two].nil?
  #do something extraordinary...
end
Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320
6

A very simple way to provide default values to your params: params[:foo] ||= 'default value'

chrpes
  • 645
  • 7
  • 17
5
if params[:one] && params[:two]
 ... do something ...
elsif params[:one]
 ... do something ...
end
fl00r
  • 82,987
  • 33
  • 217
  • 237
4

You can also do the following:

unless params.values_at(:one, :two, :three, :four).includes?(nil)
 ... excute code ..
end 

I tend to use the above solution when I want to check to more then one or two params.

.values_at returns and array with nil in the place of any undefined param key. i.e:

some_hash = {x:3, y:5}
some_hash.values_at(:x, :random, :y}

will return the following:

[3,nil,5] 

.includes?(nil) then checks the array for any nil values. It will return true is the array includes nil.

In some cases you may also want to check that params do not contain and empty string on false value.

You can handle those values by adding the following code above the unless statement.

params.delete_if{|key,value| value.blank?}

all together it would look like this:

 params.delete_if{|key,value| value.blank?}
 unless params.values_at(:one, :two, :three, :four).includes?(nil)
   ... excute code ..
  end

It is important to note that delete_if will modify your hash/params, so use with caution.

The above solution clearly takes a bit more work to set up but is worth it if you are checking more then just one or two params.

Greg L
  • 381
  • 4
  • 5
4

I just read this on RubyInRails classes http://api.rubyonrails.org/classes/Object.html#method-i-blank-3F

you can use blank? method which is equivalent to params[:one].nil? || params[:one].empty?

(e.g)

if params[:one].blank? 
  # do something if not exist
else
  # do something if exist
end
davideghz
  • 3,596
  • 5
  • 26
  • 50
Basil Mariano
  • 2,437
  • 1
  • 20
  • 13
3

In addition to previous answers: has_key? and has_value? have shorter alternatives in form of key? and value?. Ruby team also suggests using shorter alternatives, but for readability some might still prefer longer versions of these methods.

Therefore in your case it would be something like

if params.key?(:one) && params.key?(:two)
  ... do something ...
elsif params.key?(:one)
  ... do something ...
end

NB! .key? will just check if the key exists and ignores the whatever possible value. For ex:

2.3.3 :016 > a = {first: 1, second: nil, third: ''}
  => {:first=>1, :second=>nil, :third=>""}
2.3.3 :017 > puts "#{a.key?(:first)}, #{a.key?(:second)}, #{a.key?(:third), #{a.key?(:fourth)}}"
true, true, true, false
Andres
  • 2,099
  • 3
  • 22
  • 39
2

If you want to be able to return an error based on the specific missing parameter without having to switch through all of them:

required_params = [:one, :two, :three]
required_params.each do |param|
  if params.has_key?(param)
    render json: { errors: "Missing parameter #{param.to_s}." }, :status => :bad_request 
    return
  end
end
ricks
  • 3,154
  • 31
  • 51
  • 1
    The definition of [blank?](https://apidock.com/rails/Object/blank%3F), if used this way, would lead to some serious runtime issues if the needed parameter is an empty array, empty string, empty Hash, boolean FALSE or nil. What you need is [has_key?](https://apidock.com/rails/ActionController/Parameters/has_key%3F) method, as in the accepted answer. This answer is dangerous. – Vlad Dec 15 '22 at 18:11
  • 1
    @Vlad good catch, ill update the answer. – ricks Dec 15 '22 at 21:06
1

Just pieced this together for the same problem:

before_filter :validate_params

private

def validate_params
  return head :bad_request unless params_present?
end

def params_present?  
  Set.new(%w(one two three)) <= (Set.new(params.keys)) &&
  params.values.all?
end

the first line checks if our target keys are present in the params' keys using the <= subset? operator. Enumerable.all? without block per default returns false if any value is nil or false.

1

Here's what I do,

before_action :validate_presence

and then following methods:

    def check_presence
  params[:param1].present? && params[:param2].present?
 end

 def validate_presence
  if !check_presence
    render json:  {
                      error:  {
                                message: "Bad Request, parameters missing.",
                                status: 500
                              }
                    }
  end
 end
Parth Modi
  • 1,675
  • 2
  • 20
  • 26
0

I try a late, but from far sight answer:

If you want to know if values in a (any) hash are set, all above answers a true, depending of their point of view.

If you want to test your (GET/POST..) params, you should use something more special to what you expect to be the value of params[:one], something like

if params[:one]~=/   / and  params[:two]~=/[a-z]xy/

ignoring parameter (GET/POST) as if they where not set, if they dont fit like expected

just a if params[:one] with or without nil/true detection is one step to open your page for hacking, because, it is typically the next step to use something like select ... where params[:one] ..., if this is intended or not, active or within or after a framework.

an answer or just a hint

halfbit
  • 3,773
  • 2
  • 34
  • 47
0
if params[:one] && param[:two]
  ... excute code ..
end

You can also check if the parameters are empty by using params[:two].empty

Jason Yost
  • 4,807
  • 7
  • 42
  • 65
  • 1
    While this covers the case where both parameters are defined. It does not cover the case where one of them evaluates to false. – EmFi Apr 12 '11 at 01:56