openstax_utilities 4.1.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/app/routines/openstax/utilities/limit_and_paginate_relation.rb +85 -0
  3. data/app/routines/openstax/utilities/order_relation.rb +105 -0
  4. data/app/routines/openstax/utilities/search_and_organize_relation.rb +130 -0
  5. data/app/routines/openstax/utilities/search_relation.rb +94 -0
  6. data/lib/openstax/utilities/access_policy.rb +4 -3
  7. data/lib/openstax/utilities/assets.rb +29 -0
  8. data/lib/openstax/utilities/assets/manifest.rb +62 -0
  9. data/lib/openstax/utilities/version.rb +1 -1
  10. data/lib/openstax_utilities.rb +5 -5
  11. data/spec/cassettes/OpenStax_Utilities_Assets/loading_remote_manifest/uses_remote_json.yml +353 -0
  12. data/spec/dummy/app/access_policies/dummier_access_policy.rb +10 -0
  13. data/spec/dummy/app/assets/config/manifest.js +3 -0
  14. data/spec/dummy/config/application.rb +6 -11
  15. data/spec/dummy/config/environments/test.rb +1 -1
  16. data/spec/dummy/config/initializers/search_users.rb +26 -0
  17. data/spec/dummy/config/secrets.yml +2 -5
  18. data/spec/dummy/db/test.sqlite3 +0 -0
  19. data/spec/dummy/log/test.log +32144 -0
  20. data/spec/dummy/tmp/cache/C09/760/6da7b2a29da9cb0f80ef102c7effb91fab3374db +0 -0
  21. data/spec/factories/user.rb +1 -1
  22. data/spec/lib/openstax/utilities/access_policy_spec.rb +16 -15
  23. data/spec/lib/openstax/utilities/assets_spec.rb +40 -0
  24. data/spec/rails_helper.rb +1 -2
  25. data/spec/routines/openstax/utilities/limit_and_paginate_relation_spec.rb +72 -0
  26. data/spec/routines/openstax/utilities/order_relation_spec.rb +55 -0
  27. data/spec/routines/openstax/utilities/{abstract_keyword_search_routine_spec.rb → search_and_organize_relation_spec.rb} +54 -47
  28. data/spec/routines/openstax/utilities/search_relation_spec.rb +81 -0
  29. data/spec/vcr_helper.rb +18 -0
  30. metadata +123 -44
  31. data/app/handlers/openstax/utilities/keyword_search_handler.rb +0 -95
  32. data/app/routines/openstax/utilities/abstract_keyword_search_routine.rb +0 -158
  33. data/spec/dummy/app/routines/search_users.rb +0 -21
  34. data/spec/handlers/openstax/utilities/keyword_search_handler_spec.rb +0 -126
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5f74a54eb979770038bb0b7a22dd9ab49c7d0bd7
4
- data.tar.gz: 24be3a1eba292de415db675f55c93fcb127bf693
2
+ SHA256:
3
+ metadata.gz: fc7366a73ada7f334bd42ca06e41c0043f92e0d2aff08969f5ec1759275b1885
4
+ data.tar.gz: 86fa1a1dbe2f4c8521afb1b6ce635b63c8c2140e6656de3028887fd22d7872d1
5
5
  SHA512:
