Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Raising an error when nil is passed to update_attributes. #9860

Merged
merged 1 commit into from

10 participants

@wangjohn

In 9e4b715, @josevalim allowed the ability to send a nil argument to update_attributes (in response to #478). However, the reasoning behind that was because a pretty cryptic error was thrown. The current behavior in Rails is that thing.update_attributes(nil) will return and not update any attributes without throwing any error.

However, Instead of allowing you to send a nil argument, it might be better to raise an ArgumentError which has a clear error message. I've made that change here, but this PR is more intended as a discussion on which behavior is better.

Closes #9858.

activerecord/lib/active_record/attribute_assignment.rb
@@ -12,7 +12,11 @@ module AttributeAssignment
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
# exception is raised.
def assign_attributes(new_attributes)
- return if new_attributes.blank?
+ if !new_attributes
+ raise ArgumentError, "Must pass a non-nil hash as an argument when assigning attributes."
+ elsif new_attributes.empty?

Is this really necessary ? The behaviour would be the same without the elsif clause.

@jeremy Owner
jeremy added a note

It wouldn't: see the early return

He's saying the behavior would be the same because returning vs. not returning has the same result when new_attributes is empty.

Exactly @mhuggins, the attributes won't be changed wether or not you return early.

Just a personnal point of view, but something like

raise ArgumentError, "Must pass a non-nil hash as an argument when assigning attributes." if new_attributes.nil?

is better looking to me than the if / elsif branch

@Intrepidd @mhuggins It actually does change the behavior. This is because if an empty string is passed asnew_attributes then the call to stringify_keys will raise a NoMethodError.

See the following test: https://github.com/rails/rails/blob/master/activerecord/test/cases/attribute_methods_test.rb#L85-88

Why would someone pass an empty string to this method? The documentation makes it clear that a hash is expected, and I can't think of a scenario where someone would even consider passing a string. The nil check I can understand if a controller param isn't passed by the user.

That's true. I think this test is outdated. It was originally added here: 786342e. Which brings me to a second discussion, should we be raising an ArgumentError when the input is not a hash?

It seems like this second check will ensure that all arguments are correct and valid, and that stringify_keys will never throw a NoMethodError if we are sure that a new_attributes is a hash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@Intrepidd

What if the users wants to pass a custom object that responds to stringify_keys ?

I'm totally for the error on nil because it is common and this allows developers to know what the problem is real quick.

However, I'm not fond of the racism concerning the type of the parameter ;)

As I said in the outdated diff, everywhere in rails, hashes are expected without being type checked, that's the beauty of ruby, if you want strong type check, ruby is not the good solution I think :)

What do you think of :

  • Only check for nil
  • Check for something that resond_to?(:stringify_keys)

Please let me know what you think of this, I'm not an expert so feel free to point out things that I may have missed.

@wangjohn

@Intrepidd That's a pretty reasonable idea. I think checking for respond_to?(:stringify_keys) is a happy medium between checking explicitly for hashes and not checking the input arguments at all.

The PR has been updated.

@Intrepidd
activerecord/lib/active_record/attribute_assignment.rb
@@ -12,7 +12,9 @@ module AttributeAssignment
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
# exception is raised.
def assign_attributes(new_attributes)
- return if new_attributes.blank?
+ unless new_attributes && new_attributes.respond_to?(:stringify_keys)
@senny Owner
senny added a note

I always get confused when I have to read an unless with more than one condition. I'd like to write them using if.

That might only be my taste though.

/cc @carlosantoniodasilva

I think that only checking for respond_to would be enough? We don't expect nils to respond to stringify_keys I guess?

Ya, I just noticed that after I implemented @senny's change to an if statement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@senny
Owner

this looks good to me.

/cc @rafaelfranca

@senny
Owner

As this changes the functionality I think we should add a CHANGELOG entry.

activerecord/lib/active_record/attribute_assignment.rb
@@ -12,7 +12,9 @@ module AttributeAssignment
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
# exception is raised.
def assign_attributes(new_attributes)
- return if new_attributes.blank?
+ unless new_attributes && new_attributes.respond_to?(:stringify_keys)
+ raise ArgumentError, "Must pass a non-nil hash as an argument when assigning attributes."

What's a non-nil hash? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@wangjohn

@senny Added a changelog entry and rebased. @carlosantoniodasilva thanks for the comments!

@senny
Owner

@wangjohn this does no longer apply cleanly, can you push a rebased version?

@wangjohn

@senny I've just rebased and pushed.

@senny
Owner

@rafaelfranca @carlosantoniodasilva can you take another look?

@wangjohn

Any updates on this PR? If not, I'll go ahead and close it.

@senny
Owner

let's wait for more feedback. I like this change but I'm not sure if there are implications because it changes the public API.

@neerajdotname
Collaborator

I feel conflicted about this one. If user is passing nil then obviously user does not want anything to happen. After this code now user has to do sent_arguments || {} so that code does not raise exception. I think it is better if the code does not raise exception on nil.

@senny
Owner

@neerajdotname but for the default case with strong parameters you already get {}, don't you?

@neerajdotname
Collaborator

@senny you might but ActiveRecord can be used without rest of rails goodies.

Updated: replaced with by without.

@senny
Owner

@neerajdotname sure, I guess it's a matter of taste. It's important that the default case does not get overcomplicated. That's why I mentioned strong params.

Maybe we can get a word from @rafaelfranca and @jonleighton to wrap this PR up.

activerecord/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Calling ``update_attributes`` will now throw an ArgumentError whenever it
+ gets a nil argument. More specifically, it will throw an error if the
+ argument that it gets passed does not respond to to ``stringify_keys``.

I think you can use single backticks in the description here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activerecord/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Calling ``update_attributes`` will now throw an ArgumentError whenever it

ArgumentError (with backticks for better syntax)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activerecord/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Calling ``update_attributes`` will now throw an ArgumentError whenever it
+ gets a nil argument. More specifically, it will throw an error if the

in nil as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activerecord/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Calling ``update_attributes`` will now throw an ArgumentError whenever it
+ gets a nil argument. More specifically, it will throw an error if the
+ argument that it gets passed does not respond to to ``stringify_keys``.
+
+ For example:

Example:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activerecord/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Calling ``update_attributes`` will now throw an ArgumentError whenever it
+ gets a nil argument. More specifically, it will throw an error if the
+ argument that it gets passed does not respond to to ``stringify_keys``.
+
+ For example:
+
+ @my_comment.update_attributes() # => raises ArgumentError

Two more spaces to properly syntax highlight

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@carlosantoniodasilva

Please rebase from master. Thanks.

@wangjohn

@carlosantoniodasilva Rebased and changed the changelog.

@Bertg

+1 It will make debugging a lot easier for the developer as typos will be caught easily. In previous tickets the core team has stated that this is the typo of thing Rails should cater to.

The old behavior can be easily returnned by doing arguments || {} which makes it clear that the developer expects nil as an input.

@rafaelfranca

I'm fine with this change on master but I'd not put in a stable release.

I'm merging.

@rafaelfranca rafaelfranca merged commit 926c4b9 into rails:master
@rubys

Causes failures in Agile Web Development with Rails test. Minimal test case: http://intertwingly.net/tmp/update_attributes.html

@wangjohn wangjohn deleted the wangjohn:update_attributes_throws_error_with_nil branch
@wangjohn

@rubys The problem with the failures is that I removed the line return if new_attributes.blank?. It seems like this is actually required (in the case of the tests, we're getting an empty hash, so the assign_attributes method should just return without doing anything).

The simplest fix would be to just add this line back in. I'll make a PR to do this.

@benja83 benja83 referenced this pull request from a commit in benja83/toyotakataboard-relational
@benja83 benja83