diff --git a/Gemfile b/Gemfile index 1ce0a4c..3f17fdd 100644 --- a/Gemfile +++ b/Gemfile @@ -71,6 +71,8 @@ gem "mini_racer", "~> 0.6.2" gem "unicorn", "~> 6.1" +gem "redis-rails", "~> 5.0", ">= 5.0.2" + 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] diff --git a/Gemfile.lock b/Gemfile.lock index 25be0b6..c8b50cc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -330,6 +330,22 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) redis (4.6.0) + redis-actionpack (5.3.0) + actionpack (>= 5, < 8) + redis-rack (>= 2.1.0, < 3) + redis-store (>= 1.1.0, < 2) + redis-activesupport (5.3.0) + activesupport (>= 3, < 8) + redis-store (>= 1.3, < 2) + redis-rack (2.1.4) + 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.1) + redis (>= 4, < 5) regexp_parser (2.1.1) representable (3.1.1) declarative (< 0.1.0) @@ -497,6 +513,7 @@ DEPENDENCIES rack-mini-profiler (~> 2.0) rails (~> 6.1.3) rails-controller-testing (~> 1.0, >= 1.0.5) + redis-rails (~> 5.0, >= 5.0.2) rspec-rails (~> 5.0, >= 5.0.2) sass-rails (>= 6) selenium-webdriver 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 99c7249..0af85df 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -2,7 +2,7 @@ class Answer < ApplicationRecord include Votable include Commentable - belongs_to :question + belongs_to :question, touch: true belongs_to :author, class_name: "User" has_many :links, dependent: :destroy, as: :linkable diff --git a/app/models/comment.rb b/app/models/comment.rb index 8ed9ab5..bffe36a 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 end diff --git a/app/models/link.rb b/app/models/link.rb index 4d594d1..3d14518 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, :url, presence: true validates :url, format: URI::regexp(%w[http https]) diff --git a/app/models/subscription.rb b/app/models/subscription.rb index e07248f..09e5bfd 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -1,6 +1,6 @@ class Subscription < ApplicationRecord belongs_to :user - belongs_to :question + belongs_to :question, touch: true validates :user, uniqueness: { scope: :question_id } end diff --git a/app/models/vote.rb b/app/models/vote.rb index d8bc493..259bfa1 100644 --- a/app/models/vote.rb +++ b/app/models/vote.rb @@ -1,6 +1,6 @@ class Vote < ApplicationRecord belongs_to :user - belongs_to :votable, polymorphic: true + belongs_to :votable, polymorphic: true, touch: true validates :status, presence: true validates :user, uniqueness: { scope: %i[votable_type votable_id] } diff --git a/app/views/answers/_answer.html.slim b/app/views/answers/_answer.html.slim index e4e3654..78fb1fe 100644 --- a/app/views/answers/_answer.html.slim +++ b/app/views/answers/_answer.html.slim @@ -1,33 +1,34 @@ -li id="answer-li-#{answer.id}" - p = answer.body - - if (can? :destroy, answer) && (can? :update, answer) - p = link_to 'Delete answer', answer_path(answer), method: :delete, data: { confirm: "Are you sure?" }, remote: true - p = link_to "Edit", "#", class: "edit-answer-link", data: { answer_id: answer.id } - = form_with model: answer, local: false, class: 'hidden', html: {id: "edit-answer-#{answer.id}"} do |f| - = f.label :body - = f.text_area :body - p - = f.label :files - = f.file_field :files, multiple: true - p Links: - p - = f.fields_for :links do |link| - = render 'link_fields', f: link - .links - = link_to_add_association 'Add link', f, :links - = f.submit 'Save' - - if can?(:mark_as_best, @question) - p = link_to 'Mark answer as best', mark_answer_as_best_question_path(@question, answer_id: answer.id), method: :patch - = render 'shared/attached_files', resource: answer - .answer-links - = render 'shared/attached_links', resource: answer - .answer-vote - = render 'shared/vote', resource: answer - div id="answer-#{answer.id}-comments" - - if answer.comments.first - h4 = "Comments:" - ul - = render answer.comments - - if user_signed_in? - p = link_to 'Add comment', "#", class: "add-comment", data: { answer_id: answer.id } - = render 'comments/form', commentable: answer +- cache answer do + li id="answer-li-#{answer.id}" + p = answer.body + - if (can? :destroy, answer) && (can? :update, answer) + p = link_to 'Delete answer', answer_path(answer), method: :delete, data: { confirm: "Are you sure?" }, remote: true + p = link_to "Edit", "#", class: "edit-answer-link", data: { answer_id: answer.id } + = form_with model: answer, local: false, class: 'hidden', html: {id: "edit-answer-#{answer.id}"} do |f| + = f.label :body + = f.text_area :body + p + = f.label :files + = f.file_field :files, multiple: true + p Links: + p + = f.fields_for :links do |link| + = render 'link_fields', f: link + .links + = link_to_add_association 'Add link', f, :links + = f.submit 'Save' + - if can?(:mark_as_best, @question) + p = link_to 'Mark answer as best', mark_answer_as_best_question_path(@question, answer_id: answer.id), method: :patch + = render 'shared/attached_files', resource: answer + .answer-links + = render 'shared/attached_links', resource: answer + .answer-vote + = render 'shared/vote', resource: answer + div id="answer-#{answer.id}-comments" + - if answer.comments.first + h4 = "Comments:" + ul + = render answer.comments + - if user_signed_in? + p = link_to 'Add comment', "#", class: "add-comment", data: { answer_id: answer.id } + = render 'comments/form', commentable: answer diff --git a/app/views/comments/_comment.html.slim b/app/views/comments/_comment.html.slim index a627b43..d04c7f4 100644 --- a/app/views/comments/_comment.html.slim +++ b/app/views/comments/_comment.html.slim @@ -1,3 +1,4 @@ -li id="comment-#{comment.id}" - p = comment.body - p = "Author: #{comment.user.email}" +- cache comment do + li id="comment-#{comment.id}" + p = comment.body + p = "Author: #{comment.user.email}" diff --git a/app/views/questions/index.html.slim b/app/views/questions/index.html.slim index 0bb0f50..9a1bd5e 100644 --- a/app/views/questions/index.html.slim +++ b/app/views/questions/index.html.slim @@ -2,15 +2,16 @@ p= link_to 'Ask question', new_question_path h2 Questions list -table class="questions-index-table" - tr - th - p Question title - th - p Question Body - th - p View question - = render @questions +-cache collection_cache_helper_for :question do + table class="questions-index-table" + tr + th + p Question title + th + p Question Body + th + p View question + = render @questions - if current_user p = link_to 'View Awards', awards_path diff --git a/app/views/questions/show.html.slim b/app/views/questions/show.html.slim index 67f1481..50b606f 100644 --- a/app/views/questions/show.html.slim +++ b/app/views/questions/show.html.slim @@ -1,80 +1,81 @@ -.question - .question-errors - h1 Question title - h3 id="question-title-#{@question.id}" = @question.title - - if can?(:update, @question) - p = link_to 'Edit title', "#", class: "edit-question-title-link", data: { question_id: @question.id } - = form_with model: @question, local: false, class: 'hidden', html: {id: "edit-question-title-#{@question.id}"} do |f| - = f.label :title - = f.text_area :title - = f.submit 'Save' +- cache @question do + .question + .question-errors + h1 Question title + h3 id="question-title-#{@question.id}" = @question.title + - if can?(:update, @question) + p = link_to 'Edit title', "#", class: "edit-question-title-link", data: { question_id: @question.id } + = form_with model: @question, local: false, class: 'hidden', html: {id: "edit-question-title-#{@question.id}"} do |f| + = f.label :title + = f.text_area :title + = f.submit 'Save' - h1 Question body - h3 id="question-body-#{@question.id}" = @question.body - - if can?(:update, @question) - p = link_to 'Edit body', "#", class: "edit-question-body-link", data: { question_id: @question.id } - = form_with model: @question, local: false, class: 'hidden', html: {id: "edit-question-body-#{@question.id}"} do |f| - = f.label :body - = f.text_area :body - p - = f.label :files - = f.file_field :files, multiple: true - p Links: - p - = f.fields_for :links do |link| - = render 'link_fields', f: link - .links - = link_to_add_association 'Add link', f, :links - = f.submit 'Save' + h1 Question body + h3 id="question-body-#{@question.id}" = @question.body + - if can?(:update, @question) + p = link_to 'Edit body', "#", class: "edit-question-body-link", data: { question_id: @question.id } + = form_with model: @question, local: false, class: 'hidden', html: {id: "edit-question-body-#{@question.id}"} do |f| + = f.label :body + = f.text_area :body + p + = f.label :files + = f.file_field :files, multiple: true + p Links: + p + = f.fields_for :links do |link| + = render 'link_fields', f: link + .links + = link_to_add_association 'Add link', f, :links + = f.submit 'Save' - .question-files-list - = render 'shared/attached_files', resource: @question + .question-files-list + = render 'shared/attached_files', resource: @question - .question-links - = render 'shared/attached_links', resource: @question - .question-award - = render 'shared/attached_award', resource: @question.award - .question-vote - = render 'shared/vote', resource: @question - div id="question-#{@question.id}-comments" - - if @question.comments.first - h4 = "Comments:" - ul - = render @question.comments - - if user_signed_in? - p = link_to 'Add comment', "#", class: "add-comment", data: { question_id: @question.id } - = render 'comments/form', commentable: @question - .question-subscribed - - if user_signed_in? - = render 'shared/subscription', resource: @question + .question-links + = render 'shared/attached_links', resource: @question + .question-award + = render 'shared/attached_award', resource: @question.award + .question-vote + = render 'shared/vote', resource: @question + div id="question-#{@question.id}-comments" + - if @question.comments.first + h4 = "Comments:" + ul + = render @question.comments + - if user_signed_in? + p = link_to 'Add comment', "#", class: "add-comment", data: { question_id: @question.id } + = render 'comments/form', commentable: @question + .question-subscribed + - if user_signed_in? + = render 'shared/subscription', resource: @question -h2 = "Answers list" -.answer-errors -.answers - ol - - if @best_answer.present? - .best-answer - = render @best_answer - = render @other_answers + h2 = "Answers list" + .answer-errors + .answers + ol + - if @best_answer.present? + .best-answer + = render @best_answer + = render @other_answers -- if current_user - h2 Create new answer - .answer-errors-create - = form_with model: [@question, @answer], local: false do |f| - = f.label :body - = f.text_area :body - p - = f.label :files - = f.file_field :files, multiple: true - p Links: - p - = f.fields_for :links do |link| - = render 'link_fields', f: link - .links - = link_to_add_association 'Add link', f, :links - = f.submit 'Create' + - if current_user + h2 Create new answer + .answer-errors-create + = form_with model: [@question, @answer], local: false do |f| + = f.label :body + = f.text_area :body + p + = f.label :files + = f.file_field :files, multiple: true + p Links: + p + = f.fields_for :links do |link| + = render 'link_fields', f: link + .links + = link_to_add_association 'Add link', f, :links + = f.submit 'Create' -p = link_to 'Back to questions list', questions_path + p = link_to 'Back to questions list', questions_path -- if can?(:destroy, @question) - p = link_to 'Delete', question_path(@question), method: :delete, data: { confirm: "Are you sure?" } + - if can?(:destroy, @question) + p = link_to 'Delete', question_path(@question), method: :delete, data: { confirm: "Are you sure?" } diff --git a/app/views/shared/_attached_links.html.slim b/app/views/shared/_attached_links.html.slim index a1c4212..92c9b9e 100644 --- a/app/views/shared/_attached_links.html.slim +++ b/app/views/shared/_attached_links.html.slim @@ -1,10 +1,11 @@ -- if resource.links.first - h4 = "Links attached:" - ul - - resource.links.each do |link| - - if link.gist? - li = javascript_include_tag link.url+".js" - - else - li = link_to link.name, link.url - = link_to 'Remove', link_path(link.id), method: :delete, data: { confirm: 'Are you sure?' } if current_user&.author_of?(link.linkable) +-cache resource do + - if resource.links.first + h4 = "Links attached:" + ul + - resource.links.each do |link| + - if link.gist? + li = javascript_include_tag link.url+".js" + - else + li = link_to link.name, link.url + = link_to 'Remove', link_path(link.id), method: :delete, data: { confirm: 'Are you sure?' } if current_user&.author_of?(link.linkable) \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index d895ce6..bb30ccd 100644 --- a/config/application.rb +++ b/config/application.rb @@ -24,6 +24,8 @@ class Application < Rails::Application config.active_job.queue_adapter = :sidekiq config.autoload_paths += [config.root.join("app")] + config.cache_store = :redis_store, "redis://localhost:6379/0/cache", { expires_in: 90.minutes } + config.generators do |g| g.test_framework :rspec, view_specs: false, diff --git a/config/environments/development.rb b/config/environments/development.rb index 8ef15b8..f6044b1 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -25,9 +25,11 @@ "Cache-Control" => "public, max-age=#{2.days.to_i}", } else - config.action_controller.perform_caching = false + config.action_controller.perform_caching = true config.cache_store = :null_store + + config.action_controller.enable_fragment_cache_logging = true end # Store uploaded files on the local file system (see config/storage.yml for options).