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

Hxl stats view 145 147 #180

Merged
merged 26 commits into from
Aug 28, 2017
Merged

Hxl stats view 145 147 #180

merged 26 commits into from
Aug 28, 2017

Conversation

leungant
Copy link

@leungant leungant commented Jun 4, 2017

Change description:

Addresses #145 and #147:

http://.../hxlstats and http://.../hxlstats.json now show array of arrays conforming to Google spreadsheet

Any pointers/assistance much appreciated as I am a bit unfamiliar with the testing requirements.

Instructions:

rake db:migrate

or

rake db:migrate:up VERSION=20170601162837

will bring VERSION up to 20170601162837

The view is available at http://.../hxlstats.json

Sample output

.json:

[["Emotional State", "Stage of journey", "Country drawn in", "Total children affected", "Children who identify as female", "Children who identify as male", "Children who identify as neither female nor male", "Children between the ages of 5-12", "Children between the ages of 13-18", "Children under 5 years old", "Older than 18 years old"],
 ["#impact+indicator+code", "#affected+children", "#country+code", "#affected+children+total", "#affected+children+female", "#affected+children+male", "#affected+children+indicator", "#affected+children+age_5_12", "#affected+children+age_13_18", "#affected+children+age_under5", "#affected+children+age_18plus", "#affected+children+age_unknown"], 
[1, "At home", "AF", 1, 0, 0, 1, 0, 0, 1, 0, 1], 
[3, "At home", "AF", 2, 0, 1, 1, 0, 0, 2, 0, 2], 
[3, "In temporary shelter", "AF", 1, 0, 1, 0, 0, 0, 1, 0, 2], 
[3, "At home", "FR", 1, 0, 0, 1, 1, 0, 0, 0, 1], 
[2, "In temporary shelter", "AF", 1, 0, 1, 0, 1, 0, 0, 0, 1]]

image

Copy link
Member

@krissy krissy left a comment

Choose a reason for hiding this comment

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

Looking great so far @leungant, and local tests of results are exciting! 🕺

Output is missing a single but quite key row, i.e. the HXL hashtags, but that should be a quick fix!

Also some notes on how we can refactor this a bit and make you fight with Rubocop less. Please bug me if you'd like to go through anything together, pair or delegate some tasks to me. Thanks again for jumping on this so quick! :D

@@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
Copy link
Member

Choose a reason for hiding this comment

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

Pretty sure these were scaffolded so we can get rid of any unused files. Will mark any below we can remove with an amazing hashtag like #ScaffoldScrap! ✌️

Copy link
Author

Choose a reason for hiding this comment

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

removed!

@@ -0,0 +1,3 @@
// Place all the styles related to the hxlstats controller here.
Copy link
Member

Choose a reason for hiding this comment

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

#ScaffoldScrap

Copy link
Author

Choose a reason for hiding this comment

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

removed!

@@ -0,0 +1,69 @@
class HxlstatsController < ApplicationController
# Class to collect and aggregate data to parse to the HXL proxy/endpoint
def new
Copy link
Member

Choose a reason for hiding this comment

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

#ScaffoldScrap the new, create and index methods

Copy link
Author

Choose a reason for hiding this comment

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

removed!

@@ -0,0 +1,13 @@
class HxlstatsDecorator < Draper::Decorator
Copy link
Member

Choose a reason for hiding this comment

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

#ScaffoldScrap

Copy link
Author

Choose a reason for hiding this comment

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

ok!

@@ -0,0 +1,2 @@
module HxlstatsHelper
Copy link
Member

Choose a reason for hiding this comment

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

#ScaffoldScrap

Copy link
Author

Choose a reason for hiding this comment

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

ok!

@@ -0,0 +1,11 @@
<%# country moodrating stage_of_journey count gender1 gender2 gender3 age1 age2 age3 age4 %>
Copy link
Member

Choose a reason for hiding this comment

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

Can remove comments

Copy link
Author

Choose a reason for hiding this comment

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

removed

true
end

def self.to_csv # Not actually used! But could be convenient for data analysts. was %w{ } but rubocop!
Copy link
Member

Choose a reason for hiding this comment

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

