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.
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
  */