Changed ActiveRecord::Relation#update behavior so that it will work on Relation objects without giving id #11898

Merged
merged 1 commit into from Jan 2, 2015

Conversation

Projects
None yet
9 participants
@prathamesh-sonpatki

No description provided.

@egilburg

View changes

activerecord/lib/active_record/relation.rb
def update(id, attributes)
if id.is_a?(Array)
- id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
+ if attributes.is_a?(Array)

This comment has been minimized.

@egilburg

egilburg Aug 15, 2013

Contributor

This is probably simpler:

        id.map.with_index do |one_id, idx| 
          attrs = attributes.is_a?(Array) ? attributes[idx] : attributes

          update(one_id, attrs)
        end
@egilburg

egilburg Aug 15, 2013

Contributor

This is probably simpler:

        id.map.with_index do |one_id, idx| 
          attrs = attributes.is_a?(Array) ? attributes[idx] : attributes

          update(one_id, attrs)
        end

This comment has been minimized.

@prathamesh-sonpatki

prathamesh-sonpatki Aug 15, 2013

Member

@egilburg Thanks. Updated :)

@prathamesh-sonpatki

prathamesh-sonpatki Aug 15, 2013

Member

@egilburg Thanks. Updated :)

@senny

This comment has been minimized.

Show comment
Hide comment
@senny

senny Aug 15, 2013

Member

This implementation issues a single query per record to update. Since all the records are updated with the same condition we can perform the update in a single query. Is there really a need for this PR, when you could use:

Person.where(id: [1,2,3,4]).update_all(last_seen: Time.now)
Member

senny commented Aug 15, 2013

This implementation issues a single query per record to update. Since all the records are updated with the same condition we can perform the update in a single query. Is there really a need for this PR, when you could use:

Person.where(id: [1,2,3,4]).update_all(last_seen: Time.now)
@egilburg

This comment has been minimized.

Show comment
Hide comment
@egilburg

egilburg Aug 15, 2013

Contributor

Isn't update_all being deprecated?

Contributor

egilburg commented Aug 15, 2013

Isn't update_all being deprecated?

@senny

This comment has been minimized.

Show comment
Hide comment
@senny

senny Aug 15, 2013

Member

Not that I know of => https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation.rb#L304

But you need to call it on a Relation, I think it was available at the class level at some point Person.update_all but that is no longer supported.

Member

senny commented Aug 15, 2013

Not that I know of => https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation.rb#L304

But you need to call it on a Relation, I think it was available at the class level at some point Person.update_all but that is no longer supported.

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

prathamesh-sonpatki Aug 15, 2013

Member

@senny update_all doesn't run callbacks and validations.

@senny update_all doesn't run callbacks and validations.

@senny

This comment has been minimized.

Show comment
Hide comment
@senny

senny Aug 15, 2013

Member

@prathamesh-sonpatki yea right. I just browsed the API and it seems there is no method that acts on the Relation to update and also runs callbacks.

@rafaelfranca @carlosantoniodasilva is there a reason we only have Person.update(ids, values) to update with callbacks and nothing that acts on the Relation?

Member

senny commented Aug 15, 2013

@prathamesh-sonpatki yea right. I just browsed the API and it seems there is no method that acts on the Relation to update and also runs callbacks.

@rafaelfranca @carlosantoniodasilva is there a reason we only have Person.update(ids, values) to update with callbacks and nothing that acts on the Relation?

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

prathamesh-sonpatki Aug 15, 2013

Member

Investigated it a bit. Right now if you try to call update on a relation, ActiveRecord::Relation#update gets called. If we pass correct ids and values it will update the records with callbacks. But when we are dealing with relation, we should not pass ids at all. Just pass the hash of values.

I think we have 3 usecases

  1. Update different records with different values - Existing behavior of update
  2. Update different records with same values using ids - Implemented in this PR
  3. Update different records with same values using relation - Missing

Investigated it a bit. Right now if you try to call update on a relation, ActiveRecord::Relation#update gets called. If we pass correct ids and values it will update the records with callbacks. But when we are dealing with relation, we should not pass ids at all. Just pass the hash of values.

I think we have 3 usecases

  1. Update different records with different values - Existing behavior of update
  2. Update different records with same values using ids - Implemented in this PR
  3. Update different records with same values using relation - Missing
@senny

This comment has been minimized.

Show comment
Hide comment
@senny

senny Aug 15, 2013

Member

@prathamesh-sonpatki I'm not sure we need 2) if we have 3). Let's wait for some feedback.

Member

senny commented Aug 15, 2013

@prathamesh-sonpatki I'm not sure we need 2) if we have 3). Let's wait for some feedback.

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment

@carlosantoniodasilva Any thoughts?

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

