rails_web_cache 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +11 -0
  6. data/Gemfile +15 -0
  7. data/Gemfile.lock +178 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +43 -0
  10. data/Rakefile +28 -0
  11. data/app/assets/config/rails_web_cache_manifest.js +2 -0
  12. data/app/assets/images/rails_web_cache/.keep +0 -0
  13. data/app/assets/javascripts/rails_web_cache/application.js +17 -0
  14. data/app/assets/javascripts/rails_web_cache/bootstrap.js +6 -0
  15. data/app/assets/javascripts/rails_web_cache/jquery.js +2 -0
  16. data/app/assets/javascripts/rails_web_cache/keys.js +22 -0
  17. data/app/assets/javascripts/rails_web_cache/popper.js +5 -0
  18. data/app/assets/stylesheets/rails_web_cache/application.css +14 -0
  19. data/app/assets/stylesheets/rails_web_cache/bootstrap.css +7 -0
  20. data/app/controllers/rails_web_cache/application_controller.rb +7 -0
  21. data/app/controllers/rails_web_cache/keys_controller.rb +84 -0
  22. data/app/helpers/rails_web_cache/keys_helper.rb +18 -0
  23. data/app/views/layouts/rails_web_cache/application.html.erb +16 -0
  24. data/app/views/rails_web_cache/keys/index.html.erb +95 -0
  25. data/app/views/rails_web_cache/keys/show.html.erb +26 -0
  26. data/app/views/rails_web_cache/shared/_header.html.erb +17 -0
  27. data/bin/rails +20 -0
  28. data/config/routes.rb +11 -0
  29. data/lib/rails_web_cache/base.rb +44 -0
  30. data/lib/rails_web_cache/engine.rb +21 -0
  31. data/lib/rails_web_cache/entry.rb +19 -0
  32. data/lib/rails_web_cache/file_store.rb +9 -0
  33. data/lib/rails_web_cache/memory_store.rb +9 -0
  34. data/lib/rails_web_cache/redis_cache_store.rb +23 -0
  35. data/lib/rails_web_cache/version.rb +5 -0
  36. data/lib/rails_web_cache.rb +46 -0
  37. data/rails_web_cache.gemspec +39 -0
  38. data/spec/controllers/keys_controller_spec.rb +41 -0
  39. data/spec/dummy/Rakefile +6 -0
  40. data/spec/dummy/app/assets/config/manifest.js +3 -0
  41. data/spec/dummy/app/assets/javascripts/application.js +14 -0
  42. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  43. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  44. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  45. data/spec/dummy/bin/bundle +5 -0
  46. data/spec/dummy/bin/rails +6 -0
  47. data/spec/dummy/bin/rake +6 -0
  48. data/spec/dummy/bin/setup +38 -0
  49. data/spec/dummy/bin/update +33 -0
  50. data/spec/dummy/bin/yarn +13 -0
  51. data/spec/dummy/config/application.rb +23 -0
  52. data/spec/dummy/config/boot.rb +7 -0
  53. data/spec/dummy/config/environment.rb +7 -0
  54. data/spec/dummy/config/environments/development.rb +47 -0
  55. data/spec/dummy/config/environments/production.rb +74 -0
  56. data/spec/dummy/config/environments/test.rb +40 -0
  57. data/spec/dummy/config/initializers/application_controller_renderer.rb +10 -0
  58. data/spec/dummy/config/initializers/assets.rb +14 -0
  59. data/spec/dummy/config/initializers/backtrace_silencers.rb +8 -0
  60. data/spec/dummy/config/initializers/content_security_policy.rb +26 -0
  61. data/spec/dummy/config/initializers/cookies_serializer.rb +7 -0
  62. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  63. data/spec/dummy/config/initializers/inflections.rb +18 -0
  64. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  65. data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
  66. data/spec/dummy/config/locales/en.yml +33 -0
  67. data/spec/dummy/config/puma.rb +39 -0
  68. data/spec/dummy/config/routes.rb +5 -0
  69. data/spec/dummy/config/spring.rb +8 -0
  70. data/spec/dummy/config.ru +7 -0
  71. data/spec/dummy/log/development.log +630 -0
  72. data/spec/dummy/log/test.log +1791 -0
  73. data/spec/dummy/public/404.html +67 -0
  74. data/spec/dummy/public/422.html +67 -0
  75. data/spec/dummy/public/500.html +66 -0
  76. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  77. data/spec/dummy/public/apple-touch-icon.png +0 -0
  78. data/spec/dummy/public/favicon.ico +0 -0
  79. data/spec/dummy/tmp/development_secret.txt +1 -0
  80. data/spec/helpers/keys_helper_spec.rb +28 -0
  81. data/spec/rails_helper.rb +50 -0
  82. data/spec/rails_web_cache_spec.rb +37 -0
  83. data/spec/routing/keys_controller_spec.rb +31 -0
  84. data/spec/spec_helper.rb +101 -0
  85. metadata +237 -0
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsWebCache
4
+ class KeysController < RailsWebCache::ApplicationController
5
+ before_action :set_key, only: %i[show destroy]
6
+ before_action :set_page, only: :index
7
+ before_action :set_per_page, only: :index
8
+ before_action :set_offset, only: :index
9
+
10
+ # GET /keys
11
+ def index
12
+ @keys = keys
13
+ @pages = (@keys.size / @per_page.to_f).ceil
14
+ @keys = (@keys[@offset, @per_page] || []).map.with_index do |key, idx|
15
+ format_key(key, idx)
16
+ end
17
+ end
18
+
19
+ # GET /keys/:key
20
+ def show
21
+ @value = cache.read(@key)
22
+ @entry = cache.entry(@key)
23
+ end
24
+
25
+ # DELETE /keys/:key
26
+ def destroy
27
+ cache.delete(@key)
28
+ redirect_to root_path
29
+ end
30
+
31
+ # DELETE /keys/all/:keys
32
+ def destroy_all
33
+ keys = params[:keys].presence || []
34
+ keys.each do |key|
35
+ cache.delete(key)
36
+ end
37
+ redirect_to root_path
38
+ end
39
+
40
+ private
41
+
42
+ def cache
43
+ @cache ||= RailsWebCache.cache
44
+ end
45
+
46
+ def format_key(key, idx)
47
+ entry = cache.entry(key)
48
+ {
49
+ id: idx,
50
+ key: key,
51
+ value: cache.read(key),
52
+ expires_at: entry&.expires_at,
53
+ size: entry&.size
54
+ }
55
+ end
56
+
57
+ def set_key
58
+ @key = params[:key]
59
+ end
60
+
61
+ def set_page
62
+ page = (params[:page].presence || 1).to_i
63
+ page = 1 if page.negative? || page > (2**32)
64
+ @page = page
65
+ end
66
+
67
+ def set_offset
68
+ @offset = (@page - 1) * @per_page
69
+ end
70
+
71
+ def set_per_page
72
+ @per_page = 20
73
+ end
74
+
75
+ def keys
76
+ query = params[:query].presence
77
+ if query.present?
78
+ cache.search(query)
79
+ else
80
+ cache.keys
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsWebCache
4
+ module KeysHelper
5
+ def expired_at(value)
6
+ return 'n/a' unless value.present?
7
+ content_tag(:time,
8
+ value.strftime('%d %b %Y - %H:%M'),
9
+ datetime: value.iso8601)
10
+ end
11
+
12
+ def check_box_key(value, id)
13
+ (label_tag "key-#{id}", value, class: 'd-none').concat(
14
+ check_box_tag('keys[]', value, nil, id: "key-#{id}")
15
+ )
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>
5
+ Rails web cache
6
+ </title>
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+ <%= stylesheet_link_tag 'rails_web_cache/application', media: 'all' %>
10
+ <%= javascript_include_tag 'rails_web_cache/application' %>
11
+ </head>
12
+ <body>
13
+ <%= render partial: 'rails_web_cache/shared/header' %>
14
+ <%= yield %>
15
+ </body>
16
+ </html>
@@ -0,0 +1,95 @@
1
+ <div class="container mt-4">
2
+ <div class="row">
3
+ <div class="col-12">
4
+ <%= form_tag(root_path, method: :get, class: 'mb-4') do %>
5
+ <div class="input-group">
6
+ <%= text_field_tag :query, params[:query], class: 'form-control', placeholder: 'Search key(s)' %>
7
+ <div class="input-group-append">
8
+ <%= submit_tag 'Search', class: 'btn btn-primary', name: nil %>
9
+ </div>
10
+ </div>
11
+ <% end %>
12
+ </div>
13
+ </div>
14
+ <div class="row">
15
+ <div class="col-12">
16
+ <%= form_tag(destroy_all_keys_path, method: :delete) do %>
17
+ <div class="table-responsive">
18
+ <table class="table table-striped table-bordered">
19
+ <thead>
20
+ <tr>
21
+ <th>
22
+ <%= label_tag 'select-all-keys', 'Select all keys', class: 'd-none' %>
23
+ <%= check_box_tag 'select-all-keys', nil %>
24
+ </th>
25
+ <th>Key</th>
26
+ <th>Value</th>
27
+ <th>Expires at</th>
28
+ <th>Size</th>
29
+ <th>Actions</th>
30
+ </tr>
31
+ </thead>
32
+ <tbody>
33
+ <% @keys.each do |key| %>
34
+ <tr>
35
+ <td class="align-middle">
36
+ <%= check_box_key(key[:key], key[:id]) %>
37
+ </td>
38
+ <td class="align-middle">
39
+ <%= truncate(key[:key], length: 40) %>
40
+ </td>
41
+ <td class="align-middle">
42
+ <%= truncate(key[:value], length: 40) %>
43
+ </td>
44
+ <td class="align-middle">
45
+ <%= expired_at(key[:expires_at]) %>
46
+ </td>
47
+ <td class="align-middle text-center">
48
+ <%= number_to_human_size(key[:size]) %>
49
+ </td>
50
+ <td class="align-middle">
51
+ <div class="d-flex flex-row">
52
+ <%= link_to 'Show', key_path(key[:key]), title: "Show #{key[:key]}", class: 'btn btn-sm btn-primary w-100' %>
53
+ <%= link_to 'Delete', key_path(key[:key]), method: :delete, data: {confirm: 'Are you sure ?'}, title: "Delete #{key[:key]}", class: 'btn btn-sm btn-danger w-100 ml-2' %>
54
+ </div>
55
+ </td>
56
+ </tr>
57
+ <% end %>
58
+ </tbody>
59
+ </table>
60
+ </div>
61
+ <%= submit_tag 'Delete selected keys', class: 'btn btn-danger w-100 mb-4 disabled', id: 'delete-all-keys' %>
62
+ <% end %>
63
+ </div>
64
+ <div class="col-12">
65
+ <div class="d-flex justify-content-center">
66
+ <nav aria-label="Rails Web Cache">
67
+ <ul class="pagination">
68
+ <li class="page-item <%= @page <= 1 ? 'disabled' : '' %>">
69
+ <%= link_to 'Previous', root_path(params.merge(page: @page - 1 <= 0 ? 1 : @page - 1).permit(:query, :page)), title: "Pages - #{@page - 1 <= 0 ? 1 : @page - 1}", class: 'page-link' %>
70
+ </li>
71
+ <% if @pages > 2 %>
72
+ <li class="page-item">
73
+ <%= link_to (@pages - @pages) + 1, root_path(params.merge(page: 1).permit(:query, :page)), title: "Pages - #{(@pages - @pages) + 1}", class: 'page-link' %>
74
+ </li>
75
+ <% if @page > 2 %>
76
+ <li class="page-item">
77
+ <%= link_to @page - 1, root_path(params.merge(page: @page - 1).permit(:query, :page)), title: "Pages - #{@page - 1}", class: 'page-link' %>
78
+ </li>
79
+ <% end %>
80
+ <li class="page-item">
81
+ <%= link_to @page + 1, root_path(params.merge(page: @page + 1).permit(:query, :page)), title: "Pages - #{@page + 1}", class: 'page-link' %>
82
+ </li>
83
+ <li class="page-item">
84
+ <%= link_to @pages - 1, root_path(params.merge(page: @pages - 1).permit(:query, :page)), title: "Pages - #{@pages - 1}", class: 'page-link' %>
85
+ </li>
86
+ <% end %>
87
+ <li class="page-item <%= @page >= @pages ? 'disabled' : '' %>">
88
+ <%= link_to 'Next', root_path(params.merge(page: @page + 1 > @pages ? @pages : @page + 1).permit(:query, :page)), title: "Pages - #{@page + 1 > @pages ? @pages : @page + 1}", class: 'page-link' %>
89
+ </li>
90
+ </ul>
91
+ </nav>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
@@ -0,0 +1,26 @@
1
+ <div class="container mt-4">
2
+ <div class="row">
3
+ <div class="col-12">
4
+ <label>Key:</label>
5
+ <p>
6
+ <code><%= @key %></code>
7
+ </p>
8
+ <label>Value:</label>
9
+ <p>
10
+ <code><%= @value %></code>
11
+ </p>
12
+ <label>Expires at:</label>
13
+ <p>
14
+ <code><%= expired_at(@entry.expires_at) %></code>
15
+ </p>
16
+ <label>Size:</label>
17
+ <p>
18
+ <code><%= number_to_human_size(@entry.size, locale: :en) %></code>
19
+ </p>
20
+ <p>
21
+ <%= link_to 'Delete', key_path(@key), method: :delete, data: {confirm: 'Are you sure ?'}, title: "Delete #{@key}", class: 'btn btn-sm btn-danger w-100 mb-2' %>
22
+ <%= link_to 'Back', keys_path, title: 'View keys', class: 'btn btn-sm btn-primary w-100' %>
23
+ </p>
24
+ </div>
25
+ </div>
26
+ </div>
@@ -0,0 +1,17 @@
1
+ <nav class="navbar navbar-expand-lg navbar-light bg-light">
2
+ <div class="container">
3
+ <%= link_to 'Rails Web Cache', root_path, title: 'Rails Web Cache', class: 'navbar-brand' %>
4
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Rails Web Cache">
5
+ <span class="navbar-toggler-icon"></span>
6
+ </button>
7
+ <div class="collapse navbar-collapse" id="navbarSupportedContent">
8
+ <ul class="navbar-nav mr-auto">
9
+ <li class="nav-item">
10
+ <%= link_to keys_path, title: 'View keys', class: 'nav-link' do %>
11
+ Keys <span class="badge badge-info badge-pill"><%= RailsWebCache.cache.keys_size %></span>
12
+ <% end %>
13
+ </li>
14
+ </ul>
15
+ </div>
16
+ </div>
17
+ </nav>
data/bin/rails ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # This command will automatically be run when you run "rails" with Rails gems
5
+ # installed from the root of your application.
6
+
7
+ ENGINE_ROOT = File.expand_path('..', __dir__)
8
+ ENGINE_PATH = File.expand_path('../lib/rails_web_cache/engine', __dir__)
9
+ APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__)
10
+
11
+ # Set up gems listed in the Gemfile.
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
13
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
14
+
15
+ require 'rails'
16
+ # Pick the frameworks you want:
17
+ require 'action_controller/railtie'
18
+ require 'action_view/railtie'
19
+ require 'sprockets/railtie'
20
+ require 'rails/engine/commands'
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ RailsWebCache::Engine.routes.draw do
4
+ resources :keys, only: %i[index show destroy], param: :key do
5
+ collection do
6
+ delete :destroy_all
7
+ end
8
+ end
9
+
10
+ root to: 'keys#index'
11
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsWebCache
4
+ class Base
5
+ attr_reader :cache
6
+
7
+ def initialize(cache)
8
+ @cache = cache
9
+ end
10
+
11
+ def keys_size
12
+ keys.size
13
+ end
14
+
15
+ def clear
16
+ cache.clear
17
+ end
18
+
19
+ def read(key)
20
+ cache.read(key) if key
21
+ end
22
+
23
+ def delete(key)
24
+ cache.delete(key) if key
25
+ end
26
+
27
+ def entry(key, options = {})
28
+ return nil unless key
29
+ entry = read_entry(key, options)
30
+ return nil unless entry
31
+ RailsWebCache::Entry.new(entry)
32
+ end
33
+
34
+ def search(query = '')
35
+ keys.select { |key| key.downcase.include?(query.downcase) } if query
36
+ end
37
+
38
+ private
39
+
40
+ def read_entry(key, options = {})
41
+ cache.send(:read_entry, key, options)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsWebCache
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace RailsWebCache
6
+
7
+ path = Engine.root.join('app', 'assets', 'config')
8
+ config.assets.precompile << "#{path}/rails_web_cache_manifest.js"
9
+
10
+ config.generators do |generate|
11
+ # Don't generate assets
12
+ generate.assets false
13
+
14
+ # Don't generate helper
15
+ generate.helper false
16
+
17
+ # Rspec
18
+ generate.test_framework :rspec
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsWebCache
4
+ class Entry
5
+ attr_reader :entry
6
+
7
+ def initialize(entry)
8
+ @entry = entry
9
+ end
10
+
11
+ def expires_at
12
+ entry.expires_at ? Time.at(entry.expires_at) : nil
13
+ end
14
+
15
+ def size
16
+ entry.size
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsWebCache
4
+ class FileStore < Base
5
+ def keys
6
+ cache.instance_variable_get(:@data).keys
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsWebCache
4
+ class MemoryStore < Base
5
+ def keys
6
+ cache.instance_variable_get(:@data).keys
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsWebCache
4
+ class RedisCacheStore < Base
5
+ def keys_size
6
+ redis.dbsize
7
+ end
8
+
9
+ def keys
10
+ redis.keys
11
+ end
12
+
13
+ def search(query = '')
14
+ redis.scan_each(match: "*#{query.downcase}*").to_a if query
15
+ end
16
+
17
+ private
18
+
19
+ def redis
20
+ @redis ||= cache.redis
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsWebCache
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_web_cache/engine'
4
+ require 'rails_web_cache/base'
5
+ require 'rails_web_cache/entry'
6
+ require 'rails_web_cache/file_store'
7
+ require 'rails_web_cache/memory_store'
8
+ require 'rails_web_cache/redis_cache_store'
9
+
10
+ module RailsWebCache
11
+ class << self
12
+ def cache
13
+ @cache ||= driver(Rails.cache)
14
+ end
15
+
16
+ private
17
+
18
+ def driver(cache)
19
+ if redis_cache_store?(cache)
20
+ RedisCacheStore.new(cache)
21
+ elsif memory_store?(cache)
22
+ MemoryStore.new(cache)
23
+ elsif file_store?(cache)
24
+ FileStore.new(cache)
25
+ else
26
+ raise ArgumentError, "RailsWebCache doesn't support #{cache.class}"
27
+ end
28
+ end
29
+
30
+ def redis_cache_store?(driver)
31
+ defined?(::Redis) &&
32
+ defined?(::ActiveSupport::Cache::RedisCacheStore) &&
33
+ driver.is_a?(::ActiveSupport::Cache::RedisCacheStore)
34
+ end
35
+
36
+ def memory_store?(driver)
37
+ defined?(::ActiveSupport::Cache::MemoryStore) &&
38
+ driver.is_a?(::ActiveSupport::Cache::MemoryStore)
39
+ end
40
+
41
+ def file_store?(driver)
42
+ defined?(::ActiveSupport::Cache::FileStore) &&
43
+ driver.is_a?(::ActiveSupport::Cache::FileStore)
44
+ end
45
+ end
46
+ end