6
- metadata.gz: 3cd0d23114ea4b34234afab28e67ee749af4150a50510fc85c12ec740a10a45518be29405ca8a350ee713eda378850e205394207d3d09ee74ae4c68ab6fac132
7
- data.tar.gz: 77855c34ee526501c39e723164036aa2af493cb473fb4f7c68b481795844641ca6170ed8da1f39a5c6b5a15cdd99bd42ebce9b5d7f379823330f2209bc6598e8
6
+ metadata.gz: 26555b0e6c39c6cc7c3615d6e948849cce15aa9ff67ebafb5fad260ddb408a8003598b2e588f10ea1c2ed55256ade08a223ad92517b8fcc45adfeb8976583322
7
+ data.tar.gz: c36765470b6f808e37e2145538b49b56312568ea9ddc73f8329c94e7eb593ecaa8fa5bd09f999db6e7deda74a6792a8c85fdfa4bd2e850a63239de85fbaebd4d
@@ -0,0 +1,85 @@
1
+ # Database-agnostic search result limiting and pagination routine
2
+ #
3
+ # Counts the number of items in a relation and empties it
4
+ # if the number exceeds the specified absolute maximum
5
+ # Otherwise, applies the specified pagination
6
+ #
7
+ # Callers of this routine provide the relation argument and
8
+ # may provide the max_items, per_page and page arguments
9
+ #
10
+ # Required arguments:
11
+ #
12
+ # Developer-supplied:
13
+ #
14
+ # relation - the ActiveRecord::Relation to be limited or paginated
15
+ #
16
+ # Optional arguments:
17
+ #
18
+ # Developer-supplied:
19
+ #
20
+ # max_items - the maximum allowed number of search results
21
+ # default: nil (disabled)
22
+ #
23
+ # User or developer-supplied:
24
+ #
25
+ # per_page - the maximum number of search results per page
26
+ # default: nil (disabled)
27
+ # page - the number of the page to return
28
+ # default: 1
29
+ #
30
+ # This routine's outputs contain:
31
+ #
32
+ # outputs[:total_count] - the total number of items in the relation
33
+ # outputs[:items] - the original relation after it has
34
+ # potentially been emptied or paginated
35
+
36
+ require 'lev'
37
+
38
+ module OpenStax
39
+ module Utilities
40
+ class LimitAndPaginateRelation
41
+
42
+ lev_routine transaction: :no_transaction
43
+
44
+ protected
45
+
46
+ def exec(*args)
47
+
48
+ options = args.last.is_a?(Hash) ? args.pop : {}
49
+ relation = options[:relation] || args[0]
50
+ max_items = options[:max_items] || nil
51
+ per_page = Integer(options[:per_page]) rescue nil
52
+ page = Integer(options[:page]) rescue 1
53
+
54
+ raise ArgumentError, 'You must specify a :relation option' \
55
+ if relation.nil?
56
+
57
+ fatal_error(offending_inputs: :per_page,
58
+ message: 'Invalid page size',
59
+ code: :invalid_per_page) if !per_page.nil? && per_page < 1
60
+ fatal_error(offending_inputs: :page,
61
+ message: 'Invalid page number',
62
+ code: :invalid_page) if page < 1
63
+
64
+ outputs[:total_count] = relation.count
65
+
66
+ if !max_items.nil? && outputs[:total_count] > max_items
67
+ # Limiting
68
+ relation = relation.none
69
+ nonfatal_error(code: :too_many_items,
70
+ message: "The number of matches exceeded the allowed limit of #{
71
+ max_items} matches. Please refine your query and try again.")
72
+ elsif per_page.nil?
73
+ relation = relation.none if page > 1
74
+ else
75
+ # Pagination
76
+ relation = relation.limit(per_page).offset(per_page*(page-1))
77
+ end
78
+
79
+ outputs[:items] = relation
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,105 @@
1
+ # Database-agnostic search result ordering routine
2
+ #
3
+ # Performs ordering of search results
4
+ #
5
+ # Callers of this routine provide the relation,
6
+ # sortable_fields and order_by arguments
7
+ #
8
+ # Required arguments:
9
+ #
10
+ # Developer-supplied:
11
+ #
12
+ # relation - the ActiveRecord::Relation to be ordered
13
+ #
14
+ # sortable_fields - list of fields that can appear in the order_by argument
15
+ # can be a Hash that maps field names to database columns
16
+ # or an Array of Strings
17
+ # invalid fields in order_by will be replaced with
18
+ # the first field listed here, in :asc order
19
+ #
20
+ # Optional arguments:
21
+ #
22
+ # User or developer-supplied:
23
+ #
24
+ # order_by - list of fields to order by, with optional sort directions
25
+ # can be (an Array of) Hashes, or Strings
26
+ # default: {sortable_fields.values.first => :asc}
27
+ #
28
+ # This routine's outputs contain:
29
+ #
30
+ # outputs[:items] - a relation containing the ordered records
31
+
32
+ require 'lev'
33
+
34
+ module OpenStax
35
+ module Utilities
36
+ class OrderRelation
37
+
38
+ lev_routine transaction: :no_transaction
39
+
40
+ protected
41
+
42
+ def exec(*args)
43
+ options = args.last.is_a?(Hash) ? args.pop : {}
44
+ relation = options[:relation] || args[0]
45
+ sortable_fields = options[:sortable_fields] || args[1]
46
+ order_by = options[:order_by] || args[2]
47
+
48
+ raise ArgumentError, 'You must specify a :relation option' \
49
+ if relation.nil?
50
+ raise ArgumentError, 'You must specify a :sortable_fields option' \
51
+ if sortable_fields.nil?
52
+
53
+ # Convert sortable_fields to Hash if it's an Array
54
+ sortable_fields = Hash[*sortable_fields.collect{|s| [s.to_s, s]}] \
55
+ if sortable_fields.is_a? Array
56
+
57
+ # Ordering
58
+ order_bys = sanitize_order_bys(sortable_fields, order_by)
59
+ outputs[:items] = relation.order(order_bys)
60
+ end
61
+
62
+ # Returns an order_by Object
63
+ def sanitize_order_by(sortable_fields, field = nil, dir = nil)
64
+ sanitized_field = sortable_fields[field.to_s.downcase] || \
65
+ sortable_fields.values.first
66
+ sanitized_dir = dir.to_s.downcase == 'desc' ? :desc : :asc
67
+ case sanitized_field
68
+ when Symbol
69
+ {sanitized_field => sanitized_dir}
70
+ when Arel::Attributes::Attribute
71
+ sanitized_field.send sanitized_dir
72
+ else
73
+ "#{sanitized_field.to_s} #{sanitized_dir.to_s.upcase}"
74
+ end
75
+ end
76
+
77
+ # Returns an Array of order_by Objects
78
+ def sanitize_order_bys(sortable_fields, order_bys = nil)
79
+ obs = case order_bys
80
+ when Array
81
+ order_bys.collect do |ob|
82
+ case ob
83
+ when Hash
84
+ sanitize_order_by(sortable_fields, ob.keys.first, ob.values.first)
85
+ when Array
86
+ sanitize_order_by(sortable_fields, ob.first, ob.second)
87
+ else
88
+ sanitize_order_by(sortable_fields, ob)
89
+ end
90
+ end
91
+ when Hash
92
+ order_bys.collect { |k, v| sanitize_order_by(sortable_fields, k, v) }
93
+ else
94
+ order_bys.to_s.split(',').collect do |ob|
95
+ fd = ob.split(' ')
96
+ sanitize_order_by(sortable_fields, fd.first, fd.second)
97
+ end
98
+ end
99
+ obs.blank? ? sanitize_order_by(sortable_fields) : obs
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,130 @@
1
+ # Database-agnostic routine for keyword searching
2
+ # and ordering, limiting and paginating search results
3
+ #
4
+ # Searches, orders, imposes a maximum number or records and paginates
5
+ # a given relation using the appropriate routines and user-supplied
6
+ # parameters from the params hash
7
+ #
8
+ # See the search_routine.rb, order_routine.rb and
9
+ # limit_and_paginate_routine.rb files for more information
10
+ #
11
+ # Callers must provide the search_relation, search_proc and
12
+ # sortable_fields arguments and may provide the max_items argument
13
+ #
14
+ # Users must provide the q (query) argument and may provide
15
+ # the order_by, per_page and page arguments in the params hash
16
+ # Users must also be authorized to search the base class of the search_routine
17
+ #
18
+ # Required arguments:
19
+ #
20
+ # Developer-supplied:
21
+ #
22
+ # relation - the initial ActiveRecord::Relation to start searching on
23
+ # search_proc - a Proc passed to keyword_search's `search` method
24
+ # it receives keyword_search's `with` object as argument
25
+ # this proc must define the `keyword` blocks for keyword_search
26
+ # the relation to be scoped is contained in the @items instance variable
27
+ # the `to_string_array` helper can help with
28
+ # parsing strings from the query
29
+ #
30
+ # sortable_fields - list of fields that can appear in the order_by argument
31
+ # can be a Hash that maps field names to database columns
32
+ # or an Array of Strings
33
+ # invalid fields in order_by will be replaced with
34
+ # the first field listed here, in :asc order
35
+ #
36
+ # User or UI-supplied:
37
+ #
38
+ # params[:query] - a String that follows the keyword format
39
+ # Keywords have the format keyword:value
40
+ # Keywords can also be negated with -, as in -keyword:value
41
+ # Values are comma-separated; keywords are space-separated
42
+ #
43
+ # Optional arguments:
44
+ #
45
+ # Developer-supplied (recommended to prevent scraping):
46
+ #
47
+ # max_items - the maximum number of matching items allowed to be returned
48
+ # no results will be returned if this number is exceeded,
49
+ # but the total result count will still be returned
50
+ # applies even if pagination is enabled
51
+ # default: nil (disabled)
52
+ #
53
+ # User or UI-supplied:
54
+ #
55
+ # params[:order_by] - list of fields to order by, with optional sort directions
56
+ # can be (an Array of) Hashes, or Strings
57
+ # default: {sortable_fields.values.first => :asc}
58
+ #
59
+ # params[:per_page] - the maximum number of search results per page
60
+ # default: nil (disabled)
61
+ # params[:page] - the number of the page to return
62
+ # default: 1
63
+ #
64
+ # This handler's output contains:
65
+ #
66
+ # outputs[:total_count] - the total number of items that matched the query
67
+ # outputs[:items] - the relation returned by the search routines
68
+
69
+ require 'lev'
70
+
71
+ module OpenStax
72
+ module Utilities
73
+ class SearchAndOrganizeRelation
74
+
75
+ lev_routine transaction: :no_transaction
76
+
77
+ uses_routine SearchRelation,
78
+ as: :search
79
+ uses_routine OrderRelation,
80
+ as: :order
81
+ uses_routine LimitAndPaginateRelation,
82
+ as: :limit_and_paginate,
83
+ errors_are_fatal: false,
84
+ translations: { inputs: { type: :verbatim },
85
+ outputs: { type: :verbatim } }
86
+
87
+ protected
88
+
89
+ def exec(*args, &search_proc)
90
+
91
+ options = args.last.is_a?(Hash) ? args.pop : {}
92
+ relation = options[:relation] || args[0]
93
+ sortable_fields = options[:sortable_fields] || args[1]
94
+ params = options[:params] || args[2]
95
+ search_proc ||= options[:search_proc] || args[3]
96
+ max_items = options[:max_items] || nil
97
+
98
+ raise ArgumentError, 'You must specify a :relation option' \
99
+ if relation.nil?
100
+ raise ArgumentError, 'You must specify a :sortable_fields option' \
101
+ if sortable_fields.nil?
102
+ raise ArgumentError, 'You must specify a :params option' if params.nil?
103
+ raise ArgumentError, 'You must specify a block or :search_proc option' \
104
+ if search_proc.nil?
105
+
106
+ query = params[:query] || params[:q]
107
+ order_by = params[:order_by] || params[:ob]
108
+ per_page = params[:per_page] || params[:pp]
109
+ page = params[:page] || params[:p]
110
+
111
+ items = run(:search, relation: relation, search_proc: search_proc,
112
+ query: query).outputs[:items]
113
+
114
+ items = run(:order, relation: items, sortable_fields: sortable_fields,
115
+ order_by: order_by).outputs[:items]
116
+
117
+ if max_items.nil? && per_page.nil? && page.nil?
118
+ outputs[:items] = items
119
+ outputs[:total_count] = items.count
120
+ return
121
+ end
122
+
123
+ run(:limit_and_paginate, relation: items, max_items: max_items,
124
+ per_page: per_page, page: page)
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,94 @@
1
+ # Database-agnostic keyword searching routine
2
+ #
3
+ # Filters a relation based on a search Proc and a query String
4
+ # See https://github.com/bruce/keyword_search for more information
5
+ # about these arguments
6
+ #
7
+ # Callers of this routine provide the search_proc, relation and query arguments
8
+ #
9
+ # Required arguments:
10
+ #
11
+ # Developer-supplied:
12
+ #
13
+ # relation - the initial ActiveRecord::Relation to start searching on
14
+ # search_proc - a Proc passed to keyword_search's `search` method
15
+ # it receives keyword_search's `with` object as argument
16
+ # this proc must define the `keyword` blocks for keyword_search
17
+ # the relation to be scoped is contained in the @items instance variable
18
+ # the `to_string_array` helper can help with
19
+ # parsing strings from the query
20
+ #
21
+ # User or developer-supplied:
22
+ #
23
+ # query - a String that follows the keyword format
24
+ # Keywords have the format keyword:value
25
+ # Keywords can also be negated with -, as in -keyword:value
26
+ # Values are comma-separated, while keywords are space-separated
27
+ #
28
+ # This routine's outputs contain:
29
+ #
30
+ # outputs[:items] - a relation with records that match the query terms
31
+
32
+ require 'lev'
33
+ require 'keyword_search'
34
+
35
+ module OpenStax
36
+ module Utilities
37
+ class SearchRelation
38
+
39
+ lev_routine transaction: :no_transaction
40
+
41
+ protected
42
+
43
+ def exec(*args, &search_proc)
44
+
45
+ options = args.last.is_a?(Hash) ? args.pop : {}
46
+ @items = options[:relation] || args[0]
47
+ query = options[:query] || args[1]
48
+ search_proc ||= options[:search_proc] || args[2]
49
+
50
+ raise ArgumentError, 'You must specify a :relation option' if @items.nil?
51
+ raise ArgumentError, 'You must specify a block or :search_proc option' \
52
+ if search_proc.nil?
53
+
54
+ # Scoping
55
+
56
+ begin
57
+ ::KeywordSearch.search(query.to_s) do |with|
58
+ instance_exec(with, &search_proc)
59
+ end
60
+ rescue KeywordSearch::ParseError
61
+ fatal_error(code: :invalid_query,
62
+ message: 'The search query string provided is invalid')
63
+ end
64
+
65
+ outputs[:items] = @items
66
+ end
67
+
68
+ # Parses a keyword string into an array of strings
69
+ # User-supplied wildcards are removed and strings are split on commas
70
+ # Then wildcards are appended or prepended if the append_wildcard or
71
+ # prepend_wildcard options are specified
72
+ def to_string_array(str, options = {})
73
+ sa = case str
74
+ when Array
75
+ str.collect{|name| name.gsub('%', '').split(',')}.flatten
76
+ else
77
+ str.to_s.gsub('%', '').split(',')
78
+ end
79
+ sa = sa.collect{|str| "#{str}%"} if options[:append_wildcard]
80
+ sa = sa.collect{|str| "%#{str}"} if options[:prepend_wildcard]
81
+ sa
82
+ end
83
+
84
+ # Parses a keyword string into an array of numbers
85
+ # User-supplied wildcards are removed and strings are split on commas
86
+ # Only numbers are returned
87
+ def to_number_array(str)
88
+ to_string_array(str).collect{|s| Integer(s) rescue nil}.compact
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+ end
@@ -24,7 +24,8 @@ module OpenStax
24
24
  end