prathamesh-sonpatki Aug 29, 2013

Member

any updates on this?

any updates on this?

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

prathamesh-sonpatki Dec 1, 2013

Member

I have rebased with current master

Member

prathamesh-sonpatki commented Dec 1, 2013

I have rebased with current master

@rafaelfranca

This comment has been minimized.

Show comment
Hide comment
@rafaelfranca

rafaelfranca Dec 1, 2013

Member

I prefer to have 3) and not implement 2).

@prathamesh-sonpatki could you take a look if it is possible to implement 3)?

Member

rafaelfranca commented Dec 1, 2013

I prefer to have 3) and not implement 2).

@prathamesh-sonpatki could you take a look if it is possible to implement 3)?

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

prathamesh-sonpatki Dec 1, 2013

Member

@rafaelfranca Its possible. i will update soon

Member

prathamesh-sonpatki commented Dec 1, 2013

@rafaelfranca Its possible. i will update soon

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

prathamesh-sonpatki Dec 5, 2013

Member

@rafaelfranca Sorry for the delay. I have updated. Please take a look

Member

prathamesh-sonpatki commented Dec 5, 2013

@rafaelfranca Sorry for the delay. I have updated. Please take a look

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

prathamesh-sonpatki Dec 31, 2013

Member

@rafaelfranca Can you see this issue?

@rafaelfranca Can you see this issue?

@rafaelfranca

This comment has been minimized.

Show comment
Hide comment
@rafaelfranca

rafaelfranca Jan 1, 2014

Member

Sure, but I don't want to include this on 4.1, so we will have to wait the final release to merge it

Member

rafaelfranca commented Jan 1, 2014

Sure, but I don't want to include this on 4.1, so we will have to wait the final release to merge it

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

prathamesh-sonpatki Jun 4, 2014

Member

@rafaelfranca any updates on this issue?

Member

prathamesh-sonpatki commented Jun 4, 2014

@rafaelfranca any updates on this issue?

+ # # Updates multiple records from the result of a relation
+ # people = Person.where(group: 'expert')
+ # people.update(group: 'masters')
+ def update(id = nil, attributes)

This comment has been minimized.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

I don't think this will work. If you pass only one argument it will be always id.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

I don't think this will work. If you pass only one argument it will be always id.

This comment has been minimized.

@matthewd

matthewd Jun 4, 2014

Member

I wasn't sure about 1.9, but just checked: it's fine.

@matthewd

matthewd Jun 4, 2014

Member

I wasn't sure about 1.9, but just checked: it's fine.

This comment has been minimized.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

You are right. Same with 1.9.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

You are right. Same with 1.9.

This comment has been minimized.

@matthewd

matthewd Jun 4, 2014

Member

.. that said, I think I'd rather that .update(nil, group: 'masters') continued to fail, instead of suddenly meaning "update all the things".

@matthewd

matthewd Jun 4, 2014

Member

.. that said, I think I'd rather that .update(nil, group: 'masters') continued to fail, instead of suddenly meaning "update all the things".

This comment has been minimized.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

Yes. This make sense.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

Yes. This make sense.

This comment has been minimized.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

Yes, it is the best name. But .update(nil, group: 'masters') updating all the things can be a huge problem in existing applications upgrading to 4.2. Maybe we should check if id is a hash and trigger this new behaviour.

Also I was thinking. Depending on the number of record your relation return you may have a huge performance issue since it will do a update query for each record.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

Yes, it is the best name. But .update(nil, group: 'masters') updating all the things can be a huge problem in existing applications upgrading to 4.2. Maybe we should check if id is a hash and trigger this new behaviour.

Also I was thinking. Depending on the number of record your relation return you may have a huge performance issue since it will do a update query for each record.

This comment has been minimized.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

Ah, yes, maybe defaulting the id value to :all would fix this concern.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

Ah, yes, maybe defaulting the id value to :all would fix this concern.

This comment has been minimized.

@prathamesh-sonpatki

prathamesh-sonpatki Jun 4, 2014

Member

@rafaelfranca For performance issues, should we do find_each instead of to_a ?

@prathamesh-sonpatki

prathamesh-sonpatki Jun 4, 2014

Member

@rafaelfranca For performance issues, should we do find_each instead of to_a ?

This comment has been minimized.

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

We would have an improvement to not load all the record on the memory but the number of queries would be even worse. I think we should only document this possible performance problem and point users to update_all

@rafaelfranca

rafaelfranca Jun 4, 2014

Member

We would have an improvement to not load all the record on the memory but the number of queries would be even worse. I think we should only document this possible performance problem and point users to update_all

This comment has been minimized.

@prathamesh-sonpatki

