Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bulk update through act_as_list_no_update #171

Closed
wants to merge 6 commits into from
Closed

bulk update through act_as_list_no_update #171

wants to merge 6 commits into from

Conversation

randoum
Copy link
Contributor

@randoum randoum commented Sep 29, 2015

To newcomers : The conversation for this PR continues here #244

Allow to perform bulk update by disabling act_as_list callbacks

ActiveRecord::Base.act_as_list_no_update do
  TodoItem.find(5).update position: 17
  TodoItem.find(8).update position: 11
end

inspired from http://api.rubyonrails.org/classes/ActiveRecord/NoTouching/ClassMethods.html

end

def act_as_list_run_callbacks? # :nodoc:
! Disabled.applied_to?(self.class)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not leave space between ! and its argument.

@randoum
Copy link
Contributor Author

randoum commented Oct 3, 2015

@swanandp the CI error comes from bundle

Please install the sqlite3 adapter: gem install activerecord-sqlite3-adapter (sqlite3 is not part of the bundle. Add it to Gemfile.)

Shall I do something about that?

@swanandp
Copy link
Contributor

swanandp commented Oct 4, 2015

I think that calls for a separate PR. Go for it!

On Sat, Oct 3, 2015 at 11:05 AM, randoum notifications@github.com wrote:

@swanandp https://github.com/swanandp the CI error comes from bundle

Please install the sqlite3 adapter: gem install
activerecord-sqlite3-adapter (sqlite3 is not part of the bundle. Add it
to Gemfile.)

Shall I do something about that?


Reply to this email directly or view it on GitHub
#171 (comment)
.

@randoum
Copy link
Contributor Author

randoum commented Oct 4, 2015

Ok, noted. Let's focus on this one first if you don't mind. What's your feedback?

@randoum
Copy link
Contributor Author

randoum commented Oct 7, 2015

@swanandp any feedback? Thanks.

@swanandp
Copy link
Contributor

swanandp commented Oct 7, 2015

Sorry, got held up in other things, will come back to this shortly.

@randoum
Copy link
Contributor Author

randoum commented Oct 7, 2015

Ok sure.

@randoum
Copy link
Contributor Author

randoum commented Nov 15, 2015

@swanandp any feedback? Thanks.

@fabn
Copy link
Contributor

fabn commented Dec 16, 2015

@randoum tests have been fixed in master. Could you please rebase and fix hound issues as well?

Thanks.


ArrayScopeListMixin.where(id: 2).first.remove_from_list
ArrayScopeListMixin.where(id: 2).first.destroy

assert_equal [1, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: 'ParentClass').order('pos').map(&:id)
assert_equal [1, 3, 4], ArrayScopeListMixin.where(parent_id: 5, parent_type: "ParentClass").order("pos").map(&:id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [120/80]

@nickpoorman
Copy link

nickpoorman commented Dec 5, 2016

I could really use this right now... 😞 I'm updating thousands of items in a list and it's wreaking havoc on my database.

@brendon
Copy link
Owner

brendon commented Dec 5, 2016

Hi @nickpoorman, sorry to hear that. As a workaround you could use update_columns to circumvent all the callback logic. Or if you feel like chipping in on this PR please do :) It's at a bit of an impasse at the moment.

@wikyd, now that @swanandp has had his say, would you like to push this through along his recommendations?

@wikyd
Copy link

wikyd commented Dec 5, 2016

I would like to help, but the proposed changes does not work with our use case. We have a Javascript front-end that allows users to re-order and remove items from the list. Then, we take this set of changes and update our items in a batch use Rails params -> model magic. Turning off callbacks for a specific model does not help in that case.

@brendon
Copy link
Owner

brendon commented Dec 5, 2016

@wikyd, I've always found this to be the most troubling part of using acts_as_list. Unless you use the relative move methods or set one position at a time, the callbacks can get in the way. I often use this relation extension:

module Sortable
  def sort(array_of_ids)
    array_of_ids.each.with_index(1) do |id, index|
      where(:id => id).update_all(:position => index)
    end
  end
end

As a way to mass-assign positions passed in as a list of id's. It can be limited if the list of id's isn't all the id's in the scope though.

@nickpoorman
Copy link

nickpoorman commented Dec 5, 2016

@wikyd - I'm actually doing the same thing. We have a bulk upsert endpoint that does a create/destroy/update all in one. For this route, I had to disable paper trail also, as it was creating massive amounts of rows in our versions table. They seemed to have solved this "disable" issue this way: https://github.com/airblade/paper_trail/blob/05dd9fb40848c2c8e7079eac7fccffb54720db9e/lib/paper_trail/record_trail.rb#L425

ie.

@widget.paper_trail.without_versioning do
  @widget.update_attributes :name => 'Ford'
end

...and it works quite well.

@brendon
Copy link
Owner

brendon commented Dec 5, 2016

@nickpoorman, they're using some kind of class variable to track the disabled state? I'd assume that wasn't thread safe.

@nickpoorman
Copy link

@brendon - I believe it is thread safe: paper-trail-gem/paper_trail#328

@brendon
Copy link
Owner

brendon commented Dec 6, 2016

@nickpoorman, ah yes, I use that in my applications too. It's handy, but may be brittle to add as a dependency in our case? Will there be any downsides say in the console?

@brendon
Copy link
Owner

brendon commented Dec 6, 2016

I noticed ancestry has a similar method though I've not investigated how they do theirs.

@nickpoorman
Copy link

nickpoorman commented Dec 6, 2016

@brendon, as far as I can tell request_store is a wrapper for Thread.current with some middleware to clean up after requests so I see no reason why it wouldn't work in the console.

@brendon
Copy link
Owner

brendon commented Dec 6, 2016

Yes I suppose if the store variable is set and unset cleanly after the block executes then that would be fine.

Here's how ancestry is doing it: https://github.com/stefankroes/ancestry/blob/master/lib/ancestry/instance_methods.rb#L296

It's an instance method but is quite spread out in terms of its application in the code.

@swanandp, do you have any more thoughts given the latest comments here?

@swanandp
Copy link
Contributor

swanandp commented Dec 7, 2016

@brendon Definitely need to do the blocking on object level. Let's avoid class or thread level variables unless we have to, and we don't have to here.

@brendon
Copy link
Owner

brendon commented Dec 7, 2016

Thanks @swanandp. @wikyd, @randoum given this direction, would one of you like to refactor this so that it uses an instance variable and also if possible includes just checks for this variable on the callback declarations rather than inline in the code?

@randoum
Copy link
Contributor Author

randoum commented Dec 7, 2016

Personally I have zero availability.
Just a note about request store. It is used in several gems for quite some times and seems to be maturely thread safe.

@brendon
Copy link
Owner

brendon commented Dec 7, 2016

Thanks @randoum, that's all good. I have no doubts about the quality of RequestStore but as @swanandp says in this case, it's not necessary to use a thread level variable so I'd rather not add an unnecessary dependency. Thanks for your input on this PR to date. :)

@swanandp
Copy link
Contributor

swanandp commented Dec 7, 2016 via email

StoneFrog and others added 6 commits December 17, 2016 11:14
# The first commit's message is:
fix setting position when previous position was nil (#230)

* set position properly if starting position is nil
* update tests to work with rails 3.2

Closes #109
# This is the 2nd commit message:

Show items with same position in higher and lower items (#231)

* Show items with same position in higher and lower items
* Remove spacing, use update_column instead of update_columns

# This is the 3rd commit message:

Version 0.8.2

# This is the 4th commit message:

Updated Changelog 0.8.2

# This is the 5th commit message:

Extract modules (#229)

* Convert .acts_as_list to use keyword arguments
* Refactoring .acts_as_list: remove configuration variable
* Refactoring: use ActiveSupport::Inflector.foreign_key instead of ad-hoc
* Refactoring: extract #idify
* Refactoring: extract ScopeDefiner module
* Refactoring: extract TopOfListDefiner module
* Refactoring: extract ColumnDefiner module
* Refactoring: extract AddNewAtDefiner module
* Refactoring: extract UpdatePositonDefiner module
* Refactoring: remove redundant #class_eval call
* Refactoring: rename *Definer modules to *MethodDefiner
* Refactoring: extract CallbackDefiner module
* Refactoring: remove keyword arguments to support Ruby 1.9
* Add a missing dot in comments
* Refactoring: merge UpdatePositonMethodDefiner into ColumnMethodDefiner
* Refactoring: extract AuxMethodDefiner module
# This is the 6th commit message:

Add travis config for testing against multiple databases (#236)

* Add travis-CI config for tests with postgres / mysql / sqlite
# This is the 7th commit message:

Use timecop to freeze time for testing

# This is the 8th commit message:

Compare updated_at with TimeWithZone

# This is the 9th commit message:

Freeze time at Time.current

# This is the 10th commit message:

Cache the current time

# This is the 11th commit message:

Fix non regular sequence movement (#237)

# This is the 12th commit message:

Be explicit about ordering when mapping :pos in tests (#241)

# This is the 13th commit message:

Improve load method (#240)

* Refactor loading sequence
* Fix failing tests
# This is the 14th commit message:

Fixed tests to prevent warning: too many arguments for format string (#242)

# This is the 15th commit message:

README update: Adding `acts_as_list` To An Existing Model

Closes #210

# This is the 16th commit message:

Update README.md (#243)

add .all to fix undefined each method in scope option
# This is the 17th commit message:

bulk update through act_as_list_no_update
@randoum
Copy link
Contributor Author

randoum commented Dec 18, 2016

I am not sure we can do without Thread.current.

If you can do @widget.paper_trail.without_versioning {@widget.update_attributes :name => 'Ford'} it's because the without_versioning statement concerns only @widget, which is a single record.

In our case an act_as_list_no_update block would apply to the behavior of multiple records or a whole model.

In terms of use case, you would never write:

TodoItem.find(5).acts_as_list_no_callback do
  update(:position => 5)
end

Because it's plain useless.


Instead, you would be using acts_as_list_no_callback for mass updates, mass ordering, mass import or such. Hence the statements would be concerning multiple records.

How can you write a block statement that concern multiple record? There's only one way, by using a block at the class level :

TodoItem.acts_as_list_no_callback do
  mass_import_method
end

2 choices: class instance variable or Thread.current variable. The second one is way safer.

So I understand you guys fears toward Thread.current, but I'd like to remind you that my original PR was based on http://api.rubyonrails.org/classes/ActiveRecord/NoTouching/ClassMethods.html which is part or rails core and using Thread.current.

If rails core team is using it, we can. We are using Thread.current in a block context, no around thread synchronization or between different webserver sessions, it's safe to use in our case. The only important thing is to clear the thread variable in an ensureblock.


Some reading : http://stackoverflow.com/questions/7896298/safety-of-thread-current-usage-in-rails

@brendon
Copy link
Owner

brendon commented Dec 18, 2016

Hi @randoum, you make a good point :) @swanandp, can you chime in?

@randoum, your latest rebase has reverted a bunch of changes on master (Appraisal config, CHANGELOG etc...)

@randoum
Copy link
Contributor Author

randoum commented Dec 18, 2016

Yeah I messed up big time with git. I have no idea what I did. Finally I deleted my fork and I already started from a fresh one.

@randoum
Copy link
Contributor Author

randoum commented Dec 19, 2016

Kindly continue there #244

@randoum randoum closed this Dec 19, 2016
@randoum randoum mentioned this pull request Jan 16, 2017
@randoum
Copy link
Contributor Author

randoum commented Jan 16, 2017

@wikyd @nickpoorman @StoneFrog if you guys don't mind to participate to #244. Thanks

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

Successfully merging this pull request may close these issues.

8 participants