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

Rails 4 prep #4667

Merged
merged 6 commits into from
Feb 4, 2020
Merged

Conversation

Matt-Yorkley
Copy link
Contributor

What? Why?

Pulls across some commits for upgrading to Rails 4. Mostly minor changes in syntax.

What should we test?

I think a green build will be enough to validate these changes.

Release notes

Adapted some syntax in preparation for Rails 4.

Changelog Category: Changed

@Matt-Yorkley Matt-Yorkley self-assigned this Jan 12, 2020
@Matt-Yorkley Matt-Yorkley force-pushed the rails-4-prep branch 3 times, most recently from 2cd052c to 6ed7af9 Compare January 12, 2020 13:59
@Matt-Yorkley Matt-Yorkley mentioned this pull request Jan 12, 2020
Copy link
Contributor

@luisramos0 luisramos0 left a comment

Choose a reason for hiding this comment

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

nice!

app/controllers/spree/admin/base_controller.rb Outdated Show resolved Hide resolved
Copy link
Member

@mkllnk mkllnk left a comment

Choose a reason for hiding this comment

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

Nice work.

Can you link me to some information about the issue with subqueries in Rails 4? Subqueries can be much more efficient, right?

@@ -117,7 +117,7 @@ class Enterprise < ActiveRecord::Base
# checkout.
ready_enterprises = Enterprise.ready_for_checkout.select('enterprises.id')
if ready_enterprises.present?
where("enterprises.id NOT IN (?)", ready_enterprises)
where("enterprises.id NOT IN (?)", ready_enterprises.map(&:id))
Copy link
Member

Choose a reason for hiding this comment

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

I think that we can use pluck(:id) here to be more efficient. The select isn't needed then. But this could have a performance impact, the array can be big.

Copy link
Contributor

Choose a reason for hiding this comment

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

pluck could create an extra query, right?
I think this should be select :-)
#3709
I think we should agree on some rules for using map vs select vs pluck :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should agree on some rules for using map vs select vs pluck :-)

That sounds good! And we could add it to the wiki

Copy link
Member

Choose a reason for hiding this comment

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

Thank you for the clarification. I now think that I know how it works.

In this particular case, there was a select that would lead to a sub-query. The present? call creates a separate query just looking if anything is there without loading the enterprises. Then applying map would first load all the enterprise objects and then copy the ids into an array. A pluck call would do that without creating the enterprise objects (less Ruby work).

If we used the enterprise objects somewhere else, we should use map to re-use the same array and not load the enterprises again. But I don't think we do it here, so pluck would be better than map.

Matt, you explained that select * is a problem with sub-queries in Rails 4. That's logical. We should definitely avoid that. In this case, we do select('enterprises.id') in line 118 which should allow us to do an efficient sub-query. That should be no problem with Rails 4, right?

With my current knowledge, we don't need map here and should use a sub-query. We could move the select from line 118 down to the 120 to make it clearer that we are selecting the ids for the IN query part.

Copy link
Contributor

Choose a reason for hiding this comment

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

awesome Maikel 👌

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Aha! Yeah it looks like it's mostly the prepared statements in our scopes that are causing issues. In the #managed_by scope in enterprise_fee.rb this line throws a "subquery has too many columns" error:

where('enterprise_id IN (?)', user.enterprises)

but this does not:

where(enterprise_id: user.enterprises)

Copy link
Contributor

Choose a reason for hiding this comment

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

nice investigation Matt!!
is there a reason you are not satisfied with how we have been fixing things so far with:
where('enterprise_id IN (?)', user.enterprises.select(&:id))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I need to redo the other subquery PR and check some things in Rails 4. It looks like it will be better if we avoid prepared statements and favour the hash syntax, passing an ActiveRecord::Relation object (as mentioned above). And in that case I think we generally don't need the select, as ActiveRecord should in theory figure it out and build the subquery correctly from the hash parameters?

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure I agree.
Prepared statements are a good thing in general. I dont think we should plan to just avoid them.
I dont think we should let active record control things, we should control things and we should name the exact fields we want in the query.
These are general rules I have seen used in other projects: as much prepared statements as possible and lists of fields in a query as specific as possible.

