From 01a710930923f7bcd6750303e931813d48c6cab3 Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Mon, 23 Dec 2019 23:02:35 +0300 Subject: [PATCH 01/13] Add migrations for users events count --- .../20191223105928_add_events_count_to_users.rb | 10 ++++++++++ ...20191223110515_backfill_users_events_count.rb | 16 ++++++++++++++++ ..._not_null_constraint_to_users_events_count.rb | 7 +++++++ db/schema.rb | 3 ++- 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20191223105928_add_events_count_to_users.rb create mode 100644 db/migrate/20191223110515_backfill_users_events_count.rb create mode 100644 db/migrate/20191223111318_add_not_null_constraint_to_users_events_count.rb diff --git a/db/migrate/20191223105928_add_events_count_to_users.rb b/db/migrate/20191223105928_add_events_count_to_users.rb new file mode 100644 index 00000000..690faf5b --- /dev/null +++ b/db/migrate/20191223105928_add_events_count_to_users.rb @@ -0,0 +1,10 @@ +class AddEventsCountToUsers < ActiveRecord::Migration[5.2] + def up + add_column :users, :events_count, :integer + change_column_default :users, :events_count, 0 + end + + def down + remove_column :users, :events_count + end +end diff --git a/db/migrate/20191223110515_backfill_users_events_count.rb b/db/migrate/20191223110515_backfill_users_events_count.rb new file mode 100644 index 00000000..9bdba0b9 --- /dev/null +++ b/db/migrate/20191223110515_backfill_users_events_count.rb @@ -0,0 +1,16 @@ +class BackfillUsersEventsCount < ActiveRecord::Migration[5.2] + disable_ddl_transaction! + + def up + safety_assured do + execute <<-SQL.squish + UPDATE users + SET events_count = (SELECT count(*) + FROM ahoy_events ae + WHERE ae.user_id = users.id and ae.name = 'Downloaded design') + SQL + end + end + + def down; end +end diff --git a/db/migrate/20191223111318_add_not_null_constraint_to_users_events_count.rb b/db/migrate/20191223111318_add_not_null_constraint_to_users_events_count.rb new file mode 100644 index 00000000..405ab70c --- /dev/null +++ b/db/migrate/20191223111318_add_not_null_constraint_to_users_events_count.rb @@ -0,0 +1,7 @@ +class AddNotNullConstraintToUsersEventsCount < ActiveRecord::Migration[5.2] + def change + change_column_null :users, :events_count, false + end +end + + diff --git a/db/schema.rb b/db/schema.rb index c048cd08..7f84e2c2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_12_20_200156) do +ActiveRecord::Schema.define(version: 2019_12_23_111318) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -217,6 +217,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "username" + t.integer "events_count", default: 0, null: false t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true From 68f2d198dcd2cb61a0e406a929d9262c20108086 Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Mon, 23 Dec 2019 23:20:43 +0300 Subject: [PATCH 02/13] Make backfill migrations reversible --- db/migrate/20191027115750_backfill_gutentag_cache_counter.rb | 4 +++- db/migrate/20191114113825_backfill_designs_downloads_count.rb | 4 +++- .../20191118143605_backfill_designs_hourly_downloads_count.rb | 4 +++- ...ckfill_illustrations_large_url_medium_url_and_thumb_url.rb | 4 +++- db/migrate/20191216123659_backfill_blueprints_thumb_url.rb | 4 +++- .../20191219111524_backfill_designs_popularity_score.rb | 4 +++- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/db/migrate/20191027115750_backfill_gutentag_cache_counter.rb b/db/migrate/20191027115750_backfill_gutentag_cache_counter.rb index 1e61b25b..7761f3b4 100644 --- a/db/migrate/20191027115750_backfill_gutentag_cache_counter.rb +++ b/db/migrate/20191027115750_backfill_gutentag_cache_counter.rb @@ -1,7 +1,9 @@ class BackfillGutentagCacheCounter < ActiveRecord::Migration[5.2] disable_ddl_transaction! - def change + def up Gutentag::Tag.in_batches.update_all taggings_count: 0 end + + def down; end end diff --git a/db/migrate/20191114113825_backfill_designs_downloads_count.rb b/db/migrate/20191114113825_backfill_designs_downloads_count.rb index 311e4083..5326ba9f 100644 --- a/db/migrate/20191114113825_backfill_designs_downloads_count.rb +++ b/db/migrate/20191114113825_backfill_designs_downloads_count.rb @@ -1,7 +1,7 @@ class BackfillDesignsDownloadsCount < ActiveRecord::Migration[5.2] disable_ddl_transaction! - def change + def up safety_assured do execute <<-SQL.squish UPDATE designs @@ -11,4 +11,6 @@ def change SQL end end + + def down; end end diff --git a/db/migrate/20191118143605_backfill_designs_hourly_downloads_count.rb b/db/migrate/20191118143605_backfill_designs_hourly_downloads_count.rb index 0748b26c..76ee8a60 100644 --- a/db/migrate/20191118143605_backfill_designs_hourly_downloads_count.rb +++ b/db/migrate/20191118143605_backfill_designs_hourly_downloads_count.rb @@ -1,7 +1,7 @@ class BackfillDesignsHourlyDownloadsCount < ActiveRecord::Migration[5.2] disable_ddl_transaction! - def change + def up safety_assured do execute <<-SQL.squish UPDATE designs @@ -9,4 +9,6 @@ def change SQL end end + + def down; end end diff --git a/db/migrate/20191216080850_backfill_illustrations_large_url_medium_url_and_thumb_url.rb b/db/migrate/20191216080850_backfill_illustrations_large_url_medium_url_and_thumb_url.rb index dbc2c3f9..207cbd97 100644 --- a/db/migrate/20191216080850_backfill_illustrations_large_url_medium_url_and_thumb_url.rb +++ b/db/migrate/20191216080850_backfill_illustrations_large_url_medium_url_and_thumb_url.rb @@ -1,10 +1,12 @@ class BackfillIllustrationsLargeUrlMediumUrlAndThumbUrl < ActiveRecord::Migration[5.2] disable_ddl_transaction! - def change + def up Illustration.unscoped.in_batches do |relation| relation.update_all large_url: '', medium_url: '', thumb_url: '' sleep(0.1) end end + + def down; end end diff --git a/db/migrate/20191216123659_backfill_blueprints_thumb_url.rb b/db/migrate/20191216123659_backfill_blueprints_thumb_url.rb index a794c3a8..365b4381 100644 --- a/db/migrate/20191216123659_backfill_blueprints_thumb_url.rb +++ b/db/migrate/20191216123659_backfill_blueprints_thumb_url.rb @@ -1,10 +1,12 @@ class BackfillBlueprintsThumbUrl < ActiveRecord::Migration[5.2] disable_ddl_transaction! - def change + def up Blueprint.unscoped.in_batches do |relation| relation.update_all thumb_url: '' sleep(0.1) end end + + def down; end end diff --git a/db/migrate/20191219111524_backfill_designs_popularity_score.rb b/db/migrate/20191219111524_backfill_designs_popularity_score.rb index f39c5ce7..4776af5d 100644 --- a/db/migrate/20191219111524_backfill_designs_popularity_score.rb +++ b/db/migrate/20191219111524_backfill_designs_popularity_score.rb @@ -1,7 +1,7 @@ class BackfillDesignsPopularityScore < ActiveRecord::Migration[5.2] disable_ddl_transaction! - def change + def up safety_assured do now = Time.current.to_i @@ -11,4 +11,6 @@ def change SQL end end + + def down; end end From d622fce51a9f17786c1fc0ee73f0bffedcf1baf1 Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Mon, 23 Dec 2019 23:36:15 +0300 Subject: [PATCH 03/13] Add current user details to gon variables --- lib/boyutluseyler/gon_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/boyutluseyler/gon_helper.rb b/lib/boyutluseyler/gon_helper.rb index dc25a8a4..e7617006 100644 --- a/lib/boyutluseyler/gon_helper.rb +++ b/lib/boyutluseyler/gon_helper.rb @@ -4,6 +4,10 @@ module Boyutluseyler module GonHelper def add_gon_variables gon.websocket_server_url = Rails.application.config.websocket_server_url + if current_user + gon.current_user_id = current_user.id + gon.current_username = current_user.username + end end end end From 749b51ff8450f8546b23bc8b31000ec5f3bd7a9e Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Mon, 23 Dec 2019 23:40:06 +0300 Subject: [PATCH 04/13] Create like button component --- app/assets/stylesheets/framework/buttons.scss | 5 ++ .../like_button/components/like_button.vue | 66 +++++++++++++++++++ app/javascript/like_button/store/actions.js | 49 ++++++++++++++ app/javascript/like_button/store/index.js | 16 +++++ .../like_button/store/mutation_types.js | 1 + app/javascript/like_button/store/mutations.js | 7 ++ app/javascript/like_button/store/state.js | 3 + 7 files changed, 147 insertions(+) create mode 100644 app/javascript/like_button/components/like_button.vue create mode 100644 app/javascript/like_button/store/actions.js create mode 100644 app/javascript/like_button/store/index.js create mode 100644 app/javascript/like_button/store/mutation_types.js create mode 100644 app/javascript/like_button/store/mutations.js create mode 100644 app/javascript/like_button/store/state.js diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index de43c5ab..3ef8305d 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -19,6 +19,11 @@ } } +.btn-grayish-navy { + color: $white; + background-color: #525f7f; +} + .no-outline { &:focus, diff --git a/app/javascript/like_button/components/like_button.vue b/app/javascript/like_button/components/like_button.vue new file mode 100644 index 00000000..43b61e1d --- /dev/null +++ b/app/javascript/like_button/components/like_button.vue @@ -0,0 +1,66 @@ + + + diff --git a/app/javascript/like_button/store/actions.js b/app/javascript/like_button/store/actions.js new file mode 100644 index 00000000..caecfa59 --- /dev/null +++ b/app/javascript/like_button/store/actions.js @@ -0,0 +1,49 @@ +import axios from 'lib/utils/axios_utils'; +import eventHub from 'page_counters/components/event_hub'; +import * as types from './mutation_types'; + +export const setLiked = ({ commit }, liked) => commit(types.SET_LIKED, liked); + +export const receiveDeleteLikeError = ({ dispatch }, payload) => { + // TODO: post error +}; + +export const receiveDeleteLike = ({ dispatch }) => { + dispatch('setLiked', false); + + eventHub.$emit('unlike'); +}; + +export const deleteLike = ({ dispatch }, payload) => { + axios + .delete(payload.endpoint) + .then(response => { + dispatch('receiveDeleteLike'); + }) + .catch(error => { + const errorPayload = Object.assign(payload, { error }); + dispatch('receiveDeleteLikeError', errorPayload); + }); +}; + +export const receiveCreateLikeError = ({ dispatch }, payload) => { + // TODO: post error +}; + +export const receiveCreateLike = ({ dispatch }) => { + dispatch('setLiked', true); + + eventHub.$emit('like'); +}; + +export const createNewLike = ({ dispatch }, payload) => { + axios + .post(payload.endpoint) + .then(response => { + dispatch('receiveCreateLike'); + }) + .catch(error => { + const errorPayload = Object.assign(payload, { error }); + dispatch('receiveCreateLikeError', errorPayload); + }); +}; diff --git a/app/javascript/like_button/store/index.js b/app/javascript/like_button/store/index.js new file mode 100644 index 00000000..3d4f3397 --- /dev/null +++ b/app/javascript/like_button/store/index.js @@ -0,0 +1,16 @@ +import Vue from 'vue/dist/vue.esm'; +import Vuex from 'vuex'; +import * as actions from './actions'; +import mutations from './mutations'; +import state from './state'; + +Vue.use(Vuex); + +export default function createStore() { + return new Vuex.Store({ + actions, + mutations, + state: state(), + strict: process.env.NODE_ENV !== 'production', + }); +}; diff --git a/app/javascript/like_button/store/mutation_types.js b/app/javascript/like_button/store/mutation_types.js new file mode 100644 index 00000000..d1f5050e --- /dev/null +++ b/app/javascript/like_button/store/mutation_types.js @@ -0,0 +1 @@ +export const SET_LIKED = 'SET_LIKED'; diff --git a/app/javascript/like_button/store/mutations.js b/app/javascript/like_button/store/mutations.js new file mode 100644 index 00000000..f1b7147c --- /dev/null +++ b/app/javascript/like_button/store/mutations.js @@ -0,0 +1,7 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_LIKED](state, liked) { + Object.assign(state, { liked }); + }, +}; diff --git a/app/javascript/like_button/store/state.js b/app/javascript/like_button/store/state.js new file mode 100644 index 00000000..455f3d5a --- /dev/null +++ b/app/javascript/like_button/store/state.js @@ -0,0 +1,3 @@ +export default () => ({ + liked: false, +}); From 190b5f26f37ccd60a3bf5556ce5685a07e3fe4a3 Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Mon, 23 Dec 2019 23:56:27 +0300 Subject: [PATCH 05/13] Add like button to the design page --- app/assets/stylesheets/pages/design.scss | 14 +++++++++++ app/helpers/designs_helper.rb | 6 +++++ app/javascript/pages/designs/index.js | 30 ++++++++++++++++++++++-- app/views/designs/show.html.haml | 8 +++++-- 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/design.scss b/app/assets/stylesheets/pages/design.scss index 9eac5cb6..57a3c74e 100644 --- a/app/assets/stylesheets/pages/design.scss +++ b/app/assets/stylesheets/pages/design.scss @@ -92,6 +92,20 @@ a.thumb{ display: table-cell; vertical-align: top; } + + .actionbar { + display: flex; + align-items: flex-end; + margin-bottom: .5rem; + } + + .actionbar-left { + float: left; + flex: 1; + } + .actionbar-right{ + float: right; + } } .design-infos{ diff --git a/app/helpers/designs_helper.rb b/app/helpers/designs_helper.rb index 91dc2e2b..886a9432 100644 --- a/app/helpers/designs_helper.rb +++ b/app/helpers/designs_helper.rb @@ -21,4 +21,10 @@ def fetch_popular_designs JSON.parse(design_list) end + + def liked_design?(design_id) + return false unless current_user + + Ahoy::Event.cached_any_events_for?(Ahoy::Event::LIKED_DESIGN, current_user, design_id: design_id) + end end diff --git a/app/javascript/pages/designs/index.js b/app/javascript/pages/designs/index.js index 47108812..554ecad8 100644 --- a/app/javascript/pages/designs/index.js +++ b/app/javascript/pages/designs/index.js @@ -3,9 +3,11 @@ import Vue from 'vue/dist/vue.esm'; import ContentPreview from 'content_preview/components/content_preview.vue'; import ContentPreviewToggleButton from 'content_preview/components/content_preview_toggle_button.vue'; import DownloadButton from 'download_button/components/download_button.vue'; +import LikeButton from 'like_button/components/like_button.vue'; import PageCounters from 'page_counters/components/page_counters.vue'; import createContentPreviewStore from 'content_preview/store'; import createDownloadButtonStore from 'download_button/store'; +import createLikeButtonStore from 'like_button/store'; import createPageCountersStore from 'page_counters/store'; import ActionCableVue from 'actioncable-vue'; @@ -18,13 +20,26 @@ Vue.use(ActionCableVue, { }); document.addEventListener('turbolinks:load', () => { + const contentPreviewStore = createContentPreviewStore() + + const $contentPreviewToggleButtons = document.getElementsByClassName('js-content-preview-toggle-button'); + if ($contentPreviewToggleButtons.length > 0) { + Array.from($contentPreviewToggleButtons).forEach($contentPreviewToggleButton => { + const app = new Vue({ + el: $contentPreviewToggleButton, + store: contentPreviewStore, + components: { ContentPreviewToggleButton }, + }); + }); + } + const $contentPreviews = document.getElementsByClassName('js-content-preview'); if ($contentPreviews.length > 0) { Array.from($contentPreviews).forEach($contentPreview => { const app = new Vue({ el: $contentPreview, - store: createContentPreviewStore(), - components: { ContentPreview, ContentPreviewToggleButton }, + store: contentPreviewStore, + components: { ContentPreview }, }); }); } @@ -40,6 +55,17 @@ document.addEventListener('turbolinks:load', () => { }); } + const $likeButtons = document.getElementsByClassName('js-like-button'); + if ($likeButtons.length > 0) { + Array.from($likeButtons).forEach($likeButton => { + const app = new Vue({ + el: $likeButton, + store: createLikeButtonStore(), + components: { LikeButton }, + }); + }); + } + const $pageCounters = document.getElementsByClassName('js-page-counters'); if ($pageCounters.length > 0) { Array.from($pageCounters).forEach($pageCounter => { diff --git a/app/views/designs/show.html.haml b/app/views/designs/show.html.haml index 8683765e..e31edc2d 100644 --- a/app/views/designs/show.html.haml +++ b/app/views/designs/show.html.haml @@ -15,8 +15,12 @@ .design .design-photos + .actionbar + .js-content-preview-toggle-button.actionbar-left{ data: { behavior: 'vue' } } + %content-preview-toggle-button{ 'css-class': 'btn-xs btn-outline-primary no-outline' } + .js-like-button.actionbar-right{ data: { behavior: 'vue' } } + %like-button{ 'like-css-class': 'btn-xs btn-primary no-outline border-solid btn-icon', 'unlike-css-class': 'btn-xs btn-grayish-navy no-outline border-solid btn-icon', 'like-text': 'BEĞEN', 'unlike-text': 'BEĞENDİN', ':liked-status': liked_design?(@design.id), 'endpoint': design_like_path(@design) } .js-content-preview{ data: { behavior: 'vue' } } - %content-preview-toggle-button.mb-2{ 'css-class': 'btn-xs btn-outline-primary no-outline' } %content-preview{ 'data-type': 'illustration', 'content-type': 'image', ':files': @illustrations, 'toggle-button-text': 'Fotoğraflar', 'default-thumb-url': image_path('B4B4B4-1.png'), ':active': true, @@ -30,7 +34,7 @@ .d-flex.mb-3 .ml-auto .js-download-button{ data: { behavior: 'vue' } } - %download-button{ 'css-class': 'btn-xs btn-primary border-solid border-width-15x btn-icon', 'text': t('helpers.button.download'), + %download-button{ 'css-class': 'btn-xs btn-primary no-outline border-solid border-width-15x btn-icon', 'text': t('helpers.button.download'), 'downloading-text': t('helpers.button.downloading'), 'record-id': @design.id, 'endpoint': design_download_path(@design) } .d-flex.mb-2 From bdc67bf450b21828f21e801ba2a8f6b4ac0c61d7 Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Tue, 24 Dec 2019 00:18:51 +0300 Subject: [PATCH 06/13] Add DesignAfterLikeWorker for like and unlike designs --- app/controllers/designs_controller.rb | 6 +- .../designs/likes/after_like_service.rb | 62 +++++++++++++++++++ app/workers/design_after_like_worker.rb | 12 ++++ config/routes.rb | 2 + 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 app/services/designs/likes/after_like_service.rb create mode 100644 app/workers/design_after_like_worker.rb diff --git a/app/controllers/designs_controller.rb b/app/controllers/designs_controller.rb index ec5321fa..40397295 100644 --- a/app/controllers/designs_controller.rb +++ b/app/controllers/designs_controller.rb @@ -5,7 +5,7 @@ class DesignsController < ApplicationController include AhoyActions before_action :authenticate_user!, except: %i[show latest popular] - before_action :design, only: %i[show edit update destroy download] + before_action :design, only: %i[show edit update destroy download like] def new @design = Design.new @@ -72,6 +72,10 @@ def download render json: { url: url }, status: :ok end + def like + Designs::Likes::AfterLikeService.new(design, current_user, controller: self).execute + end + def latest @pagy, @designs = pagy(DesignsFinder.new(design_list_params).execute) end diff --git a/app/services/designs/likes/after_like_service.rb b/app/services/designs/likes/after_like_service.rb new file mode 100644 index 00000000..dee97458 --- /dev/null +++ b/app/services/designs/likes/after_like_service.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Designs + module Likes + class AfterLikeService + attr_reader :current_user, :controller, :params + attr_accessor :design + + def initialize(design, user = nil, params = {}) + @design = design + @current_user = user + @controller = params.delete(:controller) + @params = params.dup + end + + def execute + controller.ensure_new_visit_created_before_ahoy_async_jobs if controller.present? + + DesignAfterLikeWorker.perform_async(design.id, current_user.id, ahoy.visit_token) + end + + def perform + return delete_like if liked? + + create_new_like + end + + private + + def liked? + Ahoy::Event.cached_any_events_for?(event_name, current_user, event_properties) + end + + def delete_like + Ahoy::Event.where(name: event_name, properties: event_properties, user_id: current_user.id).destroy_all + end + + def create_new_like + ::AhoyEventService.new(current_user: current_user, + event_name: event_name, + properties: event_properties, + visit_token: params[:visit_token]).track + end + + def event_name + Ahoy::Event::LIKED_DESIGN + end + + def event_properties + { design_id: design.id } + end + + def ahoy + @ahoy ||= controller.present? ? controller.ahoy : service_ahoy + end + + def service_ahoy + ::Ahoy::Tracker.new(controller: nil, user: current_user) + end + end + end +end diff --git a/app/workers/design_after_like_worker.rb b/app/workers/design_after_like_worker.rb new file mode 100644 index 00000000..936aaf88 --- /dev/null +++ b/app/workers/design_after_like_worker.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class DesignAfterLikeWorker + include ApplicationWorker + + def perform(design_id, current_user_id, visit_token) + design = Design.find(design_id) + current_user = User.find(current_user_id) + + Designs::Likes::AfterLikeService.new(design, current_user, visit_token: visit_token).perform + end +end diff --git a/config/routes.rb b/config/routes.rb index ac6c411a..8e727e00 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -21,6 +21,8 @@ get '/3d-model/:category/:id', to: 'designs#show', as: :design_show, constraints: { id: /.*\D+.*/ } get '/design/download/:id', to: 'designs#download', as: :design_download, constraints: { id: /.*\D+.*/ } + match '/design/like/:id', to: 'designs#like', via: [:post, :delete], as: :design_like, constraints: { id: /.*\D+.*/ } + devise_for :users, path: '', controllers: { registrations: :registrations, passwords: :passwords, From cb7c3610d9132038c4025ff1f1b70f2f223dde71 Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Tue, 24 Dec 2019 02:57:41 +0300 Subject: [PATCH 07/13] Cache user events --- app/models/ahoy/event.rb | 19 +++++++++++++++++++ app/models/user.rb | 7 ++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/models/ahoy/event.rb b/app/models/ahoy/event.rb index db0b5375..855a8405 100644 --- a/app/models/ahoy/event.rb +++ b/app/models/ahoy/event.rb @@ -18,12 +18,31 @@ class Event < ApplicationRecord self.table_name = 'ahoy_events' + LIKED_DESIGN = 'Liked design' + DOWNLOADED_DESIGN = 'Downloaded design' + + USER_EVENTS = [LIKED_DESIGN, DOWNLOADED_DESIGN].freeze + belongs_to :visit belongs_to :user, optional: true + counter_culture :user, column_name: proc { |model| + (USER_EVENTS.include? model.name) ? 'events_count' : nil + }, touch: 'updated_at' belongs_to :design, class_name: 'Design', store: :properties, optional: true counter_culture :design, column_name: proc { |model| model.name == 'Downloaded design' ? 'downloads_count' : nil } + + class << self + def cached_any_events_for?(event_name, user, event_properties) + event_fk, event_id = event_properties.first + + cache_name = "any_events_for-#{event_name.parameterize.underscore}-#{event_id}-#{user.updated_at}" + Rails.cache.fetch(cache_name, expires_in: 1.week) do + Ahoy::Event.where(name: event_name, properties: event_properties, user_id: user.id).any? + end + end + end end end diff --git a/app/models/user.rb b/app/models/user.rb index 097fce1b..1603f08d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -36,6 +36,7 @@ class User < ApplicationRecord email_regexp: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i has_many :designs + has_many :events, class_name: 'Ahoy::Event', dependent: :destroy # Virtual attribute for authenticating by either username or email attr_accessor :login @@ -51,10 +52,10 @@ class User < ApplicationRecord uniqueness: { case_sensitive: false }, length: { in: 3..30 } - validates_confirmation_of :password + validates :password, confirmation: true # Only allow letter, number, underscore, hyphen and punctuation. - validates_format_of :username, - with: /\A(?:[a-zA-ZğüşıöçĞÜŞİÖÇ0-9_\.][a-zA-ZğüşıöçĞÜŞİÖÇ0-9_\-\.]*[a-zA-ZğüşıöçĞÜŞİÖÇ0-9_\-]|[a-zA-ZğüşıöçĞÜŞİÖÇ0-9_])\z/ + validates :username, + format: { with: /\A(?:[a-zA-ZğüşıöçĞÜŞİÖÇ0-9_\.][a-zA-ZğüşıöçĞÜŞİÖÇ0-9_\-\.]*[a-zA-ZğüşıöçĞÜŞİÖÇ0-9_\-]|[a-zA-ZğüşıöçĞÜŞİÖÇ0-9_])\z/ } class << self # Devise method overridden to allow sign in with email or username From 9d138825aa6322ed3c7d662a9506c4c2b9fb8fc6 Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Tue, 24 Dec 2019 15:21:26 +0300 Subject: [PATCH 08/13] Add migrations for design likes count --- .../20191223235921_add_likes_count_to_designs.rb | 10 ++++++++++ ...0191224000201_backfill_designs_likes_count.rb | 16 ++++++++++++++++ ...not_null_constraint_to_designs_likes_count.rb | 5 +++++ db/schema.rb | 3 ++- 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20191223235921_add_likes_count_to_designs.rb create mode 100644 db/migrate/20191224000201_backfill_designs_likes_count.rb create mode 100644 db/migrate/20191224000412_add_not_null_constraint_to_designs_likes_count.rb diff --git a/db/migrate/20191223235921_add_likes_count_to_designs.rb b/db/migrate/20191223235921_add_likes_count_to_designs.rb new file mode 100644 index 00000000..dcdd7138 --- /dev/null +++ b/db/migrate/20191223235921_add_likes_count_to_designs.rb @@ -0,0 +1,10 @@ +class AddLikesCountToDesigns < ActiveRecord::Migration[5.2] + def self.up + add_column :designs, :likes_count, :integer + change_column_default :designs, :likes_count, 0 + end + + def self.down + remove_column :designs, :likes_count + end +end diff --git a/db/migrate/20191224000201_backfill_designs_likes_count.rb b/db/migrate/20191224000201_backfill_designs_likes_count.rb new file mode 100644 index 00000000..b572b23f --- /dev/null +++ b/db/migrate/20191224000201_backfill_designs_likes_count.rb @@ -0,0 +1,16 @@ +class BackfillDesignsLikesCount < ActiveRecord::Migration[5.2] + disable_ddl_transaction! + + def up + safety_assured do + execute <<-SQL.squish + UPDATE designs + SET likes_count = (SELECT count(1) + FROM ahoy_events ae + WHERE ae.name = 'Liked design' and ae.properties @> json_build_object('design_id', designs.id)::jsonb) + SQL + end + end + + def down; end +end diff --git a/db/migrate/20191224000412_add_not_null_constraint_to_designs_likes_count.rb b/db/migrate/20191224000412_add_not_null_constraint_to_designs_likes_count.rb new file mode 100644 index 00000000..7e34da92 --- /dev/null +++ b/db/migrate/20191224000412_add_not_null_constraint_to_designs_likes_count.rb @@ -0,0 +1,5 @@ +class AddNotNullConstraintToDesignsLikesCount < ActiveRecord::Migration[5.2] + def change + change_column_null :designs, :likes_count, false + end +end diff --git a/db/schema.rb b/db/schema.rb index 7f84e2c2..fb3141a7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_12_23_111318) do +ActiveRecord::Schema.define(version: 2019_12_24_000412) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -119,6 +119,7 @@ t.float "hourly_downloads_count", default: 0.0, null: false t.float "popularity_score", default: 0.0, null: false t.datetime "home_popular_at" + t.integer "likes_count", default: 0, null: false t.index ["category_id"], name: "index_designs_on_category_id" t.index ["slug"], name: "index_designs_on_slug", unique: true t.index ["user_id"], name: "index_designs_on_user_id" From 0a631a0dc277b149d14f82008d90c19e1f9fa9f9 Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Tue, 24 Dec 2019 15:23:37 +0300 Subject: [PATCH 09/13] Cache design likes count --- app/models/ahoy/event.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/ahoy/event.rb b/app/models/ahoy/event.rb index 855a8405..de3d2346 100644 --- a/app/models/ahoy/event.rb +++ b/app/models/ahoy/event.rb @@ -33,6 +33,9 @@ class Event < ApplicationRecord counter_culture :design, column_name: proc { |model| model.name == 'Downloaded design' ? 'downloads_count' : nil } + counter_culture :design, column_name: proc { |model| + model.name == 'Liked design' ? 'likes_count' : nil + } class << self def cached_any_events_for?(event_name, user, event_properties) From 29b65dcf78d1d7c1b90d75080934ed5a35d29b71 Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Tue, 24 Dec 2019 15:25:35 +0300 Subject: [PATCH 10/13] Create likes counter component --- .../components/likes_counter.vue | 35 +++++++++++++++++++ app/javascript/page_counters/store/actions.js | 1 + .../page_counters/store/mutation_types.js | 2 ++ .../page_counters/store/mutations.js | 4 +++ app/javascript/page_counters/store/state.js | 1 + 5 files changed, 43 insertions(+) create mode 100644 app/javascript/page_counters/components/likes_counter.vue diff --git a/app/javascript/page_counters/components/likes_counter.vue b/app/javascript/page_counters/components/likes_counter.vue new file mode 100644 index 00000000..259fb062 --- /dev/null +++ b/app/javascript/page_counters/components/likes_counter.vue @@ -0,0 +1,35 @@ + + diff --git a/app/javascript/page_counters/store/actions.js b/app/javascript/page_counters/store/actions.js index 078f60e0..5ff7fce4 100644 --- a/app/javascript/page_counters/store/actions.js +++ b/app/javascript/page_counters/store/actions.js @@ -2,4 +2,5 @@ import * as types from './mutation_types'; export const setDownloadsCount = ({ commit }, count) => commit(types.SET_DOWNLOADS_COUNT, count); +export const setLikesCount = ({ commit }, count) => commit(types.SET_LIKES_COUNT, count); diff --git a/app/javascript/page_counters/store/mutation_types.js b/app/javascript/page_counters/store/mutation_types.js index 1f5c7652..6acad104 100644 --- a/app/javascript/page_counters/store/mutation_types.js +++ b/app/javascript/page_counters/store/mutation_types.js @@ -1 +1,3 @@ export const SET_DOWNLOADS_COUNT = 'SET_DOWNLOADS_COUNT'; + +export const SET_LIKES_COUNT = 'SET_LIKES_COUNT'; diff --git a/app/javascript/page_counters/store/mutations.js b/app/javascript/page_counters/store/mutations.js index 359aacf5..8b8f6f46 100644 --- a/app/javascript/page_counters/store/mutations.js +++ b/app/javascript/page_counters/store/mutations.js @@ -4,4 +4,8 @@ export default { [types.SET_DOWNLOADS_COUNT](state, downloadsCount) { Object.assign(state, { downloadsCount }); }, + + [types.SET_LIKES_COUNT](state, likesCount) { + Object.assign(state, { likesCount }); + }, }; diff --git a/app/javascript/page_counters/store/state.js b/app/javascript/page_counters/store/state.js index acd92edf..06511716 100644 --- a/app/javascript/page_counters/store/state.js +++ b/app/javascript/page_counters/store/state.js @@ -1,3 +1,4 @@ export default () => ({ downloadsCount: 0, + likesCount: 0, }); From 3861e7cd48def4061ada497b9380b297514e0eeb Mon Sep 17 00:00:00 2001 From: Samet Gunaydin Date: Tue, 24 Dec 2019 15:27:26 +0300 Subject: [PATCH 11/13] Refactor page counters --- .../components/downloads_counter.vue | 17 ++----- .../components/page_counters.vue | 46 ++++++++++++------- .../components/views_counter.vue | 5 +- .../page_counters/mixins/counter_mixin.js | 10 ++++ 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/app/javascript/page_counters/components/downloads_counter.vue b/app/javascript/page_counters/components/downloads_counter.vue index 2759105c..4550329c 100644 --- a/app/javascript/page_counters/components/downloads_counter.vue +++ b/app/javascript/page_counters/components/downloads_counter.vue @@ -6,17 +6,6 @@ import eventHub from './event_hub'; export default { name: 'DownloadsCounter', mixins: [counterMixin], - props: { - min: { - type: Number, - required: true, - }, - }, - computed: { - showCounter() { - return this.count >= this.min; - }, - }, created() { eventHub.$on('download', this.incrementCounter); }, @@ -27,10 +16,10 @@ export default { ...mapActions(['setDownloadsCount']), incrementCounter() { let downloadsCount = this.count; - this.setDownloadsCount(downloadsCount += 1); + this.setDownloadsCount((downloadsCount += 1)); }, - } -} + }, +};