prathamesh-sonpatki Jun 5, 2014

Member

@rafaelfranca I updated the PR. Should we also update ActiveRecord getting started guide where we mention update and update_all

@prathamesh-sonpatki

prathamesh-sonpatki Jun 5, 2014

Member

@rafaelfranca I updated the PR. Should we also update ActiveRecord getting started guide where we mention update and update_all

@rafaelfranca

This comment has been minimized.

Show comment
Hide comment
@rafaelfranca

rafaelfranca Jun 4, 2014

Member

I really not sure about this. @dhh @jeremy @tenderlove WDYT?

Member

rafaelfranca commented Jun 4, 2014

I really not sure about this. @dhh @jeremy @tenderlove WDYT?

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Jun 4, 2014

Member

I’m personally a big fan of this. I really don’t like the #update_all thing. And this strikes me as a far more welcome syntax. So 👍 from my end.

On Jun 4, 2014, at 4:19 PM, Rafael Mendonça França notifications@github.com wrote:

I really not sure about this. @dhh @jeremy @tenderlove WDYT?


Reply to this email directly or view it on GitHub.

Member

dhh commented Jun 4, 2014

I’m personally a big fan of this. I really don’t like the #update_all thing. And this strikes me as a far more welcome syntax. So 👍 from my end.

On Jun 4, 2014, at 4:19 PM, Rafael Mendonça França notifications@github.com wrote:

I really not sure about this. @dhh @jeremy @tenderlove WDYT?


Reply to this email directly or view it on GitHub.

@tenderlove

This comment has been minimized.

Show comment
Hide comment
@tenderlove

tenderlove Jun 4, 2014

Member

+1

Aaron Patterson
http://tenderlovemaking.com/
I'm on an iPhone so I apologize for top posting.

On Jun 4, 2014, at 7:18 AM, Rafael Mendonça França notifications@github.com wrote:

I really not sure about this. @dhh @jeremy @tenderlove WDYT?


Reply to this email directly or view it on GitHub.

Member

tenderlove commented Jun 4, 2014

+1

Aaron Patterson
http://tenderlovemaking.com/
I'm on an iPhone so I apologize for top posting.

On Jun 4, 2014, at 7:18 AM, Rafael Mendonça França notifications@github.com wrote:

I really not sure about this. @dhh @jeremy @tenderlove WDYT?


Reply to this email directly or view it on GitHub.

@al2o3cr

This comment has been minimized.

Show comment
Hide comment
@al2o3cr

al2o3cr Jun 4, 2014

Contributor

This seems (IMO) confusingly close to update_all, but with radically different implications for the number of queries (N+1 here, 1 for update_all). Is this something people do often enough that an explicit each loop is a readability problem?

Contributor

al2o3cr commented Jun 4, 2014

This seems (IMO) confusingly close to update_all, but with radically different implications for the number of queries (N+1 here, 1 for update_all). Is this something people do often enough that an explicit each loop is a readability problem?

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Jun 4, 2014

Member

If you want callbacks to fire, then you have to do N+1. I think this is a legit use case, it’s neatly readable, and we document clearly what’s going on.

On Jun 4, 2014, at 8:49 PM, Matt Jones notifications@github.com wrote:

This seems (IMO) confusingly close to update_all, but with radically different implications for the number of queries (N+1 here, 1 for update_all). Is this something people do often enough that an explicit each loop is a readability problem?


Reply to this email directly or view it on GitHub.

Member

dhh commented Jun 4, 2014

If you want callbacks to fire, then you have to do N+1. I think this is a legit use case, it’s neatly readable, and we document clearly what’s going on.

On Jun 4, 2014, at 8:49 PM, Matt Jones notifications@github.com wrote:

This seems (IMO) confusingly close to update_all, but with radically different implications for the number of queries (N+1 here, 1 for update_all). Is this something people do often enough that an explicit each loop is a readability problem?


Reply to this email directly or view it on GitHub.

@egilburg

This comment has been minimized.

Show comment
Hide comment
@egilburg

egilburg Jun 4, 2014

Contributor

If update_all fires no callbacks, perhaps it should be deprecated in favor of update_columns.

update_all is ambiguous and actually a bit scary since the name may imply "all as in all records in db, as opposed to current relation scope"

Also, should similar change be made for Relation#delete?

Eugene

On Jun 4, 2014, at 1:53 PM, David Heinemeier Hansson notifications@github.com wrote:

If you want callbacks to fire, then you have to do N+1. I think this is a legit use case, it’s neatly readable, and we document clearly what’s going on.

On Jun 4, 2014, at 8:49 PM, Matt Jones notifications@github.com wrote:

This seems (IMO) confusingly close to update_all, but with radically different implications for the number of queries (N+1 here, 1 for update_all). Is this something people do often enough that an explicit each loop is a readability problem?


Reply to this email directly or view it on GitHub.


Reply to this email directly or view it on GitHub.

Contributor

egilburg commented Jun 4, 2014

If update_all fires no callbacks, perhaps it should be deprecated in favor of update_columns.

update_all is ambiguous and actually a bit scary since the name may imply "all as in all records in db, as opposed to current relation scope"

Also, should similar change be made for Relation#delete?

Eugene

On Jun 4, 2014, at 1:53 PM, David Heinemeier Hansson notifications@github.com wrote:

If you want callbacks to fire, then you have to do N+1. I think this is a legit use case, it’s neatly readable, and we document clearly what’s going on.

On Jun 4, 2014, at 8:49 PM, Matt Jones notifications@github.com wrote:

This seems (IMO) confusingly close to update_all, but with radically different implications for the number of queries (N+1 here, 1 for update_all). Is this something people do often enough that an explicit each loop is a readability problem?


Reply to this email directly or view it on GitHub.


Reply to this email directly or view it on GitHub.

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Jun 4, 2014

Member

Agree. We should make that clearer. And uniform across the API. So #delete should follow #update in the same way.

On Jun 4, 2014, at 9:07 PM, Eugene Gilburg notifications@github.com wrote:

If update_all fires no callbacks, perhaps it should be deprecated in favor of update_columns.

update_all is ambiguous and actually a bit scary since the name may imply "all as in all records in db, as opposed to current relation scope"

Also, should similar change be made for Relation#delete?

Eugene

On Jun 4, 2014, at 1:53 PM, David Heinemeier Hansson notifications@github.com wrote:

If you want callbacks to fire, then you have to do N+1. I think this is a legit use case, it’s neatly readable, and we document clearly what’s going on.

On Jun 4, 2014, at 8:49 PM, Matt Jones notifications@github.com wrote:

This seems (IMO) confusingly close to update_all, but with radically different implications for the number of queries (N+1 here, 1 for update_all). Is this something people do often enough that an explicit each loop is a readability problem?


Reply to this email directly or view it on GitHub.


Reply to this email directly or view it on GitHub.

Reply to this email directly or view it on GitHub.

Member

dhh commented Jun 4, 2014

Agree. We should make that clearer. And uniform across the API. So #delete should follow #update in the same way.

On Jun 4, 2014, at 9:07 PM, Eugene Gilburg notifications@github.com wrote:

If update_all fires no callbacks, perhaps it should be deprecated in favor of update_columns.

update_all is ambiguous and actually a bit scary since the name may imply "all as in all records in db, as opposed to current relation scope"

Also, should similar change be made for Relation#delete?

Eugene

On Jun 4, 2014, at 1:53 PM, David Heinemeier Hansson notifications@github.com wrote:

If you want callbacks to fire, then you have to do N+1. I think this is a legit use case, it’s neatly readable, and we document clearly what’s going on.

On Jun 4, 2014, at 8:49 PM, Matt Jones notifications@github.com wrote:

This seems (IMO) confusingly close to update_all, but with radically different implications for the number of queries (N+1 here, 1 for update_all). Is this something people do often enough that an explicit each loop is a readability problem?


Reply to this email directly or view it on GitHub.


Reply to this email directly or view it on GitHub.

Reply to this email directly or view it on GitHub.

@matthewd

This comment has been minimized.

Show comment
Hide comment
@matthewd

matthewd Jun 4, 2014

Member

So, I think this can go in, to round out:

delete_all : delete :: destroy_all : destroy :: update_all : update

And then we separately consider whether the _all methods might be renamed to something more explicitly different/dangerous. Right?

Member

matthewd commented Jun 4, 2014

So, I think this can go in, to round out:

delete_all : delete :: destroy_all : destroy :: update_all : update

And then we separately consider whether the _all methods might be renamed to something more explicitly different/dangerous. Right?

@egilburg

This comment has been minimized.

Show comment
Hide comment
@egilburg

egilburg Jun 4, 2014

Contributor

IMO In an ideal world the strategy would be determined by common arguments and not separate methods

.create .save, .update, .destroy, including the whether on record, assoc, or relation, would all take following arguments:

validate: true/false
callbacks: true/false

Both would default to true if not set.

Bang methods like save! should also honor the arguments and only raise if save failed WITH given arguments, rather than always assume save! requires validation.

This way a whole bunch of methods can be deprecated. The choice of n+1 or single query should be implementation detail which Rails can do in most efficient way possible under given circumstances. Mist users just want things to work and not have to research which method out of a myriad of similar looking ones is the right one.

Eugene

On Jun 4, 2014, at 2:22 PM, Matthew Draper notifications@github.com wrote:

