diff --git a/Gemfile b/Gemfile index cfd40a2..7adc450 100644 --- a/Gemfile +++ b/Gemfile @@ -61,6 +61,8 @@ gem 'mini_racer' gem 'unicorn' +gem 'redis-rails' + group :development, :test do gem 'rspec-rails', '~> 5.0.0' gem 'factory_bot_rails' diff --git a/Gemfile.lock b/Gemfile.lock index f748b18..6f938de 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -330,6 +330,22 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) redis (4.1.4) + redis-actionpack (5.2.0) + actionpack (>= 5, < 7) + redis-rack (>= 2.1.0, < 3) + redis-store (>= 1.1.0, < 2) + redis-activesupport (5.2.1) + activesupport (>= 3, < 7) + redis-store (>= 1.3, < 2) + redis-rack (2.1.3) + rack (>= 2.0.8, < 3) + redis-store (>= 1.2, < 2) + redis-rails (5.0.2) + redis-actionpack (>= 5.0, < 6) + redis-activesupport (>= 5.0, < 6) + redis-store (>= 1.2, < 2) + redis-store (1.9.0) + redis (>= 4, < 5) regexp_parser (2.1.1) request_store (1.5.0) rack (>= 1.4) @@ -515,6 +531,7 @@ DEPENDENCIES rack-mini-profiler (~> 2.0) rails (~> 6.1.3, >= 6.1.3.1) rails-controller-testing + redis-rails rspec-rails (~> 5.0.0) rubocop rubocop-performance diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be79..77fd7b1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,8 @@ module ApplicationHelper + def collection_cache_helper_for(model) + klass = model.to_s.capitalize.constantize + count = klass.count + max_updated_at = klass.maximum(:updated_at).try(:utc).try(:to_s, :number) + "#{model.to_s.pluralize}/collection-#{count}-#{max_updated_at}" + end end diff --git a/app/models/answer.rb b/app/models/answer.rb index 0c60c96..2431ab1 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -3,7 +3,7 @@ class Answer < ApplicationRecord include Votable include Commentable - belongs_to :question + belongs_to :question, touch: true belongs_to :author, class_name: 'User', foreign_key: 'user_id' has_many_attached :files diff --git a/app/models/comment.rb b/app/models/comment.rb index 19cf3f8..5437da1 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,6 +1,6 @@ class Comment < ApplicationRecord belongs_to :user - belongs_to :commentable, polymorphic: true + belongs_to :commentable, polymorphic: true, touch: true validates :body, presence: true diff --git a/app/models/link.rb b/app/models/link.rb index a4d34e6..44d8c62 100644 --- a/app/models/link.rb +++ b/app/models/link.rb @@ -1,5 +1,5 @@ class Link < ApplicationRecord - belongs_to :linkable, polymorphic: true + belongs_to :linkable, polymorphic: true, touch: true validates :name, presence: true validates :url, presence: true, url: { no_local: true } diff --git a/app/views/answers/_answer.html.slim b/app/views/answers/_answer.html.slim index 9490da7..901c1b0 100644 --- a/app/views/answers/_answer.html.slim +++ b/app/views/answers/_answer.html.slim @@ -1,36 +1,37 @@ -li.answer id="answer-#{answer.id}" - .answer-errors - = render 'shared/errors' +- cache answer do + li.answer id="answer-#{answer.id}" + .answer-errors + = render 'shared/errors' - div - = "Author: #{answer.author.email}" - div - = answer.body + div + = "Author: #{answer.author.email}" + div + = answer.body - .answer-links - = render answer.links + .answer-links + = render answer.links - div - - if answer.files.attached? - = render partial: 'attachments/attachment', collection: answer.files - - div id="votable-answer-#{answer.id}" - = render 'votes/rating', votable: answer - - if signed_in? && can?(:create_vote, answer) - = render 'votes/vote', vote: answer.vote_of(current_user), votable: answer - - - if can?(:destroy, answer) - div - = link_to 'Delete answer', answer_path(answer), method: :delete, remote: true - - if can?(:update, answer) div - = link_to 'Edit answer', '#', class: 'edit-answer-link', data: { answer_id: answer.id } + - if answer.files.attached? + = render partial: 'attachments/attachment', collection: answer.files + + div id="votable-answer-#{answer.id}" + = render 'votes/rating', votable: answer + - if signed_in? && can?(:create_vote, answer) + = render 'votes/vote', vote: answer.vote_of(current_user), votable: answer - div.hidden - = render partial: '/answers/answer_form', locals: { answer: answer, html_data: { id: "edit-answer-#{answer.id}" } } + - if can?(:destroy, answer) + div + = link_to 'Delete answer', answer_path(answer), method: :delete, remote: true + - if can?(:update, answer) + div + = link_to 'Edit answer', '#', class: 'edit-answer-link', data: { answer_id: answer.id } - -if can?(:mark_best, answer) - div - = link_to 'Best answer', mark_best_answer_path(answer), method: :patch, class: 'best-answer-link', remote: true + div.hidden + = render partial: '/answers/answer_form', locals: { answer: answer, html_data: { id: "edit-answer-#{answer.id}" } } + + -if can?(:mark_best, answer) + div + = link_to 'Best answer', mark_best_answer_path(answer), method: :patch, class: 'best-answer-link', remote: true - = render "comments/comments", commentable: answer \ No newline at end of file + = render "comments/comments", commentable: answer \ No newline at end of file diff --git a/app/views/answers/_answer_form.html.slim b/app/views/answers/_answer_form.html.slim index 8767ef4..563a450 100644 --- a/app/views/answers/_answer_form.html.slim +++ b/app/views/answers/_answer_form.html.slim @@ -1,15 +1,16 @@ -= form_with model: answer, html: try(:html_data) || {}, local: false do |f| - div - = f.label :body - div - = f.text_area :body +- cache answer do + = form_with model: answer, html: try(:html_data) || {}, local: false do |f| + div + = f.label :body + div + = f.text_area :body - = render 'links/links_form', f: f + = render 'links/links_form', f: f - div - = f.label :files - div - = f.file_field :files, multiple: true, direct_upload: true + div + = f.label :files + div + = f.file_field :files, multiple: true, direct_upload: true - div - = f.submit 'Answer' \ No newline at end of file + div + = f.submit 'Answer' \ No newline at end of file diff --git a/app/views/comments/_comment.html.slim b/app/views/comments/_comment.html.slim index 2eb8e1b..5611a8a 100644 --- a/app/views/comments/_comment.html.slim +++ b/app/views/comments/_comment.html.slim @@ -1,2 +1,3 @@ -div id="comment-#{comment.id}" - = comment.body \ No newline at end of file +- cache comment do + div id="comment-#{comment.id}" + = comment.body \ No newline at end of file diff --git a/app/views/links/_link_fields.html.slim b/app/views/links/_link_fields.html.slim index a8fdf90..2bca976 100644 --- a/app/views/links/_link_fields.html.slim +++ b/app/views/links/_link_fields.html.slim @@ -1,8 +1,9 @@ -.nested-fields - .field - div = f.label :name - div = f.text_field :name - .field - div = f.label :url - div = f.text_field :url - = link_to_remove_association "Remove link", f \ No newline at end of file +- cache f do + .nested-fields + .field + div = f.label :name + div = f.text_field :name + .field + div = f.label :url + div = f.text_field :url + = link_to_remove_association "Remove link", f \ No newline at end of file diff --git a/app/views/questions/_question_form.html.slim b/app/views/questions/_question_form.html.slim index 2df3a6d..ffd7de1 100644 --- a/app/views/questions/_question_form.html.slim +++ b/app/views/questions/_question_form.html.slim @@ -1,18 +1,19 @@ -= form_with model: question, local: local do |f| - div = f.label :title - div = f.text_field :title +- cache question do + = form_with model: question, local: local do |f| + div = f.label :title + div = f.text_field :title - div = f.label :body - div = f.text_area :body + div = f.label :body + div = f.text_area :body - = render 'links/links_form', f: f + = render 'links/links_form', f: f - div = f.label :files - div = f.file_field :files, multiple: true, direct_upload: true + div = f.label :files + div = f.file_field :files, multiple: true, direct_upload: true - .award - h3 Award for the best answer - = f.fields_for :award do |award_f| - = render 'awards/award_fields', f: award_f + .award + h3 Award for the best answer + = f.fields_for :award do |award_f| + = render 'awards/award_fields', f: award_f - div = f.submit 'Ask' + div = f.submit 'Ask' diff --git a/app/views/questions/_question_full.html.slim b/app/views/questions/_question_full.html.slim index 893b900..4b4deb7 100644 --- a/app/views/questions/_question_full.html.slim +++ b/app/views/questions/_question_full.html.slim @@ -1,37 +1,38 @@ -div.question id="question-#{question.id}" - .question-errors - = render 'shared/errors' - - h4 = "Title: #{question.title}" - div = "Author: #{question.author.email}" - div = question.body - - .question-links - = render question.links - - -if question.files.attached? - = render partial: 'attachments/attachment', collection: question.files - - div id="votable-question-#{question.id}" - = render 'votes/rating', votable: question - - if signed_in? && can?(:create_vote, question) - = render 'votes/vote', vote: question.vote_of(current_user), votable: question - - - if signed_in? - = render 'subscriptions/subscription', question: question - - - if question.award.present? - .question-award - h3 = "For the best answer you will receive #{question.award.name}" - .question-award-image - = image_tag question.award.image, size:"250x250" - - - if can?(:destroy, question) - div = link_to 'Delete question', question_path(question), method: :delete - - if can?(:update, question) - div = link_to 'Edit question', '#', class: 'edit-question-link', data: { question_id: question.id } - - div.hidden - = render 'question_form', question: question, local: false - - = render "comments/comments", commentable: question \ No newline at end of file +- cache question do + div.question id="question-#{question.id}" + .question-errors + = render 'shared/errors' + + h4 = "Title: #{question.title}" + div = "Author: #{question.author.email}" + div = question.body + + .question-links + = render question.links + + -if question.files.attached? + = render partial: 'attachments/attachment', collection: question.files + + div id="votable-question-#{question.id}" + = render 'votes/rating', votable: question + - if signed_in? && can?(:create_vote, question) + = render 'votes/vote', vote: question.vote_of(current_user), votable: question + + - if signed_in? + = render 'subscriptions/subscription', question: question + + - if question.award.present? + .question-award + h3 = "For the best answer you will receive #{question.award.name}" + .question-award-image + = image_tag question.award.image, size:"250x250" + + - if can?(:destroy, question) + div = link_to 'Delete question', question_path(question), method: :delete + - if can?(:update, question) + div = link_to 'Edit question', '#', class: 'edit-question-link', data: { question_id: question.id } + + div.hidden + = render 'question_form', question: question, local: false + + = render "comments/comments", commentable: question \ No newline at end of file diff --git a/app/views/questions/index.html.slim b/app/views/questions/index.html.slim index 49e998f..bd065b3 100644 --- a/app/views/questions/index.html.slim +++ b/app/views/questions/index.html.slim @@ -3,5 +3,6 @@ h1 Questions p= link_to 'Ask question', new_question_path ul.questions - - @questions.each do |question| - li = render question + - cache collection_cache_helper_for(:question) + - @questions.each do |question| + li = render question diff --git a/config/application.rb b/config/application.rb index ec4f0bf..3c7d8f1 100644 --- a/config/application.rb +++ b/config/application.rb @@ -18,6 +18,7 @@ class Application < Rails::Application # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") + config.cache_store = :redis_store, 'redis://localhost:6379/0/cache', { expires_in: 90.minutes } config.generators do |g| g.test_framework :rspec, diff --git a/config/environments/development.rb b/config/environments/development.rb index 73b53f5..4222644 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -25,7 +25,8 @@ 'Cache-Control' => "public, max-age=#{2.days.to_i}" } else - config.action_controller.perform_caching = false + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true config.cache_store = :null_store end