1
\$\begingroup\$

I have the following function, and I have a hunch it can be written more concisely in Ruby.

def template_params
  filtered_params = params[:template].permit!
  filtered_params[:published] ||= false
  filtered_params
end

The end result is that filtered_params has published: false if the params hash doesn't have published as a key.

I'm wondering if there is some kind of conditional merge w/ Ruby to turn that code into a 1-liner.

Essentially, the params hash includes "what has been changed" and when :published becomes false (it's a checkbox that gets unchecked), it naturally doesn't get included in the params hash. I wanted to pass in the fact that :published became false when it got unchecked.

I do not need to pass in published: false by default, because whatever the default is, is fine. It is the changed state that I need, but from false to true. The alternative works: if the checkbox is checked, it becomes true and gets passed through without issue.

This is more an exercise of how can I turn this bit of code into something more "elegant" and maybe a 1-liner. As it stands, it does exactly what I need it to do.

\$\endgroup\$
  • \$\begingroup\$ What are you doing with the result? Wouldn't the value by falsy anyway? \$\endgroup\$ – 200_success Jul 19 '16 at 21:37
  • \$\begingroup\$ @200_success I've updated the question to be more concise. False cannot be the default value for the params. It's a checkbox state, so when it becomes unchecked, the params does not include it....thus conditional merge. Does such a thing exist? And if so, is there a more elegant way to do it than what I've provided? \$\endgroup\$ – risa_risa Jul 19 '16 at 22:22
  • 1
    \$\begingroup\$ @rbatta If you use Rails' form helpers for checkboxes, you don't need to do this. The helpers automatically render a hidden input (before the checkbox input), which contains the "unchecked"-value for the checkbox. Since the inputs both have the same name, the checkbox value will be sent if it's checked (because it'll override the hidden input's value). But if the checkbox is unchecked, the hidden input's value gets sent. \$\endgroup\$ – Flambino Jul 20 '16 at 1:29
  • \$\begingroup\$ @Flambino unfortunately that's not the case because my params list does NOT include the unchecked value at all. But that is inconsequential and completely unimportant to the question I'm asking. Like I said in the post, this is a question of how can I turn this code into something more elegant? That's really it. \$\endgroup\$ – risa_risa Jul 20 '16 at 17:02
  • \$\begingroup\$ @rbatta And I'm saying that your list should include the unchecked value if that value is important. Otherwise the controller is just guessing what the client meant. If you want to enforce certain parameters, raise an exception instead \$\endgroup\$ – Flambino Jul 20 '16 at 17:49
3
\$\begingroup\$

You want to merge params with one or more default values. How about, well, #merge?

{"published" => false}.merge(params[:template].permit!)

Only trick, if you can call it a trick, is that the default hash must use string keys, not symbols, despite the params object accepting either. The resulting hash will also be keyed with strings only.

Alternatively, you can use HashWithIndifferentAccess to get around this:

defaults = HashWithIndifferentAccess.new({ published: false })
filtered = defaults.merge(params[:template].permit!)

And filtered will be a HashWithIndifferentAccess much like params produces.

All that said, the better way to handle all this is to send the right parameters in the first place. With the code above you're always assuming that published should be set to false unless told otherwise. But there could be all sort of reasons for why only a subset of parameters are sent; if published is intentionally left out, it'll suddenly be filled in - perhaps wrongly - by the controller.

Relatedly, if the parameter is required, raise an exception if it's missing and tell the client. It's not the controller's job to fill in the blanks when faced with incomplete information. Conversely, it's the client's job to send the parameters that are necessary or required.

As I said in a comment, if you use Rails' regular checkbox helpers, they'll produce a hidden input with the checkbox's "unchecked" value, in addition to the checkbox input itself. If the checkbox is checked, it'll override the former, unchecked value. This is the typical (and better) way to solve this.

So in short, if you want to merge to hashes, use #merge. But in this case, you probably don't want to do that.

\$\endgroup\$
  • \$\begingroup\$ We are using Angular and one of the (unfortunate) tradeoffs is the loss of Rail's built checkbox helpers. I ended up testing with the {"published" => false}.merge(params[:template].permit!) bit of code and it works. Fantastically simple. Thanks! \$\endgroup\$ – risa_risa Jul 21 '16 at 21:16
  • \$\begingroup\$ @rbatta You're welcome. But I do still caution against having the controller "guess" at missing parameters. If this is about updating a Template record, a slightly safer solution is to set a missing published parameter to whatever it already is on the record; that way it won't accidentally get changed by a bad guess. But again, it's better to have the client provide the proper parameters in the first place. It's not hard. E.g. Angular provides an ng-false-value for checkboxes for this purpose. \$\endgroup\$ – Flambino Jul 21 '16 at 21:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.