So, I think this can go in, to round out:

delete_all : delete :: destroy_all : destroy :: update_all : update

And then we separately consider whether the _all methods might be renamed to something more explicitly different/dangerous. Right?


Reply to this email directly or view it on GitHub.

Contributor

egilburg commented Jun 4, 2014

IMO In an ideal world the strategy would be determined by common arguments and not separate methods

.create .save, .update, .destroy, including the whether on record, assoc, or relation, would all take following arguments:

validate: true/false
callbacks: true/false

Both would default to true if not set.

Bang methods like save! should also honor the arguments and only raise if save failed WITH given arguments, rather than always assume save! requires validation.

This way a whole bunch of methods can be deprecated. The choice of n+1 or single query should be implementation detail which Rails can do in most efficient way possible under given circumstances. Mist users just want things to work and not have to research which method out of a myriad of similar looking ones is the right one.

Eugene

On Jun 4, 2014, at 2:22 PM, Matthew Draper notifications@github.com wrote:

So, I think this can go in, to round out:

delete_all : delete :: destroy_all : destroy :: update_all : update

And then we separately consider whether the _all methods might be renamed to something more explicitly different/dangerous. Right?


Reply to this email directly or view it on GitHub.

@egilburg

This comment has been minimized.

Show comment
Hide comment
@egilburg

egilburg Jun 4, 2014

Contributor

Example of how this could clean API up:

.update_all => .update

.update_attributes => .update(validate: false)

.update_columns => .update(validate: false, callbacks: false)

Let users say what they want done, not how to do it.

Ditto for all the other things...

Eugene

On Jun 4, 2014, at 2:34 PM, Eugene Gilburg eugene.gilburg@gmail.com wrote:

IMO In an ideal world the strategy would be determined by common arguments and not separate methods

.create .save, .update, .destroy, including the whether on record, assoc, or relation, would all take following arguments:

validate: true/false
callbacks: true/false

Both would default to true if not set.

Bang methods like save! should also honor the arguments and only raise if save failed WITH given arguments, rather than always assume save! requires validation.

This way a whole bunch of methods can be deprecated. The choice of n+1 or single query should be implementation detail which Rails can do in most efficient way possible under given circumstances. Mist users just want things to work and not have to research which method out of a myriad of similar looking ones is the right one.

Eugene

On Jun 4, 2014, at 2:22 PM, Matthew Draper notifications@github.com wrote:

So, I think this can go in, to round out:

delete_all : delete :: destroy_all : destroy :: update_all : update

And then we separately consider whether the _all methods might be renamed to something more explicitly different/dangerous. Right?


Reply to this email directly or view it on GitHub.

Contributor

egilburg commented Jun 4, 2014

Example of how this could clean API up:

.update_all => .update

.update_attributes => .update(validate: false)

.update_columns => .update(validate: false, callbacks: false)

Let users say what they want done, not how to do it.

Ditto for all the other things...

Eugene

On Jun 4, 2014, at 2:34 PM, Eugene Gilburg eugene.gilburg@gmail.com wrote:

IMO In an ideal world the strategy would be determined by common arguments and not separate methods

.create .save, .update, .destroy, including the whether on record, assoc, or relation, would all take following arguments:

validate: true/false
callbacks: true/false

Both would default to true if not set.

Bang methods like save! should also honor the arguments and only raise if save failed WITH given arguments, rather than always assume save! requires validation.

This way a whole bunch of methods can be deprecated. The choice of n+1 or single query should be implementation detail which Rails can do in most efficient way possible under given circumstances. Mist users just want things to work and not have to research which method out of a myriad of similar looking ones is the right one.

Eugene

On Jun 4, 2014, at 2:22 PM, Matthew Draper notifications@github.com wrote:

So, I think this can go in, to round out:

delete_all : delete :: destroy_all : destroy :: update_all : update

And then we separately consider whether the _all methods might be renamed to something more explicitly different/dangerous. Right?


Reply to this email directly or view it on GitHub.

@al2o3cr

This comment has been minimized.

Show comment
Hide comment
@al2o3cr

al2o3cr Jun 5, 2014

Contributor

@egilburg I don't think update and update_all should be the same method. Even shoehorning update_columns in makes the abstraction leak. Here's a breakdown of what I'm thinking:

On a single ActiveRecord instance:

# equivalent to current update method on an instance
@some_instance.update({foo: 'bar'}, {validate: true, callbacks: true})

# equivalent to update_columns
# will fail on invalid SQL if foo is not a database column
# should check to see if the columns being updated are marked as readonly
# and raise if so - update_columns currently does this
@some_instance.update({foo: 'bar'}, {validate: false, callbacks: false})

