openstax_utilities 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/Rakefile +4 -3
- data/app/handlers/openstax/utilities/abstract_keyword_search_handler.rb +92 -0
- data/app/routines/openstax/utilities/abstract_keyword_search_routine.rb +149 -0
- data/lib/openstax/utilities/delegate_access_control.rb +5 -0
- data/lib/openstax/utilities/engine.rb +4 -8
- data/lib/openstax/utilities/helpers/datetime.rb +4 -4
- data/{app/helpers → lib}/openstax/utilities/osu_helper.rb +9 -1
- data/lib/openstax/utilities/version.rb +1 -1
- data/lib/openstax_utilities.rb +1 -7
- data/spec/dummy/README.md +1 -1
- data/spec/dummy/Rakefile +1 -2
- data/spec/dummy/app/assets/javascripts/application.js +3 -5
- data/spec/dummy/app/assets/stylesheets/application.css +5 -3
- data/spec/dummy/app/controllers/application_controller.rb +3 -3
- data/spec/dummy/app/handlers/users_search.rb +11 -0
- data/spec/dummy/app/routines/search_users.rb +22 -0
- data/spec/dummy/app/views/layouts/application.html.erb +2 -2
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +1 -1
- data/spec/dummy/config/application.rb +1 -37
- data/spec/dummy/config/boot.rb +4 -9
- data/spec/dummy/config/database.yml +8 -8
- data/spec/dummy/config/environment.rb +3 -3
- data/spec/dummy/config/environments/development.rb +19 -19
- data/spec/dummy/config/environments/production.rb +41 -30
- data/spec/dummy/config/environments/test.rb +17 -15
- data/spec/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +6 -5
- data/spec/dummy/config/initializers/mime_types.rb +0 -1
- data/spec/dummy/config/initializers/session_store.rb +1 -6
- data/spec/dummy/config/initializers/wrap_parameters.rb +6 -6
- data/spec/dummy/config/locales/en.yml +20 -2
- data/spec/dummy/config/routes.rb +0 -3
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/migrate/0_create_users.rb +8 -2
- data/spec/dummy/db/schema.rb +13 -7
- data/spec/dummy/public/404.html +54 -13
- data/spec/dummy/public/422.html +54 -13
- data/spec/dummy/public/500.html +53 -12
- data/spec/factories/user.rb +8 -0
- data/spec/handlers/openstax/utilities/abstract_keyword_search_handler_spec.rb +93 -0
- data/spec/lib/openstax/utilities/access_policy_spec.rb +2 -2
- data/spec/rails_helper.rb +54 -0
- data/spec/routines/openstax/utilities/abstract_keyword_search_routine_spec.rb +114 -0
- data/spec/spec_helper.rb +80 -11
- metadata +102 -24
- data/app/assets/javascripts/openstax_utilities.js +0 -0
- data/app/assets/stylesheets/openstax_utilities.css +0 -4
- data/spec/dummy/app/assets/stylesheets/scaffold.css +0 -56
- data/spec/dummy/app/controllers/users_controller.rb +0 -87
- data/spec/dummy/app/views/users/_form.html.erb +0 -17
- data/spec/dummy/app/views/users/edit.html.erb +0 -6
- data/spec/dummy/app/views/users/index.html.erb +0 -21
- data/spec/dummy/app/views/users/new.html.erb +0 -5
- data/spec/dummy/app/views/users/show.html.erb +0 -5
- data/spec/dummy/config/initializers/secret_token.rb +0 -7
- data/spec/dummy/script/rails +0 -6
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NjczMjZiNWYzMjljMzM4ZjMyM2FlYjg2MGMxNzhlZDUxODNiOWY1MQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZDMzZDg3N2E2MzRmZjYzNDIxYjA5YjNiOTNmYjBkODllNjM1ODE0ZA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NzA2YTc4MGI1ZDhjZmU0ZWVlMzc3YzAzMDBlMGZjMzZjODE4NDNjZGNlNzRj
|
10
|
+
YTNkYzU0NDA2YTgzOWUxNTkxZWEzYjZmODAxNDRkOTc0MDZjNzVlMGMwYzYy
|
11
|
+
ZGY0NzQxMGI2ZTJkNWNhMzI3ZGRhOWYwYjRkZDdkNTYzMGQwMWI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MzZiN2YyYzBmMTRhOTMzNDFkNTA2MWZiY2JlZDFmZWVmNGJhZGJiN2YwYmM3
|
14
|
+
OGUzYThkOGIxMTQ1ZmY1MDljNTQ5NTZlNTVjNWVlZmQ4N2ZlNGUyM2E0NDg4
|
15
|
+
NGQxMDM1ZWMyNzYwNmFiNWY4MTE1M2VmYjI4YjVkZjM3NGE3NDA=
|
data/Rakefile
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
|
+
|
2
3
|
begin
|
3
4
|
require 'bundler/setup'
|
4
5
|
rescue LoadError
|
5
6
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
7
|
end
|
7
8
|
|
8
|
-
APP_RAKEFILE = File.expand_path(
|
9
|
+
APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
|
9
10
|
load 'rails/tasks/engine.rake'
|
10
11
|
|
11
12
|
Bundler::GemHelper.install_tasks
|
@@ -13,7 +14,7 @@ Bundler::GemHelper.install_tasks
|
|
13
14
|
require 'rspec/core'
|
14
15
|
require 'rspec/core/rake_task'
|
15
16
|
|
16
|
-
desc
|
17
|
+
desc 'Run all specs in spec directory (excluding plugin specs)'
|
17
18
|
RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
|
18
19
|
|
19
|
-
task :default => :spec
|
20
|
+
task :default => :spec
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# Database-agnostic keyword searching handler
|
2
|
+
#
|
3
|
+
# Keywords have the format keyword:value
|
4
|
+
# Keywords can also be negated with -, as in -keyword:value
|
5
|
+
# Values are comma-separated, while keywords are space-separated
|
6
|
+
# See https://github.com/bruce/keyword_search for more information
|
7
|
+
#
|
8
|
+
# Subclasses must set the search_routine class variable,
|
9
|
+
# as well as the authorized? method
|
10
|
+
#
|
11
|
+
# Required:
|
12
|
+
#
|
13
|
+
# search_routine - the Lev::Routine that will handle the search
|
14
|
+
#
|
15
|
+
# Optional (recommended to prevent scraping):
|
16
|
+
#
|
17
|
+
# min_characters - the minimum number of characters allowed in the query
|
18
|
+
# only an error will be returned if the query has less
|
19
|
+
# than the minimum number of characters allowed
|
20
|
+
# default: nil (disabled)
|
21
|
+
#
|
22
|
+
# max_items - the maximum number of matching items allowed to be returned
|
23
|
+
# no results will be returned if this number is exceeded,
|
24
|
+
# but the total result count will still be returned
|
25
|
+
# applies even if pagination is enabled
|
26
|
+
# default: nil (disabled)
|
27
|
+
#
|
28
|
+
# This handler expects the following parameters from the user or the UI:
|
29
|
+
#
|
30
|
+
# Required:
|
31
|
+
#
|
32
|
+
# q - the query itself, a String that follows the keyword format above
|
33
|
+
#
|
34
|
+
# Optional:
|
35
|
+
#
|
36
|
+
# order_by - a String used to order the search results - default: 'created_at ASC'
|
37
|
+
# per_page - the number of results returned per page - default: nil (disabled)
|
38
|
+
# page - the current page number - default: 1
|
39
|
+
#
|
40
|
+
# This handler's output contains:
|
41
|
+
#
|
42
|
+
# outputs[:total_count] - the total number of items that matched the query
|
43
|
+
# set even when no results are returned due to
|
44
|
+
# a query that is too short or too generic
|
45
|
+
# outputs[:items] - the array of objects returned by the search routine
|
46
|
+
#
|
47
|
+
# See spec/dummy/app/handlers/users_search.rb for an example search handler
|
48
|
+
|
49
|
+
require 'lev'
|
50
|
+
|
51
|
+
module OpenStax
|
52
|
+
module Utilities
|
53
|
+
class AbstractKeywordSearchHandler
|
54
|
+
|
55
|
+
lev_handler
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
class_attribute :search_routine, :max_items, :min_characters
|
60
|
+
|
61
|
+
def authorized?
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle
|
66
|
+
raise NotImplementedError if search_routine.nil?
|
67
|
+
|
68
|
+
query = params[:q] || params[:query]
|
69
|
+
|
70
|
+
fatal_error(code: :no_query,
|
71
|
+
message: 'You must provide a query parameter (q or query).') if query.nil?
|
72
|
+
fatal_error(code: :query_too_short,
|
73
|
+
message: "The provided query is too short (minimum #{
|
74
|
+
min_characters} characters).") \
|
75
|
+
if !min_characters.nil? && query.length < min_characters
|
76
|
+
|
77
|
+
items = run(search_routine, query, params).outputs[:items]
|
78
|
+
|
79
|
+
outputs[:total_count] = items.limit(nil).offset(nil).count
|
80
|
+
|
81
|
+
fatal_error(code: :too_many_matches,
|
82
|
+
message: "The number of matches exceeded the allowed limit of #{
|
83
|
+
max_items} matches. Please refine your query and try again.") \
|
84
|
+
if !max_items.nil? && outputs[:total_count] > max_items
|
85
|
+
|
86
|
+
outputs[:items] = items.to_a
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# Database-agnostic keyword searching routine
|
2
|
+
#
|
3
|
+
# Keywords have the format keyword:value
|
4
|
+
# Keywords can also be negated with -, as in -keyword:value
|
5
|
+
# Values are comma-separated, while keywords are space-separated
|
6
|
+
# See https://github.com/bruce/keyword_search for more information
|
7
|
+
#
|
8
|
+
# Subclasses must set the initial_relation, search_proc and sortable_fields class variables
|
9
|
+
#
|
10
|
+
# initial_relation - the ActiveRecord::Relation that contains all
|
11
|
+
# records to be searched, usually ClassName.unscoped
|
12
|
+
#
|
13
|
+
# search_proc - a proc passed to keyword_search's `search` method
|
14
|
+
# it receives keyword_search's `with` object as argument
|
15
|
+
# this proc must define the `keyword` blocks for keyword_search
|
16
|
+
# the relation to be scoped is contained in the @items instance variable
|
17
|
+
#
|
18
|
+
# The `to_string_array` helper can help with parsing strings from the query
|
19
|
+
#
|
20
|
+
# sortable_fields_map - a Hash that maps the lowercase names of fields
|
21
|
+
# which can be used to sort the results to symbols
|
22
|
+
# for their respective database columns
|
23
|
+
# keys are lowercase strings that should be allowed
|
24
|
+
# in options[:order_by]
|
25
|
+
# values are the corresponding database column names
|
26
|
+
# that will be passed to the order() method
|
27
|
+
#
|
28
|
+
# Callers of subclass routines provides a query argument and an options hash
|
29
|
+
#
|
30
|
+
# Required arguments:
|
31
|
+
#
|
32
|
+
# query - a string that follows the keyword format above
|
33
|
+
#
|
34
|
+
# Options hash:
|
35
|
+
#
|
36
|
+
# Ordering:
|
37
|
+
#
|
38
|
+
# :order_by - list of fields to sort by, with optional sort directions
|
39
|
+
# can be a String, Array of Strings or Array of Hashes
|
40
|
+
# default: {"created_at" => :asc}
|
41
|
+
#
|
42
|
+
# Pagination:
|
43
|
+
#
|
44
|
+
# :per_page - the maximum number of results per page - default: nil (disabled)
|
45
|
+
# :page - the page to return - default: 1
|
46
|
+
#
|
47
|
+
# This routine's output contains:
|
48
|
+
#
|
49
|
+
# outputs[:items] - an ActiveRecord::Relation that matches the query terms and options
|
50
|
+
#
|
51
|
+
# You can use the following expression to obtain the
|
52
|
+
# total count of records that matched the query terms:
|
53
|
+
#
|
54
|
+
# outputs[:items].limit(nil).offset(nil).count
|
55
|
+
#
|
56
|
+
# See spec/dummy/app/routines/search_users.rb for an example search routine
|
57
|
+
|
58
|
+
require 'lev'
|
59
|
+
require 'keyword_search'
|
60
|
+
|
61
|
+
module OpenStax
|
62
|
+
module Utilities
|
63
|
+
class AbstractKeywordSearchRoutine
|
64
|
+
|
65
|
+
lev_routine transaction: :no_transaction
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
class_attribute :initial_relation, :search_proc, :sortable_fields_map
|
70
|
+
|
71
|
+
def exec(query, options = {})
|
72
|
+
raise NotImplementedError if initial_relation.nil? || \
|
73
|
+
search_proc.nil? || sortable_fields_map.nil?
|
74
|
+
|
75
|
+
@items = initial_relation
|
76
|
+
|
77
|
+
return @items.none unless query.is_a? String
|
78
|
+
|
79
|
+
# Scoping
|
80
|
+
|
81
|
+
::KeywordSearch.search(query) do |with|
|
82
|
+
instance_exec(with, &search_proc)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Ordering
|
86
|
+
|
87
|
+
order_bys = sanitize_order_bys(options[:order_by])
|
88
|
+
@items = @items.order(order_bys)
|
89
|
+
|
90
|
+
# Pagination
|
91
|
+
|
92
|
+
per_page = Integer(options[:per_page]) rescue nil
|
93
|
+
unless per_page.nil?
|
94
|
+
page = Integer(options[:page]) rescue 1
|
95
|
+
@items = @items.limit(per_page).offset(per_page*(page-1))
|
96
|
+
end
|
97
|
+
|
98
|
+
outputs[:items] = @items
|
99
|
+
end
|
100
|
+
|
101
|
+
def sanitize_order_by(field, dir = nil)
|
102
|
+
sanitized_field = sortable_fields_map[field.to_s.downcase] || :created_at
|
103
|
+
sanitized_dir = dir.to_s.downcase == 'desc' ? :desc : :asc
|
104
|
+
{sanitized_field => sanitized_dir}
|
105
|
+
end
|
106
|
+
|
107
|
+
def sanitize_order_bys(order_bys)
|
108
|
+
case order_bys
|
109
|
+
when Array
|
110
|
+
order_bys.collect do |ob|
|
111
|
+
case ob
|
112
|
+
when Hash
|
113
|
+
sanitize_order_by(ob.keys.first, ob.values.first)
|
114
|
+
when Array
|
115
|
+
sanitize_order_by(ob.first, ob.second)
|
116
|
+
else
|
117
|
+
sanitize_order_by(ob)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
when Hash
|
121
|
+
order_bys.collect { |k, v| sanitize_order_by(k, v) }
|
122
|
+
else
|
123
|
+
order_bys.to_s.split(',').collect do |ob|
|
124
|
+
fd = ob.split(' ')
|
125
|
+
sanitize_order_by(fd.first, fd.second)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Parses a keyword string into an array of strings
|
131
|
+
# User-supplied wildcards are removed and strings are split on commas
|
132
|
+
# Then wildcards are appended or prepended if the append_wildcard or
|
133
|
+
# prepend_wildcard options are specified
|
134
|
+
def to_string_array(str, options = {})
|
135
|
+
sa = case str
|
136
|
+
when Array
|
137
|
+
str.collect{|name| name.gsub('%', '').split(',')}.flatten
|
138
|
+
else
|
139
|
+
str.to_s.gsub('%', '').split(',')
|
140
|
+
end
|
141
|
+
sa = sa.collect{|str| "#{str}%"} if options[:append_wildcard]
|
142
|
+
sa = sa.collect{|str| "%#{str}"} if options[:prepend_wildcard]
|
143
|
+
sa
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "openstax/utilities/action_list"
|
2
|
+
|
1
3
|
ActiveSupport::Inflector.inflections do |inflect|
|
2
4
|
inflect.acronym 'OpenStax'
|
3
5
|
end
|
@@ -7,14 +9,8 @@ module OpenStax
|
|
7
9
|
class Engine < ::Rails::Engine
|
8
10
|
isolate_namespace OpenStax::Utilities
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
initializer 'openstax_utilities.action_controller' do |app|
|
15
|
-
ActiveSupport.on_load :action_controller do
|
16
|
-
helper OSU::OsuHelper
|
17
|
-
end
|
12
|
+
config.after_initialize do
|
13
|
+
OSU::SITE_NAME = ::Rails.application.class.parent_name.underscore
|
18
14
|
end
|
19
15
|
|
20
16
|
config.generators do |g|
|
@@ -4,25 +4,25 @@ module OpenStax::Utilities::Helpers
|
|
4
4
|
def standard_date(datetime)
|
5
5
|
datetime.nil? ?
|
6
6
|
"" :
|
7
|
-
datetime.strftime(
|
7
|
+
datetime.strftime(OpenStax::Utilities.configuration.standard_date_format)
|
8
8
|
end
|
9
9
|
|
10
10
|
def standard_datetime(datetime)
|
11
11
|
datetime.nil? ?
|
12
12
|
"" :
|
13
|
-
datetime.strftime(
|
13
|
+
datetime.strftime(OpenStax::Utilities.configuration.standard_datetime_format)
|
14
14
|
end
|
15
15
|
|
16
16
|
def standard_time(datetime)
|
17
17
|
datetime.nil? ?
|
18
18
|
"" :
|
19
|
-
datetime.strftime(
|
19
|
+
datetime.strftime(OpenStax::Utilities.configuration.standard_time_format)
|
20
20
|
end
|
21
21
|
|
22
22
|
def standard_datetime_zone(datetime, zone)
|
23
23
|
datetime.nil? ?
|
24
24
|
"" :
|
25
|
-
datetime.in_time_zone(zone).strftime(
|
25
|
+
datetime.in_time_zone(zone).strftime(OpenStax::Utilities.configuration.standard_datetime_format)
|
26
26
|
end
|
27
27
|
|
28
28
|
def month_year(datetime)
|
@@ -1,3 +1,9 @@
|
|
1
|
+
require "openstax/utilities/classy_helper"
|
2
|
+
require "openstax/utilities/helpers/misc"
|
3
|
+
require "openstax/utilities/helpers/partials"
|
4
|
+
require "openstax/utilities/helpers/action_list"
|
5
|
+
require "openstax/utilities/helpers/datetime"
|
6
|
+
|
1
7
|
module OpenStax::Utilities
|
2
8
|
module OsuHelper
|
3
9
|
|
@@ -13,4 +19,6 @@ module OpenStax::Utilities
|
|
13
19
|
end
|
14
20
|
|
15
21
|
end
|
16
|
-
end
|
22
|
+
end
|
23
|
+
|
24
|
+
ActionController::Base.send :helper, OpenStax::Utilities::OsuHelper
|
data/lib/openstax_utilities.rb
CHANGED
@@ -15,16 +15,10 @@ require "openstax/utilities/enum"
|
|
15
15
|
require "openstax/utilities/ruby"
|
16
16
|
require "openstax/utilities/text"
|
17
17
|
require "openstax/utilities/network"
|
18
|
-
require "openstax/utilities/action_list"
|
19
18
|
require "openstax/utilities/acts_as_numberable"
|
20
19
|
require "openstax/utilities/delegate_access_control"
|
21
20
|
require "openstax/utilities/access_policy"
|
22
|
-
|
23
|
-
require "openstax/utilities/classy_helper"
|
24
|
-
require "openstax/utilities/helpers/misc"
|
25
|
-
require "openstax/utilities/helpers/partials"
|
26
|
-
require "openstax/utilities/helpers/action_list"
|
27
|
-
require "openstax/utilities/helpers/datetime"
|
21
|
+
require "openstax/utilities/osu_helper"
|
28
22
|
|
29
23
|
module OpenStax
|
30
24
|
module Utilities
|
data/spec/dummy/README.md
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Dummy application used to test the openstax_utilities gem.
|
1
|
+
Dummy application used to test the openstax_utilities gem.
|
data/spec/dummy/Rakefile
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
#!/usr/bin/env rake
|
2
1
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
3
2
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
4
3
|
|
5
4
|
require File.expand_path('../config/application', __FILE__)
|
6
5
|
|
7
|
-
|
6
|
+
Rails.application.load_tasks
|
@@ -5,11 +5,9 @@
|
|
5
5
|
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
6
|
//
|
7
7
|
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
-
//
|
8
|
+
// compiled file.
|
9
9
|
//
|
10
|
-
//
|
11
|
-
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
12
|
//
|
13
|
-
//= require jquery
|
14
|
-
//= require jquery_ujs
|
15
13
|
//= require_tree .
|
@@ -5,9 +5,11 @@
|
|
5
5
|
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
6
|
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
7
|
*
|
8
|
-
* You're free to add application-wide styles to this file and they'll appear at the
|
9
|
-
* compiled file
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
10
12
|
*
|
11
|
-
*= require_self
|
12
13
|
*= require_tree .
|
14
|
+
*= require_self
|
13
15
|
*/
|