diff --git a/Makefile b/Makefile index 872316a4..386d39e7 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,10 @@ db_migrate: db_rollback: docker-compose run ${RAILS_CONTAINER} rake db:rollback +.PHONY: db_seed +db_seed: + docker-compose run ${RAILS_CONTAINER} rake db:seed + .PHONY: test test: bg docker-compose run operationcode-psql bash -c "while ! psql --host=operationcode-psql --username=postgres -c 'SELECT 1'; do sleep 5; done;" diff --git a/apiary.apib b/apiary.apib index e170bf56..62255f26 100644 --- a/apiary.apib +++ b/apiary.apib @@ -393,12 +393,13 @@ API endpoints that Operation Code's Rails backend makes available to its React f errors: "Some error message" } -## User | Count By Location [/api/v1/users/by_location{?zip,city,lat_long,radius}] +## User | Count By Location [/api/v1/users/by_location{?state,zip,city,lat_long,radius}] ### By Location [GET] + Parameters + + state (string, optional) - String of comma-separated state abbreviations, i.e. 'TX', or 'TX, CA' + zip (string, optional) - String of comma-separated zip code(s), i.e. '80126', or '80126, 80203' + city (string, optional) - A string of a 'City, State, County' (i.e. 'Denver, CO, US') + lat_long (integer, optional) - Comma-separated integer latitude and longitude, i.e. 30.285648, -97.742052 @@ -408,6 +409,12 @@ API endpoints that Operation Code's Rails backend makes available to its React f + Body + { + state: 'TX, CA' + } + + /*or*/ + { zip: '80126, 80203' } diff --git a/app/lib/format_data.rb b/app/lib/format_data.rb new file mode 100644 index 00000000..e2f23e57 --- /dev/null +++ b/app/lib/format_data.rb @@ -0,0 +1,11 @@ +class FormatData + + # Converts a comma-separated string into an array of its contents. + # + # @param comma_separated_string [String] String of comma-separated values, i.e '80123, 80202' + # @return [Array] An array of strings, i.e. ['80123', '80202'] + # + def self.csv_to_array(comma_separated_string) + comma_separated_string.split(',').map(&:strip) + end +end diff --git a/app/lib/users_by_location.rb b/app/lib/users_by_location.rb index 96ea34c4..af6d0813 100644 --- a/app/lib/users_by_location.rb +++ b/app/lib/users_by_location.rb @@ -1,5 +1,6 @@ class UsersByLocation def initialize(params) + @state = params[:state] @zip = params[:zip] @city = params[:city] @lat_long = params[:lat_long] @@ -7,7 +8,9 @@ def initialize(params) end def count - if @zip.present? + if @state.present? + User.count_by_state @state + elsif @zip.present? User.count_by_zip @zip elsif @city.present? User.count_by_location @city, @radius diff --git a/app/models/user.rb b/app/models/user.rb index 16c76b5b..1cf4202e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -29,17 +29,32 @@ class User < ApplicationRecord scope :mentors, -> { where(mentor: true) } scope :by_zip, ->(zip) { where(zip: zip) } + scope :by_state, ->(state) { where(state: state) } # Returns a count of all users with the passed in zip code(s) # # @param zip_codes [String] String of comma-separated zip code(s), i.e. '80126', or '80126, 80203' + # @return [Integer] A count of the requested users. # def self.count_by_zip(zip_codes) return 0 unless zip_codes.present? - zips = zip_codes.split(',').map(&:strip) + zips = FormatData.csv_to_array(zip_codes) - by_zip(zips).count + self.by_zip(zips).count + end + + # Returns a count of all users with the passed in state abbreviations. + # + # @param state_abbreviations [String] String of comma-separated state_abbreviations, i.e. 'CO', or 'CO, TX' + # @return [Integer] A count of the requested users. + # + def self.count_by_state(state_abbreviations) + return 0 unless state_abbreviations.present? + + states = FormatData.csv_to_array(state_abbreviations) + + self.by_state(states).count end # Returns a count of all users within the passed in location. The location can @@ -48,6 +63,7 @@ def self.count_by_zip(zip_codes) # @param location [String || Array] Either a 'City, State, County' or [Latitude, Longitude] # For example, 'Denver, CO, US'. # @param radius [Integer] Include results within the radius' distance from the location, in miles. + # @return [Integer] A count of the requested users. # @see https://github.com/alexreisner/geocoder#for-activerecord-models # def self.count_by_location(location, radius=20) diff --git a/test/controllers/api/v1/users_controller_test.rb b/test/controllers/api/v1/users_controller_test.rb index c5f0b2e2..19ed1b32 100644 --- a/test/controllers/api/v1/users_controller_test.rb +++ b/test/controllers/api/v1/users_controller_test.rb @@ -11,11 +11,16 @@ class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest end test ":by_location returns User.count of users located in the passed in location" do - tom = create :user, zip: '78705' - sam = create :user, zip: '78756' + tom = create :user + sam = create :user - tom.update latitude: 30.285648, longitude: -97.742052 - sam.update latitude: 30.312601, longitude: -97.738591 + tom.update_columns latitude: 30.285648, longitude: -97.742052, zip: '78705', state: 'TX' + sam.update_columns latitude: 30.312601, longitude: -97.738591, zip: '78756', state: 'TX' + + params = { state: 'TX' } + get api_v1_users_by_location_url(params), headers: @headers, as: :json + assert_equal({ 'user_count' => 2 }, response.parsed_body) + assert_equal 200, response.status params = { zip: '78705' } get api_v1_users_by_location_url(params), as: :json @@ -25,13 +30,16 @@ class Api::V1::UsersControllerTest < ActionDispatch::IntegrationTest params = { zip: '78705, 78756' } get api_v1_users_by_location_url(params), as: :json assert_equal({ 'user_count' => 2 }, response.parsed_body) + assert_equal 200, response.status params = { lat_long: [30.285648, -97.742052] } get api_v1_users_by_location_url(params), as: :json assert_equal({ 'user_count' => 2 }, response.parsed_body) + assert_equal 200, response.status params = { lat_long: [30.285648, -97.742052], radius: 1 } get api_v1_users_by_location_url(params), as: :json assert_equal({ 'user_count' => 1 }, response.parsed_body) + assert_equal 200, response.status end end diff --git a/test/lib/format_data_test.rb b/test/lib/format_data_test.rb new file mode 100644 index 00000000..e103c50d --- /dev/null +++ b/test/lib/format_data_test.rb @@ -0,0 +1,14 @@ +require 'test_helper' + +class FormatDataTest < ActiveSupport::TestCase + test '.csv_to_array' do + results = FormatData.csv_to_array '80202' + assert_equal ['80202'], results + + results = FormatData.csv_to_array '80202, 80126' + assert_equal ['80202', '80126'], results + + results = FormatData.csv_to_array 'TX, MN' + assert_equal ['TX', 'MN'], results + end +end diff --git a/test/lib/users_by_location_test.rb b/test/lib/users_by_location_test.rb new file mode 100644 index 00000000..bd0bb24a --- /dev/null +++ b/test/lib/users_by_location_test.rb @@ -0,0 +1,50 @@ +require 'test_helper' + +class UsersByLocationTest < ActiveSupport::TestCase + setup do + tom = create :user + sam = create :user + + tom.update_columns latitude: 39.763034, longitude: -104.961969, zip: '80205', state: 'CO' + sam.update_columns latitude: 30.312601, longitude: -97.738591, zip: '78756', state: 'TX' + end + + test '#count by state' do + params = { state: 'CO' } + results = UsersByLocation.new(params).count + assert_equal 1, results + + params = { state: 'TX' } + results = UsersByLocation.new(params).count + assert_equal 1, results + + params = { state: 'CO, TX' } + results = UsersByLocation.new(params).count + assert_equal 2, results + end + + test '#count by zip' do + params = { zip: '80205' } + results = UsersByLocation.new(params).count + assert_equal 1, results + + params = { zip: '78756' } + results = UsersByLocation.new(params).count + assert_equal 1, results + + params = { zip: '80205, 78756' } + results = UsersByLocation.new(params).count + assert_equal 2, results + end + + test '#count by lat_long' do + params = { lat_long: [39.763034, -104.961969] } + results = UsersByLocation.new(params).count + assert_equal 1, results + + params = { lat_long: [30.312601, -97.738591] } + results = UsersByLocation.new(params).count + assert_equal 1, results + end +end + diff --git a/test/models/user_test.rb b/test/models/user_test.rb index e0d88242..2cfa11fa 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -124,4 +124,23 @@ def stub_geocoder test 'it returns a users full name' do assert_equal 'first last', User.new(first_name: 'first', last_name: 'last').name end + + test '.count_by_state returns a count of all users within the passed in state(s)' do + tom = create :user + sam = create :user + bob = create :user + + tom.update_columns state: 'TX' + sam.update_columns state: 'TX' + bob.update_columns state: 'CA' + + results = User.count_by_state 'TX' + assert_equal 2, results + + results = User.count_by_state 'TX, CA' + assert_equal 3, results + + results = User.count_by_state '' + assert_equal 0, results + end end