# equivalent to attributes= followed by save(validate: false)
@some_instance.update({foo: 'bar'}, {validate: false, callbacks: true})

# equivalent to... nothing currently available. Does this even make sense?
@some_instance.update({foo: 'bar'}, {validate: true, callbacks: false})

On a Relation:

# equivalent to the update method added in this PR
@some_relation.update({foo: 'bar'}, {validate: true, callbacks: true})

# equivalent to update_all
# will fail on invalid SQL if foo is not a database column
# does not check for readonly attributes
# should also accept a SQL string ('some_column = some_column + 1')
# *or* an array with placeholders (['some_column = MAGIC_SQL_FUNCTION(?)', value])
@some_relation.update({foo: 'bar'}, {validate: false, callbacks: false})

# no current equivalent
@some_relation.update({foo: 'bar'}, {validate: false, callbacks: true})

# equivalent to... nothing currently available. Again, does this even make sense?
@some_relation.update({foo: 'bar'}, {validate: true, callbacks: false})

To me, the significant number of nonsensical argument combinations (for instance, @some_relation.update('some_column = some_column +1', {validate: true, callbacks: true}), or any case with {validates: true, callbacks: false}) indicates that this isn't a single method.

What about maintaining the separation between "loads objects" and "doesn't load objects" but tidying up the names?:

# same as current
# maybe add validate: false as a possible option?
@some_instance.update({foo: 'bar'})

# also the same
@some_instance.update_columns({foo: 'bar'})

# new functionality; is this a thing people do?
# basically equivalent to:
# @some_instance.class.where(id: @some_instance.id).update_all(...)
@some_instance.update_columns('some_column = some_column + 1')

# on relations

# as presented in this PR; maybe add validate: false as an option?
@some_relation.update({foo: 'bar'})

# rename update_all to clarify the relationship with the instance version
# not quite equivalent to @some_relation.to_a.each { |record| record.update_columns(...) }
# because of readonly checking in the instance version
# validate: false would not be meaningful here
@some_relation.update_columns({foo: 'bar'})
@some_relation.update_columns('some_column = some_column + 1')

This collapses the "many methods" problem to exactly two: one that instantiates record(s) / runs callbacks, and one that bypasses the entire mechanism and pushes SQL at the database.

Apologies for the length of this comment; maybe this is a discussion better had on the mailing list?

Contributor

al2o3cr commented Jun 5, 2014

@egilburg I don't think update and update_all should be the same method. Even shoehorning update_columns in makes the abstraction leak. Here's a breakdown of what I'm thinking:

On a single ActiveRecord instance:

# equivalent to current update method on an instance
@some_instance.update({foo: 'bar'}, {validate: true, callbacks: true})

# equivalent to update_columns
# will fail on invalid SQL if foo is not a database column
# should check to see if the columns being updated are marked as readonly
# and raise if so - update_columns currently does this
@some_instance.update({foo: 'bar'}, {validate: false, callbacks: false})

# equivalent to attributes= followed by save(validate: false)
@some_instance.update({foo: 'bar'}, {validate: false, callbacks: true})

# equivalent to... nothing currently available. Does this even make sense?
@some_instance.update({foo: 'bar'}, {validate: true, callbacks: false})

On a Relation:

# equivalent to the update method added in this PR
@some_relation.update({foo: 'bar'}, {validate: true, callbacks: true})

# equivalent to update_all
# will fail on invalid SQL if foo is not a database column
# does not check for readonly attributes
# should also accept a SQL string ('some_column = some_column + 1')
# *or* an array with placeholders (['some_column = MAGIC_SQL_FUNCTION(?)', value])
@some_relation.update({foo: 'bar'}, {validate: false, callbacks: false})

# no current equivalent
@some_relation.update({foo: 'bar'}, {validate: false, callbacks: true})

# equivalent to... nothing currently available. Again, does this even make sense?
@some_relation.update({foo: 'bar'}, {validate: true, callbacks: false})

To me, the significant number of nonsensical argument combinations (for instance, @some_relation.update('some_column = some_column +1', {validate: true, callbacks: true}), or any case with {validates: true, callbacks: false}) indicates that this isn't a single method.

What about maintaining the separation between "loads objects" and "doesn't load objects" but tidying up the names?:

# same as current
# maybe add validate: false as a possible option?
@some_instance.update({foo: 'bar'})

# also the same
@some_instance.update_columns({foo: 'bar'})

# new functionality; is this a thing people do?
# basically equivalent to:
# @some_instance.class.where(id: @some_instance.id).update_all(...)
@some_instance.update_columns('some_column = some_column + 1')

# on relations

# as presented in this PR; maybe add validate: false as an option?
@some_relation.update({foo: 'bar'})

