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

api sort on sql friendly virtual attributes #13409

Merged
merged 4 commits into from
Jan 16, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions app/controllers/api/base_controller/parameters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

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)

Copy link
Member

@kbrock kbrock Jan 11, 2017

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)

Copy link
Author

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

Copy link
Member

@kbrock kbrock Jan 12, 2017

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?

Copy link
Member

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.

raise BadRequestError, "#{klass.name} cannot be sorted by #{attr}"
elsif klass.attribute_supported_by_sql?(attr)
sort_directive(klass, attr, order, options)
else
raise BadRequestError, "#{attr} is not a valid attribute for #{klass.name}"
end
end.compact
end

def sort_directive(attr, order, options)
sort_item = attr
sort_item = "LOWER(#{sort_item})" if options.map(&:downcase).include?("ignore_case")
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)
Copy link
Member

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

if options.map(&:downcase).include?("ignore_case") && order
Copy link
Contributor

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

Copy link
Member

@kbrock kbrock Jan 16, 2017

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"

klass.arel_attribute(attr).lower.public_send(order.downcase)
elsif order
klass.arel_attribute(attr).public_send(order.downcase)
else
klass.arel_attribute(attr)
end
end
end
end
Expand Down
28 changes: 28 additions & 0 deletions spec/requests/api/querying_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,34 @@ def create_vms_by_name(names)
expect_query_result(:vms, 2, 2)
expect_result_resources_to_match_hash([{"name" => "redhat_vm"}, {"name" => "vmware_vm"}])
end

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')
Copy link
Contributor

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?

vm_bar = FactoryGirl.create(:vm, :name => 'vm_bar')
host_foo.vms << vm_foo
host_bar.vms << vm_bar

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'}])
Copy link
Contributor

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.

Copy link
Member

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.

Copy link
Author

Choose a reason for hiding this comment

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

@abellotti will do!

end

it 'does not support non sql friendly virtual attributes' do
FactoryGirl.create(:vm)

run_get vms_url, :sort_by => 'aggressive_recommended_mem', :sort_order => 'asc'

expected = {
'error' => a_hash_including(
'message' => 'Vm cannot be sorted by aggressive_recommended_mem'
)
}
expect(response).to have_http_status(:bad_request)
expect(response.parsed_body).to include(expected)
end
end

describe "Filtering vms" do
Expand Down