-
Notifications
You must be signed in to change notification settings - Fork 897
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
api sort on sql friendly virtual attributes #13409
api sort on sql friendly virtual attributes #13409
Conversation
@@ -161,6 +161,8 @@ def sort_params(klass) | |||
options = String(params['sort_options']).split(",") | |||
params['sort_by'].split(",").zip(orders).collect do |attr, order| | |||
if klass.attribute_method?(attr) || klass.method_defined?(attr) || attr == klass.primary_key | |||
raise BadRequestError, | |||
"#{klass.name} cannot be sorted by #{attr}" unless klass.attribute_supported_by_sql?(attr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wondering if attribute_supported_by_sql?
implies the condition on 163? Could this be simplified?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@imtayadeway no it's not implied on 163. I will try to simplify it, but I also want to send back that specific error message, rather than the one below that indicates it is not an attribute (since it is, just not a sortable one)
sort_item << " ASC" if order && order.downcase.start_with?("asc") | ||
sort_item << " DESC" if order && order.downcase.start_with?("desc") | ||
sort_item | ||
if options.map(&:downcase).include?("ignore_case") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a change of behavior or a refactoring?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@imtayadeway change of behavior. Format needs to be { :attribute => order }
rather than the <attribute> <ORDER>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this preserve the behavior of doing ignore case/descending?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@imtayadeway good catch I'll update it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jntullo if this was green we must have insufficient test coverage here....can you add some to verify existing behavior? Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@imtayadeway so, afaik (spent a bit of time diving into this) it's not possible to add the LOWER to the hash that's needed to sort on the virtual_attributes
. Because it was getting a little icky, I decided to account for that by adding in another method for sorting of virtual attributes, but not sure how I feel about that. Thoughts, suggestions?
Maybe @kbrock can provide some insight into case-insensitive sort on virtual attributes? reorder(LOWER(attr)
does not work, and it requires the format .reorder({:attr => 'asc'})
. Is there a way to add case insensitive sort to this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great.
Just one main comment.
Possibly, it will allow you to keep all in sort_directive
and not have to implement 2 different methods for this to work
|
||
def sort_virtual_attr_directive(klass, attr, order, options) | ||
raise BadRequestError, | ||
"#{klass.name} cannot be sorted by #{attr}" unless klass.attribute_supported_by_sql?(attr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great.
In reports/views, we do support sorting by non sql friendly attributes.
It is a major memory hit and very slow.
@@ -175,6 +177,18 @@ def sort_directive(attr, order, options) | |||
sort_item << " DESC" if order && order.downcase.start_with?("desc") | |||
sort_item | |||
end | |||
|
|||
def sort_virtual_attr_directive(klass, attr, order, options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
darn, was hoping that virtual and regular ones can be implemented together.
raise BadRequestError, | ||
"#{klass.name} cannot be sorted with ignored case" if options.map(&:downcase).include?("ignore_case") | ||
if order | ||
{ attr.to_sym => order.downcase } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe something like:
klass.arel_attribute(attr).downcase.send(order.downcase)
I wonder if there is a way for us to reuse miq_report/search.rb#get_order_info
Also, a while ago, arel_attribute().lower
was not supported for either attributes or virtual attributes - can't quite remember. So instead of the Arel::Nodes::Lower
you may want to just try .lower
and see if it will work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kbrock that worked! Thank you!
kicking |
@@ -160,20 +160,24 @@ def sort_params(klass) | |||
orders = String(params['sort_order']).split(",") | |||
options = String(params['sort_options']).split(",") | |||
params['sort_by'].split(",").zip(orders).collect do |attr, order| | |||
if klass.attribute_method?(attr) || klass.method_defined?(attr) || attr == klass.primary_key | |||
sort_directive(attr, order, options) | |||
if klass.virtual_attribute?(attr) && !klass.attribute_supported_by_sql?(attr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can probably drop klass.virtual_attribute?(attr)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
heck, can you just do:
if klass.attribute_supported_by_sql?(attr)
sort_directive(klass, attr, order, options)
else
raise BadRequestError, "#{klass.name} cannot be sorted by #{attr}"
end
(I probably missed something obvious here - e.g.: may need attr == klass.primary_key
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kbrock would definitely be more simplistic, but I thought it might be a good idea to have both the is not a valid attribute
and cannot be sorted by
messages separate for clarification
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
I don't think this part is necessary:
klass.attribute_method?(attr) || klass.method_defined?(attr) || attr == klass.primary_key
@abellotti you ok just using klass.attribute_supported_by_sql?(attr)
instead and dropping the ever neat previous one?
Did we need to support any ruby method or only fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the simpler path is more elegant, I like what @jntullo did to have the 2 different error messages. I'm ok with this approach here as this is low count iteration.
sort_item << " ASC" if order && order.downcase.start_with?("asc") | ||
sort_item << " DESC" if order && order.downcase.start_with?("desc") | ||
sort_item | ||
def sort_directive(klass, attr, order, options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoa - now THAT is what I'm talking about
sort_item | ||
def sort_directive(klass, attr, order, options) | ||
if options.map(&:downcase).include?("ignore_case") && order | ||
klass.arel_attribute(attr).lower.send(order.downcase) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems unlikely, but could send
be exploited? At the very least I think public_send
might be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 very nice enhancement @jntullo. Thanks !!
run_get vms_url, :sort_by => 'host_name', :sort_order => 'desc', :expand => 'resources' | ||
|
||
expect_query_result(:vms, 2, 2) | ||
expect_result_resources_to_match_hash([{'name' => 'vm_bar'}, {'name' => 'vm_foo'}]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we're sorting in descending order by host_name, shouldn't the list coming back be vm_foo then vm_bar ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh. Is there a bug in the core for sorting?
Curious what the sql is for this query?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kbrock oh...yikes...I totally missed that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abellotti turned out to be a mistake in the test. a facepalm moment. good to go now 👍
@@ -160,20 +160,24 @@ def sort_params(klass) | |||
orders = String(params['sort_order']).split(",") | |||
options = String(params['sort_options']).split(",") | |||
params['sort_by'].split(",").zip(orders).collect do |attr, order| | |||
if klass.attribute_method?(attr) || klass.method_defined?(attr) || attr == klass.primary_key | |||
sort_directive(attr, order, options) | |||
if klass.virtual_attribute?(attr) && !klass.attribute_supported_by_sql?(attr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the simpler path is more elegant, I like what @jntullo did to have the 2 different error messages. I'm ok with this approach here as this is low count iteration.
changing from send to public send fix spec simplifying sort params to use supported by sql
sort_item << " DESC" if order && order.downcase.start_with?("desc") | ||
sort_item | ||
def sort_directive(klass, attr, order, options) | ||
if options.map(&:downcase).include?("ignore_case") && order |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, but I noticed all three branches had the same code, and two of them contained the same condition. I don't know if something like this is any better:
arel = klass.arel_attribute(attr)
if order
if options.map(&:downcase).include?("ignore_case")
arel = arel.lower
end
arel = arel.asc if order.downcase == "asc"
arel = arel.desc if order.downcase == "desc" # more verbose, but lower cognitive load than dynamic method sending
end
arel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think arel.asc
is not necessary.
So you can skip the entire line: arel = arel.asc if order.downcase == "asc"
if order | ||
if options.map(&:downcase).include?("ignore_case") && order | ||
arel = arel.lower | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for && order above, you can replace lines 176-178 with:
arel = arel.lower if options.map(&:downcase).include?("ignore_case")
Thanks @jntullo for the update, LGTM!! Waiting for 🍏 |
Checked commits jntullo/manageiq@f1f932d~...3f7034b with ruby 2.2.5, rubocop 0.37.2, and haml-lint 0.16.1 |
run_get vms_url, :sort_by => 'host_name', :sort_order => 'desc', :expand => 'resources' | ||
|
||
expect_query_result(:vms, 2, 2) | ||
expect_result_resources_to_match_hash([{'name' => 'vm_foo'}, {'name' => 'vm_bar'}]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the outside, it looks like this query returned your objects in the order you created them. I think to prove that they were ordered successfully it may be necessary to create three objects out of order, so that you can see that the result put them into some order in a way that couldn't be an accident.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jntullo can you create another PR to enhance the tests as per @imtayadeway ? Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abellotti will do!
it 'supports sql friendly virtual attributes' do | ||
host_foo = FactoryGirl.create(:host, :name => 'foo') | ||
host_bar = FactoryGirl.create(:host, :name => 'bar') | ||
vm_foo = FactoryGirl.create(:vm, :name => 'vm_foo') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you try :host => host_foo
here?
🎉 |
This updates the sort directive to allow for sorting on sql friendly virtual attributes. Adds an additional error message to indicate the collection cannot be sorted by the non-sql friendly virtual attribute.
@miq-bot assign @abellotti
@miq-bot add_label api, enhancement
cc: @jeff-phillips-18