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

Refactor sort process into a service #189

Merged
merged 1 commit into from Dec 10, 2018
Merged

Conversation

elrayle
Copy link
Contributor

@elrayle elrayle commented Dec 6, 2018

The sort process is complex. The tests for deep_sort_service lays out the various potential sorting scenarios. Precedence on sort...

  • numeric sort, if both are integers
  • simple alpha sort of one term, if both are single value
  • same language list comparison, if all terms in a list are the same language or no language
  • preferred language comparison only, if both lists have the at least one term in the preferred language
  • calculate the comparator value for every language individually and sum to get a relative comparison of two multi-language lists (Best I could come up with. I am open to suggestions. In most cases, if not all, one of the earlier comparators will be selected. This is the junk drawer of multi-lingual sorting.)

@elrayle elrayle self-assigned this Dec 6, 2018
@@ -22,7 +22,7 @@ def initialize(url_config)

# Selective extract substitution variable-value pairs from the provided substitutions.
# @param [Hash, ActionController::Parameters] full set of passed in substitution values
# @returns [HashWithIndifferentAccess] Only variable-value pairs for variables defined in the variable mapping.
# @return [HashWithIndifferentAccess] Only variable-value pairs for variables defined in the variable mapping.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Multiple fixes to yarddoc @return statements. Oops ;)

Copy link
Contributor

@carolyncole carolyncole left a comment

Choose a reason for hiding this comment

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

@elrayle This is not a complete review. I'm suggesting some rejiggering of the code locations which I believe would make it easier to read and review. @randalldfloyd what are your thoughts?

That said it may be easier for you to make additional refactorings a separate step (or not at all).

def sort
return @sorted_array if @sorted_array.present?
presort_sortkey_values
@sorted_array = @the_array.sort do |a, b|
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if this would be easier to read if the array was first mapped to a deep sort element and then sorted. The setup comparison method is a little magic which makes it a smidge hard to read. It may also be more efficient since you are constructing the elements only once instead of at each comparison.

   sortable_elements = @the_array.map {|item|  DeepSortElement.new(item[sort_key)}
  @sorted_array = soratble_elements.sort do |a_element, b_element|
      if a_element.integer? && b_element.integer?
          numeric_comparator(a_element, b_element)
     elsif a_element.single? && b_element.single?
          single_value_comparator(a_element, b_element)
     else 
          multiple_value_comparator(a_element, b_element)
     end
  end

Or what could be even better is if the DeepSortElement knew how to compare itself with another element and this would read

   sortable_elements = @the_array.map {|item|  DeepSortElement.new(item[sort_key)}
  @sorted_array = soratble_elements.sort do |a_element, b_element|
       a_element.compare(b_element)
  end

You could then put a bunch of this code into the DeepSortElement class.

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 like that idea. I'll look at making that adjustment.

app/services/qa/linked_data/deep_sort_service.rb Outdated Show resolved Hide resolved

def presort_sortkey_values
the_array.map! do |element|
element[sort_key] = Qa::LinkedData::LanguageSortService.new(element[sort_key], preferred_language).sort
Copy link
Contributor

Choose a reason for hiding this comment

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

If the array was mapped to DeepSortElements you could take advantage of sorted_literals in that class here?

@randalldfloyd
Copy link
Contributor

@elrayle @Cam156 Just throwing in an initial response from a first pass...

Any custom sort is going to be complex so there's no way around that, but I was able to follow the logic and syntax for the most part. The way it is used and supposed to work is still eluding me a bit so I'm going to read the tests to see it in action and maybe that will help.

I was also trying to think if any of the heavy lifting or setup could be simplified by using the Comparable mixin. Not sure it would make it better or worse or just not applicable:

https://docs.ruby-lang.org/en/2.5.0/Comparable.html.
https://medium.com/@farsi_mehdi/the-comparable-mixin-8a3096fa1a0a
https://rubyplus.com/articles/2791-Ruby-Comparable-Basics

I don't have experience with it in Ruby but I've seen it in Java. I think the gist of it is that you get to supply behavior for comparison operators that objects are going to get for free anyway (or something like that) which could potentially simplify a layer of comparison setup.

@elrayle elrayle changed the title Refactor sort process into a service [WIP] Refactor sort process into a service Dec 10, 2018
@elrayle elrayle changed the title [WIP] Refactor sort process into a service Refactor sort process into a service Dec 10, 2018
@elrayle
Copy link
Contributor Author

elrayle commented Dec 10, 2018

@Cam156 @randalldfloyd Reworked DeepSortService to make better use of ruby <=> and comparators patterns.

@elrayle
Copy link
Contributor Author

elrayle commented Dec 10, 2018

@randalldfloyd I took a look at the links you provided. Thanks for adding those.

RE: Comparable mixin - The doc states "Comparable uses <=> to implement the conventional comparison operators (<, <=, ==, >=, and >) and the method between?." I don't think we need these extra methods so I didn't add the mixin.

@elrayle elrayle changed the title Refactor sort process into a service [WIP] Refactor sort process into a service Dec 10, 2018
@elrayle
Copy link
Contributor Author

elrayle commented Dec 10, 2018

I think I'm going to make one more change to this. It bothers me that DeepSortElement is a class that anyone can use since it is specific to the DeepSortService. I'm going to make it a private class using the approach described here... https://blog.arkency.com/2016/02/private-classes-in-ruby/

@elrayle elrayle changed the title [WIP] Refactor sort process into a service Refactor sort process into a service Dec 10, 2018
@elrayle
Copy link
Contributor Author

elrayle commented Dec 10, 2018

Done and ready for review.

Copy link
Contributor

@carolyncole carolyncole left a comment

Choose a reason for hiding this comment

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

@elrayle Just a couple simplifications that could be made. I like this version!


def includes_preferred_language?
return @has_preferred_language if @has_preferred_language.present?
filtered = filtered_literals(preferred_language)
Copy link
Contributor

Choose a reason for hiding this comment

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

Given the literals are sorted by language you could just test language == preferred_language since the first item will either be in the prefered language or it is not in the list at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


def all_same_language?
return @all_same_language if @all_same_language.present?
@all_same_language = true
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the literal list is sorted by language you should be able to compare language(0) == language(size-1) to see if they are all the same

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

end

def filtered_literals(filter_language)
filtered_literals_by_language.key?(filter_language) ? filtered_literals_by_language[filter_language] : []
Copy link
Contributor

Choose a reason for hiding this comment

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

this can be simplified into filtered_literals_by_language.fetch(filter_language,[]) see https://ruby-doc.org/core-2.5.1/Hash.html#method-i-fetch

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

bins = {}
0.upto(size - 1) do |idx|
lang = language(idx, literals)
filter = bins.key?(lang) ? bins[lang] : []
Copy link
Contributor

Choose a reason for hiding this comment

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

This can also be simplified by fetch filter = bins.fetch(lang, [])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor

@randalldfloyd randalldfloyd left a comment

Choose a reason for hiding this comment

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

This version is a lot easier to understand on a first read. Given the substantial tests showing it in action, any developer should be able to come into this cold and be able to debug/refactor/enhance without too much trouble if they take the time to digest it.

@elrayle
Copy link
Contributor Author

elrayle commented Dec 10, 2018

@Cam156 Requested changes are complete.

@carolyncole carolyncole merged commit 94f720f into master Dec 10, 2018
@carolyncole carolyncole deleted the ld_refactor_sort branch December 10, 2018 19:34
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

3 participants