app/routines/openstax/accounts/search_users.rb in openstax_accounts-0.3.0 vs app/routines/openstax/accounts/search_users.rb in openstax_accounts-1.0.0
- old
+ new
@@ -1,47 +1,189 @@
-require 'squeel'
+# Routine for searching for users
+#
+# Caller provides a query and some options. The query follows the rules of
+# https://github.com/bruce/keyword_search , e.g.:
+#
+# "username:jps,richb" --> returns the "jps" and "richb" users
+# "name:John" --> returns Users with first, last, or full name
+# starting with "John"
+#
+# Query terms can be combined, e.g. "username:jp first_name:john"
+#
+# There are currently two options to control query pagination:
+#
+# :per_page -- the max number of results to return per page (default: 20)
+# :page -- the zero-indexed page to return (default: 0)
+#
+# There is also an option to control the ordering:
+#
+# :order_by -- comma-separated list of fields to sort by, with an optional
+# space-separated sort direction (default: "username ASC")
+#
+# Finally, you can also specify a maximum allowed number of results:
+#
+# :max_matching_users -- the max number of results allowed (default: 10)
+#
+# This routine will return an empty relation if the number of results exceeds
+# max_matching_users. You can tell that this happened because the result will
+# have a non-zero num_matching_users.
module OpenStax
module Accounts
-
class SearchUsers
+
lev_routine transaction: :no_transaction
+
+ protected
+
+ SORTABLE_FIELDS = ['username', 'first_name', 'last_name', 'id']
+ SORT_ASCENDING = 'ASC'
+ SORT_DESCENDING = 'DESC'
- protected
+ def exec(query, options={})
- def exec(terms, type=:any)
- # Return empty results if no search terms
- return User.where{id == nil}.where{id != nil} if terms.blank?
+ if !OpenStax::Accounts.configuration.enable_stubbing? &&\
+ KeywordSearch.search(query).values_at('email', :default).compact.any?
+ # Delegate to Accounts
- # Note: % is the wildcard. This allows the user to search
- # for stuff that "begins with" but not "ends with".
- case type
- when :name
- users = User.scoped
- terms.gsub(/[%,]/, '').split.each do |t|
- next if t.blank?
- query = t + '%'
- users = users.where{(first_name =~ query) | (last_name =~ query)}
+ response = OpenStax::Accounts.application_users_index(query)
+
+ user_search = OpenStruct.new
+ search_rep = OpenStax::Accounts::Api::V1::UserSearchRepresenter.new(user_search)
+ search_rep.from_json(response.body)
+
+ # Need to query local database in order to obtain ID's (primary keys)
+ outputs[:users] = OpenStax::Accounts::User.where{
+ openstax_uid.in user_search.users.collect{ |u| u.openstax_uid }
+ }
+ outputs[:query] = user_search.q
+ outputs[:per_page] = user_search.per_page
+ outputs[:page] = user_search.page
+ outputs[:order_by] = user_search.order_by
+ outputs[:num_matching_users] = user_search.num_matching_users
+
+ else
+
+ # Local search
+ users = OpenStax::Accounts::User.scoped
+
+ KeywordSearch.search(query) do |with|
+
+ with.default_keyword :any
+
+ with.keyword :username do |usernames|
+ users = users.where{username.like_any my{prep_usernames(usernames)}}
+ end
+
+ with.keyword :first_name do |first_names|
+ users = users.where{lower(first_name).like_any my{prep_names(first_names)}}
+ end
+
+ with.keyword :last_name do |last_names|
+ users = users.where{lower(last_name).like_any my{prep_names(last_names)}}
+ end
+
+ with.keyword :full_name do |full_names|
+ users = users.where{lower(full_name).like_any my{prep_names(full_names)}}
+ end
+
+ with.keyword :name do |names|
+ names = prep_names(names)
+ users = users.where{ (lower(full_name).like_any names) |
+ (lower(last_name).like_any names) |
+ (lower(first_name).like_any names) }
+ end
+
+ with.keyword :id do |ids|
+ users = users.where{openstax_uid.in ids}
+ end
+
+ with.keyword :email do |emails|
+ users = OpenStax::Accounts::User.where('0=1')
+ end
+
+ # Rerun the queries above for 'any' terms (which are ones without a
+ # prefix).
+
+ with.keyword :any do |terms|
+ names = prep_names(terms)
+
+ users = users.where{
+ ( lower(username).like_any my{prep_usernames(terms)}) |
+ (lower(first_name).like_any names) |
+ (lower(last_name).like_any names) |
+ (lower(full_name).like_any names) |
+ (id.in terms)
+ }
+ end
+
end
- when :username
- query = terms.gsub('%', '') + '%'
- users = User.where{username =~ query}
- when :any
- users = User.scoped
- terms.gsub(/[%,]/, '').split.each do |t|
- next if t.blank?
- query = t + '%'
- users = users.where{(first_name =~ query) |
- (last_name =~ query) |
- (username =~ query)}
+
+ # Pagination
+
+ page = options[:page] || 0
+ per_page = options[:per_page] || 20
+
+ users = users.limit(per_page).offset(per_page*page)
+
+ #
+ # Ordering
+ #
+
+ # Parse the input
+ order_bys = (options[:order_by] || 'username').split(',').collect{|ob| ob.strip.split(' ')}
+
+ # Toss out bad input, provide default direction
+ order_bys = order_bys.collect do |order_by|
+ field, direction = order_by
+ next if !SORTABLE_FIELDS.include?(field)
+ direction ||= SORT_ASCENDING
+ next if direction != SORT_ASCENDING && direction != SORT_DESCENDING
+ [field, direction]
end
- else
- fatal_error(:unknown_user_search_type, data: type)
+
+ order_bys.compact!
+
+ # Use a default sort if none provided
+ order_bys = ['username', SORT_ASCENDING] if order_bys.empty?
+
+ # Convert to query style
+ order_bys = order_bys.collect{|order_by| "#{order_by[0]} #{order_by[1]}"}
+
+ # Make the ordering call
+ order_bys.each do |order_by|
+ users = users.order(order_by)
+ end
+
+ # Translate to routine outputs
+
+ outputs[:users] = users
+ outputs[:query] = query
+ outputs[:per_page] = per_page
+ outputs[:page] = page
+ outputs[:order_by] = order_bys.join(', ') # convert back to one string
+ outputs[:num_matching_users] = users.except(:offset, :limit, :order).count
+
end
- outputs[:users] = users
+ # Return no results if query exceeds maximum allowed number of matches
+ max_users = options[:max_matching_users] || \
+ OpenStax::Accounts.configuration.max_matching_users
+ outputs[:users] = OpenStax::Accounts::User.where('0=1') \
+ if outputs[:num_matching_users] > max_users
+
end
+
+ # Downcase, and put a wildcard at the end.
+ # For the moment don't exclude characters.
+ def prep_names(names)
+ names.collect{|name| name.downcase + '%'}
+ end
+
+ def prep_usernames(usernames)
+ usernames.collect{|username| username.gsub(OpenStax::Accounts::User::USERNAME_DISCARDED_CHAR_REGEX, '').downcase + '%'}
+ end
end
end
-end
\ No newline at end of file
+end