Copy link
Member

Choose a reason for hiding this comment

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

In general, the new Rails 4 way where.not(id: ready_enterprises) sounds better than what we have at the moment. But for compatibility with Rails 3 and 4, I would prefer where("enterprises.id NOT IN (?)", ready_enterprises.select("enterprises.id")) for now. The reason I prefer this version is that it's very explicit what's happening, it's compatible with many versions and avoids the Rails 3 bug where multiple where(id: collection) statements override each other.

Instead of optimising the way we use the ActiveRecord interface further, I would also suggest looking at the underlying problem and changing our logic here. This negation is really expensive. It would be better to find criteria for enterprises that make them not being ready for checkout and using those in a direct query.

One problem is that there can be several reasons why an enterprise is not ready. And we are querying them all together. Instead, we could actually query those individually and change the UI to say:

  • These enterprises are missing a shipping method: ...
  • These enterprises are missing a payment method: ...
  • ...

@Matt-Yorkley I noticed that your most recent version still uses pluck. Does select('enterprises.id') still raise the too-many-column error? I don't understand that because enterprises.id is only one column, isn't it? Or are there many more in JOIN clauses that count as well?

@Matt-Yorkley
Copy link
Contributor Author

Matt-Yorkley commented Jan 16, 2020

Can you link me to some information about the issue with subqueries in Rails 4?

It's difficult to find anything concrete for this specific error. Basically when a subquery is not specifying which columns to fetch and defaulting to SELECT *, it throws a fatal error. It seems to be specific to Postgres and Rails 4. I guess it enforces more efficient queries by forcing you to select only the columns you will actually use in the subquery.

@luisramos0
Copy link
Contributor

there's this issue for it:
#3708

@sigmundpetersen sigmundpetersen added the pr-staged-uk staging.openfoodnetwork.org.uk label Jan 22, 2020
@Matt-Yorkley Matt-Yorkley removed the pr-staged-uk staging.openfoodnetwork.org.uk label Jan 22, 2020
return if distributors.empty? || flash[:notice].present?

flash[:notice] = active_distributors_not_ready_for_checkout_message(distributors)
warning = OrderCycleWarning.new(spree_current_user).call
Copy link
Contributor

Choose a reason for hiding this comment

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

With this abstraction the intent is much clearer now 👌

Copy link
Member

@kristinalim kristinalim left a comment

Choose a reason for hiding this comment

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

Nice work, @Matt-Yorkley! 😃

@kristinalim
Copy link
Member

@mkllnk Are you happy with this PR already? 🙂

@mkllnk
Copy link
Member

mkllnk commented Jan 30, 2020

I'm still concerned about the performance of pluck in that one example. If it's really needed to be compatible with Rails 4, okay. But I got mixed answers here, nothing explaining why we can't use select in this example, selecting only once column in the subquery.

@Matt-Yorkley
Copy link
Contributor Author

I'm still concerned about the performance of pluck in that one example.

I'll try it again in Rails 4

@Matt-Yorkley Matt-Yorkley force-pushed the rails-4-prep branch 2 times, most recently from 6c7979e to 2df09e3 Compare February 4, 2020 00:07
@Matt-Yorkley
Copy link
Contributor Author

Aha! The resulting query here looked like this in Rails 4:

...NOT IN (SELECT DISTINCT enterprises.*, enterprises.id FROM "enterprises" JOIN...

The scope that's being negated here uses .select('DISCINCT enterpises.*) at the end. I've used #except here to override it, and it now works in Rails 4 without #pluck.

@mkllnk
Copy link
Member

mkllnk commented Feb 4, 2020

Great work @Matt-Yorkley! Only Code Climate is the usual grinch. 😉

@sauloperez
Copy link
Contributor

All green so merging!

@sauloperez sauloperez merged commit 246235b into openfoodfoundation:master Feb 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants