From dd40cc016af5e8a927c042ac9ee8fde225035028 Mon Sep 17 00:00:00 2001 From: Allen Taylor Date: Tue, 26 Nov 2019 14:29:46 -0500 Subject: [PATCH] Cache Touchpoint Form --- .cfignore | 1 - .env.sample | 3 +++ .gitignore | 1 - Gemfile | 9 ++------ Gemfile.lock | 3 +-- app/controllers/submissions_controller.rb | 6 ++--- app/models/form.rb | 17 +++++++------- app/models/form_section.rb | 7 +++--- app/models/question.rb | 4 ++++ app/models/question_option.rb | 4 ++++ app/models/touchpoint.rb | 4 ++++ app/models/touchpoint_cache.rb | 18 +++++++++++++++ config/environments/development.rb | 18 ++------------- config/environments/production.rb | 3 +++ config/environments/staging.rb | 3 +++ spec/features/touchpoints_spec.rb | 1 + spec/models/touchpoint_cache_spec.rb | 27 +++++++++++++++++++++++ 17 files changed, 88 insertions(+), 41 deletions(-) create mode 100644 app/models/touchpoint_cache.rb create mode 100644 spec/models/touchpoint_cache_spec.rb diff --git a/.cfignore b/.cfignore index f9b84d192..986b207c1 100644 --- a/.cfignore +++ b/.cfignore @@ -21,7 +21,6 @@ /yarn-error.log /public/assets -.byebug_history # Ignore master key for decrypting credentials and more. /config/master.key diff --git a/.env.sample b/.env.sample index 862f4e90b..ff711957a 100644 --- a/.env.sample +++ b/.env.sample @@ -24,6 +24,9 @@ LOGIN_GOV_REDIRECT_URI='http://localhost:3002/users/auth/login_dot_gov/callback' # For New Relic NEW_RELIC_KEY=YOUR-NEW-RELIC-KEY-HERE +# Redis cache store +REDIS_CACHE_STORE=redis://localhost:6379/1 + # For image uploads S3_AWS_ACCESS_KEY_ID= S3_AWS_SECRET_ACCESS_KEY= diff --git a/.gitignore b/.gitignore index ebc474550..e1f38fa48 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,6 @@ /yarn-error.log /public/assets -.byebug_history # Ignore master key for decrypting credentials and more. /config/master.key diff --git a/Gemfile b/Gemfile index 6649c9059..44689d689 100644 --- a/Gemfile +++ b/Gemfile @@ -22,17 +22,12 @@ gem 'rack-cors', require: 'rack/cors' gem 'sass-rails' gem 'sidekiq' gem 'uglifier' -# Use Redis adapter to run Action Cable in production -# gem 'redis', '~> 4.0' +# Use Redis to cache Touchpoints in all envs +gem 'redis', '~> 4.0' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' -# Use ActiveStorage variant -# gem 'mini_magick', '~> 4.8' - group :development, :test do - # Call 'byebug' anywhere in the code to stop execution and get a debugger console - gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'dotenv-rails' gem 'pry' gem 'rspec_junit_formatter' diff --git a/Gemfile.lock b/Gemfile.lock index 26f9a29e6..f22a8bfcc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,7 +77,6 @@ GEM bindata (2.4.4) bindex (0.8.1) builder (3.2.3) - byebug (11.0.1) capybara (3.29.0) addressable mini_mime (>= 0.1.3) @@ -330,7 +329,6 @@ PLATFORMS DEPENDENCIES aws-sdk-rails - byebug capybara caracal carrierwave @@ -352,6 +350,7 @@ DEPENDENCIES puma rack-cors rails (~> 5.2.1) + redis (~> 4.0) rspec-rails rspec_junit_formatter sass-rails diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 37af58a0b..9890a3516 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -41,7 +41,7 @@ def create end @submission = Submission.new(submission_params) - @submission.touchpoint_id = @touchpoint.id + @submission.touchpoint = @touchpoint @submission.user_agent = request.user_agent @submission.referer = submission_params[:referer] @submission.page = submission_params[:page] @@ -90,9 +90,9 @@ def create_in_local_database(submission) def set_touchpoint if params[:touchpoint] # coming from /touchpoints/:id/submit - @touchpoint = Touchpoint.find(params[:id]) + @touchpoint = TouchpointCache.fetch(params[:id]) else - @touchpoint = Touchpoint.find(params[:touchpoint_id]) + @touchpoint = TouchpointCache.fetch(params[:touchpoint_id]) end raise InvalidArgument("Touchpoint does not exist") unless @touchpoint end diff --git a/app/models/form.rb b/app/models/form.rb index 300af3ba0..86ff143ab 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -7,17 +7,18 @@ class Form < ApplicationRecord validates :character_limit, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100000 } validates_length_of :disclaimer_text, in: 0..500, allow_blank: true - after_create :create_first_form_section - - def create_first_form_section - self.form_sections.create(title: (I18n.t 'form.page_1'), position: 1) + after_initialize do |user| + self.modal_button_text = I18n.t('form.help_improve') + self.success_text = I18n.t('form.submit_thankyou') end - def success_text - super.present? ? super : (I18n.t 'form.submit_thankyou') + after_create :create_first_form_section + + after_save do |form| + TouchpointCache.invalidate(form.touchpoint.id) if form.touchpoint.present? end - def modal_button_text - super.present? ? super : (I18n.t 'form.help_improve') + def create_first_form_section + self.form_sections.create(title: (I18n.t 'form.page_1'), position: 1) end end diff --git a/app/models/form_section.rb b/app/models/form_section.rb index 19b14b3fe..9597d257a 100644 --- a/app/models/form_section.rb +++ b/app/models/form_section.rb @@ -2,9 +2,10 @@ class FormSection < ApplicationRecord belongs_to :form has_many :questions + after_save do | form_section | + TouchpointCache.invalidate(form_section.form.touchpoint.id) if form_section.form.touchpoint.present? + end + default_scope { order(position: :asc) } - def title - super ? super : "Unnamed Form Section Title" - end end diff --git a/app/models/question.rb b/app/models/question.rb index 3ba09df50..96fdb1d9d 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -6,5 +6,9 @@ class Question < ApplicationRecord validates :answer_field, presence: true validates :character_limit, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 100000, allow_nil: true } + after_save do | question | + TouchpointCache.invalidate(question.form.touchpoint.id) if question.form.touchpoint.present? + end + default_scope { order(position: :asc) } end diff --git a/app/models/question_option.rb b/app/models/question_option.rb index a0a60350d..0a77e85c6 100644 --- a/app/models/question_option.rb +++ b/app/models/question_option.rb @@ -3,5 +3,9 @@ class QuestionOption < ApplicationRecord validates :position, presence: true + after_save do | question_option | + TouchpointCache.invalidate(question_option.question.form.touchpoint.id) if question_option.question.form.touchpoint.present? + end + default_scope { order(position: :asc) } end diff --git a/app/models/touchpoint.rb b/app/models/touchpoint.rb index 267202131..52c10c1af 100644 --- a/app/models/touchpoint.rb +++ b/app/models/touchpoint.rb @@ -9,6 +9,10 @@ class Touchpoint < ApplicationRecord validate :omb_number_with_expiration_date + after_save do |touchpoint| + TouchpointCache.invalidate(touchpoint.id) + end + def omb_number_with_expiration_date if omb_approval_number.present? && !expiration_date.present? errors.add(:expiration_date, "required with an OMB Number") diff --git a/app/models/touchpoint_cache.rb b/app/models/touchpoint_cache.rb new file mode 100644 index 000000000..a167ce6f7 --- /dev/null +++ b/app/models/touchpoint_cache.rb @@ -0,0 +1,18 @@ +class TouchpointCache + + TOUCHPOINT_NAMESPACE = "namespace:touchpoint-" + + # Cache Store fetch will return the cached item + # or run the block if the cached item does not exist + def self.fetch(id) + Rails.cache.fetch(TOUCHPOINT_NAMESPACE + id.to_s, expires_in: 1.day) do + #Pull in all objects required to build a touchpoint + Touchpoint.includes({form: [:questions, form_sections: [questions: [:question_options]]]}, service: [:organization]).find(id) + end + end + + def self.invalidate(id) + Rails.cache.delete(TOUCHPOINT_NAMESPACE + id.to_s) + end + +end diff --git a/config/environments/development.rb b/config/environments/development.rb index 8e5d8a0a8..646104481 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,11 +1,6 @@ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. - # In the development environment your application's code is reloaded on - # every request. This slows down response time but is perfect for development - # since you don't have to restart the web server when you make code changes. - config.cache_classes = false - # Do not eager load code on boot. config.eager_load = false @@ -14,17 +9,8 @@ # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. - if Rails.root.join('tmp', 'caching-dev.txt').exist? - config.action_controller.perform_caching = true - - config.cache_store = :memory_store - config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{2.days.to_i}" - } - else - config.action_controller.perform_caching = false - - config.cache_store = :null_store + Rails.application.configure do + config.cache_store = :redis_cache_store, { url: ENV["REDIS_CACHE_STORE"] } end # Store uploaded files on the local file system (see config/storage.yml for options) diff --git a/config/environments/production.rb b/config/environments/production.rb index d5738ad29..6a44e552e 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -58,6 +58,9 @@ # Use a different cache store in production. # config.cache_store = :mem_cache_store + Rails.application.configure do + config.cache_store = :redis_cache_store, { url: ENV["REDIS_CACHE_STORE"] } + end # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 91dbc15f5..e036bb027 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -58,6 +58,9 @@ # Use a different cache store in production. # config.cache_store = :mem_cache_store + Rails.application.configure do + config.cache_store = :redis_cache_store, { url: ENV["REDIS_CACHE_STORE"] } + end # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque diff --git a/spec/features/touchpoints_spec.rb b/spec/features/touchpoints_spec.rb index 45b115c62..216d2e2e0 100644 --- a/spec/features/touchpoints_spec.rb +++ b/spec/features/touchpoints_spec.rb @@ -27,6 +27,7 @@ context "custom success text" do before do touchpoint.form.update_attribute(:success_text, "Much success, yessss.") + touchpoint.reload visit touchpoint_path(touchpoint) expect(page.current_path).to eq("/touchpoints/#{touchpoint.id}/submit") expect(page).to have_content("OMB Approval ##{touchpoint.omb_approval_number}") diff --git a/spec/models/touchpoint_cache_spec.rb b/spec/models/touchpoint_cache_spec.rb new file mode 100644 index 000000000..a34a5624f --- /dev/null +++ b/spec/models/touchpoint_cache_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +RSpec.describe TouchpointCache, type: :model do + + let!(:touchpoint) { FactoryBot.create(:touchpoint, :with_form) } + + describe "validate cache fetch" do + context "Store and Fetch Touchpoint" do + it "caches a touchpoint" do + @tpc = TouchpointCache.fetch(touchpoint.id) + expect(touchpoint.id).to eq(@tpc.id) + end + end + + context "Invalidate Cache" do + before do + @tpc = TouchpointCache.fetch(touchpoint.id) + expect(touchpoint.id).to eq(@tpc.id) + TouchpointCache.invalidate(touchpoint.id) + end + + it "removes a touchpoint from cache" do + expect(Rails.cache.read("namespace:touchpoint-" + touchpoint.id.to_s)).to eq(nil) + end + end + end +end