Let's remove this one. Ideally for any of our HDX integrations, this app will only act as an input source for the HXL Proxy, and any exports / filtering / analysis will occur in the HXL Proxy itself. Better to keep things minimal rather than keep methods in that most likely won't be used but would still need maintenance with view changes etc.

Copy link
Author

Choose a reason for hiding this comment

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

removed!

@@ -0,0 +1,23 @@
<h1> hello </h1>
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if this is used for anything? If not, we can remove too.

Copy link
Author

@leungant leungant Jun 10, 2017

Choose a reason for hiding this comment

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

removed!


# rubocop:disable MethodLength
def show
# Prepare output results array
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for working all this grouping logic out! Tested locally and so exciting to finally have an endpoint with data.
👏

A couple of suggestions for cleaning it up a little bit, keeping Rubocop happy and also making testing a bit easier:

  1. Move everything from line 12 to 59, i.e. creating the results array, out of the controller. One suggestion is making it a public class method in the HxlStatsView model, like results_by_emotional_state. Or another option is to create a new service object / Plain Old Ruby Object (PORO) like drawings_by_emotional_state_query in a new directory like app/models/queries, to save from bloating the model also.

  2. Once the logic is extracted, you can extract each block below into its own private method, which will be easier to read / maintain / identify the purpose of each block by explicit method naming - this last bit also makes it less necessary to feel like you need to prefix comments on each block, as the methods should tell the tale. AND this will make you fight with Rubocop less and remove the need for # rubocop:disable MethodLength I see stuck up there :)

  3. Once the logic is extracted outside of the controller, testing becomes a bit clearer/easier. For controller specs (not detrimental if these are missing btw), you can set up just a couple of tests that focus on asserting the response of the show method (i.e. returns html if normal request, returns data if json specified) and just stub the value of the results generation method. Then you can focus on testing the actual results generation in the model / service object, using FactoryGirl for HxlStatsView.

As always, buzz or let me know if you'd like to go through this or pair!

Copy link
Author

Choose a reason for hiding this comment

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

Mostly done, in the most minimal way possible ;) thanks for the comments!

def show
# Prepare output results array
@results = []
@results.append(["Emotional State", "Stage of journey", "Country drawn in", "Total children affected",
Copy link
Member

Choose a reason for hiding this comment

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

As well as these headings, we need another row with the HXL tagged headings (e.g. #impact +indicator +code) as per the spreadsheet. Both the headings and HXL headings can probably live as constants in the HxlStatsView model, or service object as suggested above.

Copy link
Author

Choose a reason for hiding this comment

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

ok!

Copy link
Author

@leungant leungant Jun 10, 2017

Choose a reason for hiding this comment

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

When to have spaces in the tags and when not?

Deferring named constants to when we start using in multiple places.

@leungant
Copy link
Author

leungant commented Jun 11, 2017

Appreciate any further thoughts!

Copy link
Member

@krissy krissy left a comment

Choose a reason for hiding this comment

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

Great! Have added a few more comments. Let me know if you'd like to go over anything. 🍍

@@ -0,0 +1,2 @@
%h1 Hxlstats#new
Copy link
Member

Choose a reason for hiding this comment

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

We can remove this too as we don't have a new route set up anymore.

@@ -0,0 +1,109 @@
HXLSTATS_COLUMN_HEADERS =
Copy link
Member

Choose a reason for hiding this comment

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

Any reason line 1 - 66 are outside of the class? Let's pop them back in. The constants (in this case HXLSTATS_COLUMN_HEADERS and HXL_STATS_TAGS) usually would live right at the top of the class (but inside it), and the methods get_counts_by_gender and get_counts_by_age can be private methods within the class. I.e. stick them under the keyword private.

def get_counts_by_gender(hxlstatsmajorgroup)
# Group by gender and aggregate
gender_totals = Hash.new(0)
@hxlstatsgroupsgender = hxlstatsmajorgroup.group_by(&:gender) # shorthand for { |hxlstat| hxlstat.gender }
Copy link
Member

Choose a reason for hiding this comment

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

No need for an instance variable here as it will only be used locally within the method. So @hxlstatsgroupsgender -> hxlstatsgroupsgender

# rubocop:disable MethodLength
def get_counts_by_age(hxlstatsmajorgroup)
# Processing Age aggregations
@hxlstatsgroupsage = hxlstatsmajorgroup.group_by(&:age) # shorthand for { |hxlstat| hxlstat.age }
Copy link
Member

Choose a reason for hiding this comment

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

No need for an instance variable here as it will only be used locally within the method. So @hxlstatsgroupsage -> hxlstatsgroupsage


def self.hxl_stats_counts
results = []
@hxlstats = HxlStatsView.all
Copy link
Member

Choose a reason for hiding this comment

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

Same re replacing instance variable with local variable - @hxlstats -> hxlstats

@hxlstats = HxlStatsView.all

# First group by major categories, before aggregating independently for gender and age and recombining
@hxlstatsgroups = @hxlstats.group_by { |hxlstat| [hxlstat.mood_rating, hxlstat.stage_of_journey, hxlstat.country] }
Copy link
Member

Choose a reason for hiding this comment

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

Same re replacing instance variable with local variable - @hxlstatsgroups -> hxlstatsgroups

end

def self.results_by_emotional_state
@results = []
Copy link
Member

Choose a reason for hiding this comment

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

Same re replacing instance variable with local variable - @results -> results.

true
end

@gender_lookup = { 'male' => 0,
Copy link
Member

Choose a reason for hiding this comment

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

We already conveniently have the gender mapping available as an enum in the Drawing model. We can reference that in the hxl_stats_counts method rather than create a new hash here to avoid repetition.

I.e.

Drawing.genders

# Response => {"not_specified"=>0, "female"=>1, "male"=>2, "other"=>3}

Drawing.genders["female"]

# Response => 1

What value does hxl_stats_counts return for drawings with 'other' i.e. 3? We might need to update the logic so anything with 0 or 3 falls into 'other' for now.

"#affected+children+age_under5",
"#affected+children+age_18plus"].freeze

private_class_method :get_counts_by_gender
Copy link
Member

Choose a reason for hiding this comment

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

Ah the 'private class method' debacle! Yeah, wish this was a bit less painful in Rails.

We can use the private_class_method option, however this needs to be declared after the methods themselves or Rails complains. A second, think less PITA option, is to use: class << self around all of the methods. It's usually clearer to use the def self.method_name style to define class methods, however when all of the methods within a class are public, wrapping everything in class << self is fine and more readable. Also, this way you can use the private keyword as normal.

I.e.

class << self
  def results_by_emotional_state
    ...
  end

  private

  def hxl_stats_counts
    ...
  end

  def get_counts_by_gender(hxlstatsmajorgroup)
    ...
  end

  def get_counts_by_age(hxlstatsmajorgroup)
    ...
  end
end

gender_totals # hash of key gender to v counts
end

# rubocop:disable MethodLength
Copy link
Member

Choose a reason for hiding this comment

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

Rubocop doesn't need this methodlength exception for this method.

Also, you can run rubocop locally without having to wait for CI to build. Just cd into the root of the project and type rubocop. Or you can do it for a specific file by typing the path after it.

@@ -0,0 +1,24 @@
class CreateHxlStatsView < ActiveRecord::Migration
def up
execute <<-SQL
Copy link
Member

Choose a reason for hiding this comment

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

Run locally, some rubocop bits:

db/migrate/20170601162837_create_hxl_stats_view.rb:2:9: C: Trailing whitespace detected.
  def up
        ^
db/migrate/20170601162837_create_hxl_stats_view.rb:5:33: C: Trailing whitespace detected.
      SELECT d.id AS drawing_id,
                                ^
db/migrate/20170601162837_create_hxl_stats_view.rb:8:29: C: Trailing whitespace detected.
             o.id AS org_id,
                            ^^
db/migrate/20170601162837_create_hxl_stats_view.rb:11:29: C: Trailing whitespace detected.
                    users u,
                            ^
db/migrate/20170601162837_create_hxl_stats_view.rb:24:1: C: 1 trailing blank lines detected.

@krissy
Copy link
Member

krissy commented Aug 28, 2017

@leungant I reviewed the last couple of changes and QA'ed them locally. Had to add an extra 'unknown ages' column to the spreadsheet and HXL logic as age isn't compulsory in the form. All else looking fine so will get this merged!

@krissy krissy merged commit 5775c18 into master Aug 28, 2017
@krissy krissy deleted the hxl_stats_view-145-147 branch August 28, 2017 11:40
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.

2 participants