# rename update_all to clarify the relationship with the instance version
# not quite equivalent to @some_relation.to_a.each { |record| record.update_columns(...) }
# because of readonly checking in the instance version
# validate: false would not be meaningful here
@some_relation.update_columns({foo: 'bar'})
@some_relation.update_columns('some_column = some_column + 1')

This collapses the "many methods" problem to exactly two: one that instantiates record(s) / runs callbacks, and one that bypasses the entire mechanism and pushes SQL at the database.

Apologies for the length of this comment; maybe this is a discussion better had on the mailing list?

+ # people = Person.where(group: 'expert')
+ # people.update(group: 'masters')
+ #
+ # Note: Updating large number of records will be time consuming

This comment has been minimized.

@rafaelfranca

rafaelfranca Jun 6, 2014

Member
#   Note: Updating a large number of records will run a
#   UPDATE query for each record, which may cause a performance
#   issue. So if it is not needed to run callbacks for each update, is
#   prefered to use <tt>update_all</tt> for updating all records using
#   a single query.
@rafaelfranca

rafaelfranca Jun 6, 2014

Member
#   Note: Updating a large number of records will run a
#   UPDATE query for each record, which may cause a performance
#   issue. So if it is not needed to run callbacks for each update, is
#   prefered to use <tt>update_all</tt> for updating all records using
#   a single query.

This comment has been minimized.

@prathamesh-sonpatki

prathamesh-sonpatki Jun 7, 2014

Member

@rafaelfranca Should we say update_all can be used instead of update_all is preferred

@prathamesh-sonpatki

prathamesh-sonpatki Jun 7, 2014

Member

@rafaelfranca Should we say update_all can be used instead of update_all is preferred

@@ -1421,6 +1421,14 @@ def test_update_all_with_joins_and_offset_and_order
assert_equal posts(:welcome), comments(:greetings).post
end
+ def test_update_on_relation

This comment has been minimized.

@rafaelfranca

rafaelfranca Jun 6, 2014

Member

Can we add a test to make sure callbacks are run?

@rafaelfranca

rafaelfranca Jun 6, 2014

Member

Can we add a test to make sure callbacks are run?

+ # Testing that the callbacks have run
+ assert_equal 'David', topic1.reload.author_name
+ assert_equal 'David', topic1.reload.author_name
+ end

This comment has been minimized.

@prathamesh-sonpatki

prathamesh-sonpatki Jun 7, 2014

Member

@rafaelfranca Added test to make sure callbacks run

@prathamesh-sonpatki

prathamesh-sonpatki Jun 7, 2014

Member

@rafaelfranca Added test to make sure callbacks run

@rafaelfranca

This comment has been minimized.

Show comment
Hide comment
@rafaelfranca

rafaelfranca Jun 7, 2014

Member

The build is red now. Could you check?

Member

rafaelfranca commented Jun 7, 2014

The build is red now. Could you check?

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

prathamesh-sonpatki Jun 8, 2014

Member

@rafaelfranca Can you restart the build. I fixed the failing test

Member

prathamesh-sonpatki commented Jun 8, 2014

@rafaelfranca Can you restart the build. I fixed the failing test

@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment

@rafaelfranca Build is 💚

@mechanicles

This comment has been minimized.

Show comment
Hide comment
@mechanicles

mechanicles Jun 10, 2014

Contributor

👍

Contributor

mechanicles commented Jun 10, 2014

👍

@dhh

This comment has been minimized.

Show comment
Hide comment
@dhh

dhh Jun 10, 2014

Member

Agree that we shouldn’t mix the methods that update records one at the time with callbacks with the bulk SQL update commands. These are so different in strategy that we shouldn’t try to pave over that.

On Jun 4, 2014, at 9:41 PM, Eugene Gilburg notifications@github.com wrote:

Example of how this could clean API up:

.update_all => .update

.update_attributes => .update(validate: false)

.update_columns => .update(validate: false, callbacks: false)

Let users say what they want done, not how to do it.

Ditto for all the other things...

Eugene

On Jun 4, 2014, at 2:34 PM, Eugene Gilburg eugene.gilburg@gmail.com wrote:

IMO In an ideal world the strategy would be determined by common arguments and not separate methods

.create .save, .update, .destroy, including the whether on record, assoc, or relation, would all take following arguments:

validate: true/false
callbacks: true/false

Both would default to true if not set.

Bang methods like save! should also honor the arguments and only raise if save failed WITH given arguments, rather than always assume save! requires validation.

This way a whole bunch of methods can be deprecated. The choice of n+1 or single query should be implementation detail which Rails can do in most efficient way possible under given circumstances. Mist users just want things to work and not have to research which method out of a myriad of similar looking ones is the right one.

