Skip to content

Commit

Permalink
I think this is everything for the refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Howard M. Miller committed Sep 23, 2020
1 parent c4ca807 commit 9bb4c30
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 89 deletions.
13 changes: 12 additions & 1 deletion app/blueprints/filter_options_blueprint.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# FilterOptionsBlueprint emits a structure that the browse Vue page can turn
# into checkboxes or other form UI elements to filter a list of contributions
#
# We assume that there's a top level type of filter (e.g ContactMethodFilter)
# that then references a series of these FilterOptionBlueprint objects for
# each element (e.g. each available contact method)
class FilterOptionsBlueprint < Blueprinter::Base
# The identifier here needs to be in a format that the BrowseFilter can then
# interpret and use to filter results
identifier :id do |category, _options|
"#{category.class.to_s}[#{category.id}]"
"#{category.class}[#{category.id}]"
end

# This is currently used as a display name so people can understand
# which each option represents
field :name
end
17 changes: 5 additions & 12 deletions app/controllers/contributions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ class ContributionsController < ApplicationController

def index
@filter_types = BrowseFilter.filter_options_json
# The BrowserFilter takes the result of the parameters from the FilterType checkboxes and returns a list of contributions
filter = BrowseFilter.new(params, self)
# The BrowserFilter takes the result of the parameters that match the filter_type options and returns a list of contributions
filter = BrowseFilter.new(allowed_params, self)
@contributions = ContributionBlueprint.render(filter.contributions, **filter.options)
respond_to do |format|
format.html
Expand Down Expand Up @@ -48,17 +48,10 @@ def triage_update
end

private
#
# def filter_params
# return Hash.new unless allowed_params && allowed_params.to_h.any?
# allowed_params.to_h.filter { |key, _v| BrowseFilter::ALLOWED_PARAMS.keys.include? key}.tap do |hash|
# hash.keys.each { |key| hash[key] = hash[key].keys}
# end
# end

# def allowed_params
# @allowed_params ||= params.permit(:format, **BrowseFilter::ALLOWED_PARAMS)
# end
def allowed_params
params.permit(:format, BrowseFilter::ALLOWED_PARAMS_FILTER)
end

def set_contribution
@contribution = Listing.find(params[:id])
Expand Down
25 changes: 25 additions & 0 deletions app/filters/basic_filter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# This class has information about how we sort and filter categories
# and is capable of accepting a model scope and adding additional
# `where` clauses to it in order to filter the message

# BasicFilter is more like an abstract parent class, but it also serves
# as a NullObject version of the filter, if you want one
class BasicFilter
# This method should be overridden to return a hash with the following keys:
# * :name => a short string that the user will see that describes what type of filters these are
# * :filters => the output a call to FilterOptionsBlueprint.render_as_hash that represent each filter option to check or uncheck
def self.options
{}
end

attr_reader :parameters

def initialize(parameters)
@parameters = parameters
end

# By default, return the model scope unfiltered
def filter(scope)
scope
end
end
14 changes: 8 additions & 6 deletions app/filters/category_filter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class CategoryFilter
class CategoryFilter < BasicFilter
def self.options
{
name: "Categories",
Expand All @@ -7,9 +7,11 @@ def self.options
}
end

def self.filter(relation, parameters)
ids = parameters["Category"]
return relation if ids.blank?
relation.tagged_with(Category.roots.where(id: ids).pluck('name'), any: true)
def filter(scope)
return super unless parameters
scope.tagged_with(
Category.roots.where(id: parameters.keys).pluck('name'),
any: true
)
end
end
end
9 changes: 5 additions & 4 deletions app/filters/contact_method_filter.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
class ContactMethodFilter
class ContactMethodFilter < BasicFilter
def self.options
{
name: 'Contact Methods',
filters: FilterOptionsBlueprint.render_as_hash(ContactMethod.enabled.distinct(:name))
}
end

def self.filter(relation, _parameters)
relation
def filter(scope)
return super unless parameters
scope.joins(:person).where(people: { preferred_contact_method: parameters.keys })
end
end
end
14 changes: 12 additions & 2 deletions app/filters/contribution_type_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,18 @@ def self.options
{ id: 'ContributionType[Offer]', name: 'Offer' }
]}
end
ALL_ALLOWED_TYPES = ['Ask', 'Offer'].freeze

def self.filter(relation, _parameters)
relation
attr_reader :parameters

def initialize(params)
@parameters = params
end

def scopes
classes = parameters.blank? ? ALL_ALLOWED_TYPES : parameters.keys
classes.intersection(ALL_ALLOWED_TYPES).map do |type|
type.constantize.matchable
end
end
end
9 changes: 5 additions & 4 deletions app/filters/service_area_filter.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
class ServiceAreaFilter
class ServiceAreaFilter < BasicFilter
def self.options
{
name: 'Service Areas',
filters: FilterOptionsBlueprint.render_as_hash(ServiceArea.i18n)
}
end

