openstax_utilities 3.0.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
*/
|