Eugene

On Jun 4, 2014, at 2:22 PM, Matthew Draper notifications@github.com wrote:

So, I think this can go in, to round out:

delete_all : delete :: destroy_all : destroy :: update_all : update

And then we separately consider whether the _all methods might be renamed to something more explicitly different/dangerous. Right?


Reply to this email directly or view it on GitHub.

Reply to this email directly or view it on GitHub.

Member

dhh commented Jun 10, 2014

Agree that we shouldn’t mix the methods that update records one at the time with callbacks with the bulk SQL update commands. These are so different in strategy that we shouldn’t try to pave over that.

On Jun 4, 2014, at 9:41 PM, Eugene Gilburg notifications@github.com wrote:

Example of how this could clean API up:

.update_all => .update

.update_attributes => .update(validate: false)

.update_columns => .update(validate: false, callbacks: false)

Let users say what they want done, not how to do it.

Ditto for all the other things...

Eugene

On Jun 4, 2014, at 2:34 PM, Eugene Gilburg eugene.gilburg@gmail.com wrote:

IMO In an ideal world the strategy would be determined by common arguments and not separate methods

.create .save, .update, .destroy, including the whether on record, assoc, or relation, would all take following arguments:

validate: true/false
callbacks: true/false

Both would default to true if not set.

Bang methods like save! should also honor the arguments and only raise if save failed WITH given arguments, rather than always assume save! requires validation.

This way a whole bunch of methods can be deprecated. The choice of n+1 or single query should be implementation detail which Rails can do in most efficient way possible under given circumstances. Mist users just want things to work and not have to research which method out of a myriad of similar looking ones is the right one.

Eugene

On Jun 4, 2014, at 2:22 PM, Matthew Draper notifications@github.com wrote:

So, I think this can go in, to round out:

delete_all : delete :: destroy_all : destroy :: update_all : update

And then we separately consider whether the _all methods might be renamed to something more explicitly different/dangerous. Right?


Reply to this email directly or view it on GitHub.

Reply to this email directly or view it on GitHub.

@rafaelfranca rafaelfranca removed this from the 4.2.0 milestone Aug 18, 2014

@rafaelfranca rafaelfranca added this to the 5.0.0 milestone Aug 18, 2014

Allow ActiveRecord::Relation#update to run on result of a relation wi…
…th callbacks and validations

- Right now, there is no method to update multiple records with
  validations and callbacks.
- Changed the behavior of existing `update` method so that when `id`
  attribute is not given and the method is called on an `Relation`
  object, it will execute update for every record of the `Relation` and
  will run validations and callbacks for every record.
- Added test case for validating that the callbacks run when `update` is
  called on a `Relation`.
- Changed test_create_columns_not_equal_attributes test from
  persistence_test to include author_name column on topics table as it
  it used in before_update callback.
- This change introduces performance issues when a large number of
  records are to be updated because it runs UPDATE query for every
  record of the result. The `update_all` method can be used in that case
  if callbacks are not required because it will only run single UPDATE
  for all the records.
@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
@prathamesh-sonpatki

prathamesh-sonpatki Dec 20, 2014

Member

@rafaelfranca Rebased with latest master. Can we take a look at this now?

@rafaelfranca Rebased with latest master. Can we take a look at this now?

@rafaelfranca rafaelfranca merged commit 5ef713c into rails:master Jan 2, 2015

1 check passed

continuous-integration/travis-ci The Travis CI build passed
Details

rafaelfranca added a commit that referenced this pull request Jan 2, 2015

Merge pull request #11898 from prathamesh-sonpatki/patch-update
Changed ActiveRecord::Relation#update behavior so that it will work on Relation objects without giving id

Conflicts:
	activerecord/CHANGELOG.md
@prathamesh-sonpatki

This comment has been minimized.

Show comment
Hide comment
Member

prathamesh-sonpatki commented Jan 2, 2015

@rafaelfranca Thanks :)

@prathamesh-sonpatki prathamesh-sonpatki deleted the prathamesh-sonpatki:patch-update branch Jan 2, 2015

@rafaelfranca rafaelfranca modified the milestones: 5.0.0 [temp], 5.0.0 Dec 30, 2015

prathamesh-sonpatki added a commit to prathamesh-sonpatki/rails that referenced this pull request Jun 7, 2016

prathamesh-sonpatki added a commit to prathamesh-sonpatki/rails that referenced this pull request Jun 7, 2016

prathamesh-sonpatki added a commit to prathamesh-sonpatki/rails that referenced this pull request Jun 7, 2016

prathamesh-sonpatki added a commit to prathamesh-sonpatki/rails that referenced this pull request Jun 9, 2016

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