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.
Files changed (62) hide show
  1. checksums.yaml +8 -8
  2. data/Rakefile +4 -3
  3. data/app/handlers/openstax/utilities/abstract_keyword_search_handler.rb +92 -0
  4. data/app/routines/openstax/utilities/abstract_keyword_search_routine.rb +149 -0
  5. data/lib/openstax/utilities/delegate_access_control.rb +5 -0
  6. data/lib/openstax/utilities/engine.rb +4 -8
  7. data/lib/openstax/utilities/helpers/datetime.rb +4 -4
  8. data/{app/helpers → lib}/openstax/utilities/osu_helper.rb +9 -1
  9. data/lib/openstax/utilities/version.rb +1 -1
  10. data/lib/openstax_utilities.rb +1 -7
  11. data/spec/dummy/README.md +1 -1
  12. data/spec/dummy/Rakefile +1 -2
  13. data/spec/dummy/app/assets/javascripts/application.js +3 -5
  14. data/spec/dummy/app/assets/stylesheets/application.css +5 -3
  15. data/spec/dummy/app/controllers/application_controller.rb +3 -3
  16. data/spec/dummy/app/handlers/users_search.rb +11 -0
  17. data/spec/dummy/app/routines/search_users.rb +22 -0
  18. data/spec/dummy/app/views/layouts/application.html.erb +2 -2
  19. data/spec/dummy/bin/bundle +3 -0
  20. data/spec/dummy/bin/rails +4 -0
  21. data/spec/dummy/bin/rake +4 -0
  22. data/spec/dummy/config.ru +1 -1
  23. data/spec/dummy/config/application.rb +1 -37
  24. data/spec/dummy/config/boot.rb +4 -9
  25. data/spec/dummy/config/database.yml +8 -8
  26. data/spec/dummy/config/environment.rb +3 -3
  27. data/spec/dummy/config/environments/development.rb +19 -19
  28. data/spec/dummy/config/environments/production.rb +41 -30
  29. data/spec/dummy/config/environments/test.rb +17 -15
  30. data/spec/dummy/config/initializers/assets.rb +8 -0
  31. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  32. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  33. data/spec/dummy/config/initializers/inflections.rb +6 -5
  34. data/spec/dummy/config/initializers/mime_types.rb +0 -1
  35. data/spec/dummy/config/initializers/session_store.rb +1 -6
  36. data/spec/dummy/config/initializers/wrap_parameters.rb +6 -6
  37. data/spec/dummy/config/locales/en.yml +20 -2
  38. data/spec/dummy/config/routes.rb +0 -3
  39. data/spec/dummy/config/secrets.yml +22 -0
  40. data/spec/dummy/db/migrate/0_create_users.rb +8 -2
  41. data/spec/dummy/db/schema.rb +13 -7
  42. data/spec/dummy/public/404.html +54 -13
  43. data/spec/dummy/public/422.html +54 -13
  44. data/spec/dummy/public/500.html +53 -12
  45. data/spec/factories/user.rb +8 -0
  46. data/spec/handlers/openstax/utilities/abstract_keyword_search_handler_spec.rb +93 -0
  47. data/spec/lib/openstax/utilities/access_policy_spec.rb +2 -2
  48. data/spec/rails_helper.rb +54 -0
  49. data/spec/routines/openstax/utilities/abstract_keyword_search_routine_spec.rb +114 -0
  50. data/spec/spec_helper.rb +80 -11
  51. metadata +102 -24
  52. data/app/assets/javascripts/openstax_utilities.js +0 -0
  53. data/app/assets/stylesheets/openstax_utilities.css +0 -4
  54. data/spec/dummy/app/assets/stylesheets/scaffold.css +0 -56
  55. data/spec/dummy/app/controllers/users_controller.rb +0 -87
  56. data/spec/dummy/app/views/users/_form.html.erb +0 -17
  57. data/spec/dummy/app/views/users/edit.html.erb +0 -6
  58. data/spec/dummy/app/views/users/index.html.erb +0 -21
  59. data/spec/dummy/app/views/users/new.html.erb +0 -5
  60. data/spec/dummy/app/views/users/show.html.erb +0 -5
  61. data/spec/dummy/config/initializers/secret_token.rb +0 -7
  62. 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
- NTE4ZjlhOTM0OTAxNzhhMTI2ZWU0ODhiNTIwZTAxZTk3ZGYyOTc0OA==
4
+ NjczMjZiNWYzMjljMzM4ZjMyM2FlYjg2MGMxNzhlZDUxODNiOWY1MQ==
5
5
  data.tar.gz: !binary |-
6
- MmE0MTg2ZWRhNWRlMDViMjZjODc5NTM2OGYzYjJmZDdlMDI4Y2QzZQ==
6
+ ZDMzZDg3N2E2MzRmZjYzNDIxYjA5YjNiOTNmYjBkODllNjM1ODE0ZA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YjkxMmQ2MGJmMjAyMDczNWJmZmExNzk1ODBhZDRjODYyOTNmMmRiZDE5OWZh
10
- ZDljYzY5OTkzMTJiZWVjZTBhYWYxODQ2YjQ1Mzc5N2ExNjg4NmIzOTU0YWI4
11
- OGFkYTI4N2QyMjk1ZTBiNzk5ZmI3OGNiNTY2ZTQzY2VlOWU0NWI=
9
+ NzA2YTc4MGI1ZDhjZmU0ZWVlMzc3YzAzMDBlMGZjMzZjODE4NDNjZGNlNzRj
10
+ YTNkYzU0NDA2YTgzOWUxNTkxZWEzYjZmODAxNDRkOTc0MDZjNzVlMGMwYzYy
11
+ ZGY0NzQxMGI2ZTJkNWNhMzI3ZGRhOWYwYjRkZDdkNTYzMGQwMWI=
12
12
  data.tar.gz: !binary |-
13
- YjliMzBmNTk3YjNmMWQyYTE4ZTM4OTc1YmRlZWYyMGZiZTIwMjhmNzFiY2Jh
14
- ZjAzMGJhOTk3ODExNzY0NDliM2QxY2JkYjhmNTMyZmVhZDdhZDBmN2M1NmFh
15
- OWMzYWY4OTkzY2YzYmY0MmRjZGQ2YTdjOWQyZTQyNjE0OTIzYTI=
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("../spec/dummy/Rakefile", __FILE__)
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 "Run all specs in spec directory (excluding plugin specs)"
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
@@ -42,6 +42,11 @@ module OpenStax
42
42
  EOV
43
43
 
44
44
  end
45
+
46
+ # Convenience method for delegate_access_control
47
+ def delegate_access_control_to(relation, options = {})
48
+ delegate_access_control options.merge(to: relation)
49
+ end
45
50
 
46
51
  end
47
52
 
@@ -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
- initializer "openstax_utilities.assets.precompile" do |app|
11
- app.config.assets.precompile += %w(openstax_utilities.css openstax_utilities.js)
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(OSU.configuration.standard_date_format)
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(OSU.configuration.standard_datetime_format)
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(OSU.configuration.standard_time_format)
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(OSU.configuration.standard_datetime_format)
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
@@ -1,5 +1,5 @@
1
1
  module OpenStax
2
2
  module Utilities
3
- VERSION = "3.0.0"
3
+ VERSION = "4.0.0"
4
4
  end
5
5
  end
@@ -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
- Dummy::Application.load_tasks
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
- // the compiled file.
8
+ // compiled file.
9
9
  //
10
- // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
- // GO AFTER THE REQUIRES BELOW.
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 top of the
9
- * compiled file, but it's generally better to create a new file per style scope.
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
  */