def self.filter(relation, _parameters)
relation
def filter(scope)
return super unless parameters
scope.where(service_area_id: parameters.keys)
end
end
end
71 changes: 31 additions & 40 deletions app/models/browse_filter.rb
Original file line number Diff line number Diff line change
@@ -1,60 +1,51 @@
# BrowseFilter code currently makes a lot of assumptions about the model coming in
# I'm going to have to some lifting to make it accept other models
# but at least all these changes will be isolated to this class
# BrowseFilter lets the browse page filter contributions by parameters, delegating much of the actual filtering
# to the classes listed in BrowseFilter::FILTER_CLASSES
class BrowseFilter
FILTERS = [ContributionTypeFilter, CategoryFilter, ServiceAreaFilter, ContactMethodFilter]
# FILTERS = {
# 'ServiceArea' => ->(ids, scope) { scope.where(service_area: ids) },
# 'ContactMethod' => ->(ids, scope) { scope.joins(:person).where(people: {preferred_contact_method: ids})},
# 'Category' => lambda do |ids, scope|
# scope.tagged_with(
# Category.roots.where(id: ids).pluck('name'),
# any: true
# )
# end,
# 'UrgencyLevel' => lambda do |ids, scope|
# ids.length == UrgencyLevel::TYPES.length ? scope : scope.where(urgency_level_id: ids)
# end
# }.freeze

ALLOWED_MODEL_NAMES = ['Ask', 'Offer'].freeze
# ALLOWED_PARAMS = (['ContributionType'] + FILTERS.keys).each_with_object({}) do |key, hash|
# hash[key] = {}
# end.freeze
FILTERS = {
'Category' => CategoryFilter,
'ServiceArea' => ServiceAreaFilter,
'ContactMethod' => ContactMethodFilter,
'ContributionType' => ContributionTypeFilter
# 'UrgencyLevel' => UrgencyLevelFilter
}.freeze
FILTER_CLASSES = FILTERS.values.freeze
ALLOWED_PARAMS_FILTER = FILTERS.keys.each_with_object({}) do |key, hash|
hash[key] = {}
end.freeze

attr_reader :parameters, :context

def self.filter_options_json
FILTERS.map(&:options).to_json
FILTER_CLASSES.map(&:options).to_json
end

def initialize(parameters, context = nil)
@parameters = parameters
@context = context
end

# Return a filtered array of contributions
def contributions
model_names = parameters['ContributionType']&.keys || ALLOWED_MODEL_NAMES
models = model_names.intersection(ALLOWED_MODEL_NAMES).map(&:constantize)
@contributions ||= models.map { |model| filters(model) }.flatten
# ContributionTypeFilter is special and needs to come first because of Single Table Inheretence
# Currently, the only other option seemed to be pulling in a gem that supports UNION queries in SQL
starting_relations = ContributionTypeFilter.new(parameters['ContributionType']).scopes

# So using whatever relations ContributionTypeFilter gives us (unmatched asks, unmatched offers, etc.)
@contributions ||= FILTERS.reduce(starting_relations) do |resulting_relations, (filter_name, klass)|
# Skip ContributionTypeFilter because we've already used it
next resulting_relations if klass == ContributionTypeFilter

# then filter the relations further based on the rules in each class for doing so
resulting_relations.map do |scope|
klass.new(parameters[filter_name]).filter(scope)
end
end.flatten
end

# These options are for the blueprint so it can add appropriate links to the json for each contribution
def options
return {} unless context

{ respond_path: ->(id) { context.respond_contribution_path(id)} }
end

private

def filter(model)
# parameters.keys.reduce(model.unmatched) do |scope, key|
# filter = FILTERS.fetch(key, ->(_condition, s) {s})
# filter.call(parameters[key], scope)
# end

FILTERS.reduce(model.unmatched) do |relation, filter|
filter.filter(relation, parameters)
end
{ respond_path: ->(id) { context.respond_contribution_path(id) } }
end
end
13 changes: 0 additions & 13 deletions app/models/contribution_type.rb

This file was deleted.

1 change: 0 additions & 1 deletion app/models/service_area.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ class ServiceArea < ApplicationRecord
where("mobility_string_translations.key = 'name' AND mobility_string_translations.locale = 'en'").
where("LOWER(mobility_string_translations.value) = ?", name) }

scope :as_filter_types, -> { i18n.select :id, :name }
scope :publicly_visible, -> { where(display_to_public: true) }

def full_name
Expand Down
7 changes: 1 addition & 6 deletions app/models/urgency_level.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,13 @@
class UrgencyLevel
TYPES = [new(1, 'Within 1-2 days'), new(2, 'Within 3-5 days'), new(3, 'About a week'), new(4, 'Anytime')].freeze

def self.as_filter_types
TYPES
end

def self.find(id)
return unless id
TYPES[id-1]
TYPES[id - 1]
end

def self.where(id: [])
ids = [id].flatten
ids.map {|i| TYPES.select {|t| t.id == i }}
end

end

0 comments on commit 9bb4c30

Please sign in to comment.