25
25
 
26
26
  def self.require_action_allowed!(action, requestor, resource)
27
- raise SecurityTransgression unless action_allowed?(action, requestor, resource)
27
+ msg = "\"#{requestor.inspect}\" is not allowed to perform \"#{action}\" on \"#{resource.inspect}\""
28
+ raise(SecurityTransgression, msg) unless action_allowed?(action, requestor, resource)
28
29
  end
29
30
 
30
31
  def self.action_allowed?(action, requestor, resource)
@@ -38,7 +39,7 @@ module OpenStax
38
39
  end
39
40
 
40
41
  resource_class = resource.is_a?(Class) ? resource : resource.class
41
- policy_class = instance.resource_policy_map[resource_class.to_s]
42
+ policy_class = instance.resource_policy_map[resource_class.to_s].try(:constantize)
42
43
 
43
44
  # If there is no policy registered, we by default deny access
44
45
  return false if policy_class.nil?
@@ -47,7 +48,7 @@ module OpenStax
47
48
  end
48
49
 
49
50
  def self.register(resource_class, policy_class)
50
- self.instance.resource_policy_map[resource_class.to_s] = policy_class
51
+ self.instance.resource_policy_map[resource_class.to_s] = policy_class.to_s
51
52
  end
52
53
 
53
54
  end