Merge pull request #213 from Retrospring/refactor/profile

Move profile fields to seperate table
This commit is contained in:
Karina Kwiatek 2021-12-25 22:44:10 +01:00 committed by GitHub
commit 053ebafbc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 187 additions and 84 deletions

View File

@ -28,8 +28,7 @@ class UserController < ApplicationController
end
def update
user_attributes = params.require(:user).permit(:display_name, :motivation_header, :website, :show_foreign_themes, :location, :bio,
:profile_picture_x, :profile_picture_y, :profile_picture_w, :profile_picture_h,
user_attributes = params.require(:user).permit(:show_foreign_themes, :profile_picture_x, :profile_picture_y, :profile_picture_w, :profile_picture_h,
:profile_header_x, :profile_header_y, :profile_header_w, :profile_header_h, :profile_picture, :profile_header)
if current_user.update_attributes(user_attributes)
text = t('flash.user.update.text')
@ -41,6 +40,17 @@ class UserController < ApplicationController
end
redirect_to edit_user_profile_path
end
def update_profile
profile_attributes = params.require(:profile).permit(:display_name, :motivation_header, :website, :location, :description)
if current_user.profile.update_attributes(profile_attributes)
flash[:success] = t('flash.user.update.text')
else
flash[:error] = t('flash.user.update.error')
end
redirect_to edit_user_profile_path
end
# endregion
# region Privacy settings

View File

@ -14,11 +14,11 @@ module ApplicationHelper::GraphMethods
# @param user [User]
def user_opengraph(user)
opengraph_meta_tags({
'og:title': user.safe_name,
'og:title': user.profile.safe_name,
'og:type': 'profile',
'og:image': full_profile_picture_url(user),
'og:url': show_user_profile_url(user.screen_name),
'og:description': user.bio,
'og:description': user.profile.description,
'og:site_name': APP_CONFIG['site_name'],
'profile:username': user.screen_name
})
@ -29,8 +29,8 @@ module ApplicationHelper::GraphMethods
meta_tags({
'twitter:card': 'summary',
'twitter:site': '@retrospring',
'twitter:title': user.motivation_header.presence || "Ask me anything!",
'twitter:description': "Ask #{user.safe_name} anything on Retrospring",
'twitter:title': user.profile.motivation_header.presence || "Ask me anything!",
'twitter:description': "Ask #{user.profile.safe_name} anything on Retrospring",
'twitter:image': full_profile_picture_url(user)
})
end
@ -38,7 +38,7 @@ module ApplicationHelper::GraphMethods
# @param answer [Answer]
def answer_opengraph(answer)
opengraph_meta_tags({
'og:title': "#{answer.user.safe_name} answered: #{answer.question.content}",
'og:title': "#{answer.user.profile.safe_name} answered: #{answer.question.content}",
'og:type': 'article',
'og:image': full_profile_picture_url(answer.user),
'og:url': show_user_answer_url(answer.user.screen_name, answer.id),

View File

@ -3,7 +3,7 @@ module UserHelper
# @return [String] The user name
def user_screen_name(user, anonymous: false, url: true, link_only: false)
return APP_CONFIG['anonymous_name'] if user.nil? || anonymous
name = user.display_name.blank? ? user.screen_name : user.display_name
name = user.profile.display_name.blank? ? user.screen_name : user.profile.display_name
if url
link = show_user_profile_path(user.screen_name)
return link if link_only

29
app/models/profile.rb Normal file
View File

@ -0,0 +1,29 @@
class Profile < ApplicationRecord
belongs_to :user
attr_readonly :user_id
validates :display_name, length: { maximum: 50 }
validates :location, length: { maximum: 72 }
validates :description, length: { maximum: 200 }
before_save do
unless website.blank?
self.website = if website.match %r{\Ahttps?://}
website
else
"http://#{website}"
end
end
end
def display_website
website.match(/https?:\/\/([A-Za-z.\-0-9]+)\/?(?:.*)/i)[1]
rescue NoMethodError
website
end
def safe_name
display_name.presence || user.screen_name
end
end

View File

@ -32,7 +32,7 @@ class Services::Tumblr < Service
asker = if answer.question.author_is_anonymous?
APP_CONFIG['anonymous_name']
else
answer.question.user.display_name.blank? ? answer.question.user.screen_name : answer.question.user.display_name
answer.question.user.profile.display_name.blank? ? answer.question.user.screen_name : answer.question.user.profile.display_name
end
client.text(
self.uid,

View File

@ -45,6 +45,7 @@ class User < ApplicationRecord
has_many :subscriptions, dependent: :destroy
has_many :totp_recovery_codes, dependent: :destroy
has_one :profile, dependent: :destroy
has_one :theme, dependent: :destroy
SCREEN_NAME_REGEX = /\A[a-zA-Z0-9_]{1,16}\z/
@ -57,22 +58,11 @@ class User < ApplicationRecord
validates :email, fake_email: true
validates :screen_name, presence: true, format: { with: SCREEN_NAME_REGEX }, uniqueness: { case_sensitive: false }, screen_name: true
validates :display_name, length: { maximum: 50 }
validates :bio, length: { maximum: 200 }
mount_uploader :profile_picture, ProfilePictureUploader, mount_on: :profile_picture_file_name
process_in_background :profile_picture
mount_uploader :profile_header, ProfileHeaderUploader, mount_on: :profile_header_file_name
process_in_background :profile_header
before_save do
self.website = if website.match %r{\Ahttps?://}
website
else
"http://#{website}"
end unless website.blank?
end
# when a user has been deleted, all reports relating to the user become invalid
before_destroy do
rep = Report.where(target_id: self.id, type: 'Reports::User')
@ -84,6 +74,10 @@ class User < ApplicationRecord
end
end
after_create do
Profile.create(user_id: id) if Profile.where(user_id: id).count.zero?
end
def login=(login)
@login = login
end
@ -169,12 +163,6 @@ class User < ApplicationRecord
comment.smiles.pluck(:user_id).include? self.id
end
def display_website
website.match(/https?:\/\/([A-Za-z.\-0-9]+)\/?(?:.*)/i)[1]
rescue NoMethodError
website
end
def comment(answer, content)
Comment.create!(user: self, answer: answer, content: content)
end
@ -253,10 +241,6 @@ class User < ApplicationRecord
!self.export_processing
end
def safe_name
self.display_name.presence || self.screen_name
end
# %w[admin moderator].each do |m|
# define_method(m) { raise "not allowed: #{m}" }
# define_method(m+??) { raise "not allowed: #{m}?"}

View File

@ -1,9 +1,9 @@
.card
.card-header
- if user.motivation_header.blank?
- if user.profile.motivation_header.blank?
= t 'views.questionbox.title'
- else
= user.motivation_header
= user.profile.motivation_header
.card-body
- if user.banned?
.text-center

View File

@ -6,12 +6,12 @@
%img.avatar-md.mr-2{ src: u.profile_picture.url(:medium) }
.media-body
%h6.media-heading.answerbox__question-user
- if u.display_name.blank?
- if u.profile.display_name.blank?
%a{ href: show_user_profile_path(u.screen_name) }
= u.screen_name
- else
%a{ href: show_user_profile_path(u.screen_name) }
= u.display_name
= u.profile.display_name
%span.text-muted= u.screen_name
%p.answerbox__question-text
- case type

View File

@ -3,9 +3,9 @@
.card-body
%img.userbox__avatar{ src: user.profile_picture.url(:small) }
%a.profile__name{ href: show_user_profile_path(user.screen_name) }
- unless user.display_name.blank?
- unless user.profile.display_name.blank?
.profile__display-name
= user.display_name
= user.profile.display_name
.profile__screen-name
= user.screen_name
.row

View File

@ -13,31 +13,31 @@
%p.font-weight-bold.mb-0 Display name
%p.text-muted
- if current_user.display_name.blank?
- if current_user.profile.display_name.blank?
None set!
- else
= current_user.display_name
= current_user.profile.display_name
%p.font-weight-bold.mb-0 Bio
%p.text-muted
- if current_user.bio.blank?
- if current_user.profile.description.blank?
None set!
- else
= current_user.bio
= current_user.profile.description
%p.font-weight-bold.mb-0 Location
%p.text-muted
- if current_user.location.blank?
- if current_user.profile.location.blank?
None set!
- else
= current_user.location
= current_user.profile.location
%p.font-weight-bold.mb-0 Website
%p.text-muted
- if current_user.website.blank?
- if current_user.profile.website.blank?
None set!
- else
= current_user.website
= current_user.profile.website
.col-md-6.col-sm-6.col-xs-12
%h4 Pictures
%p.font-weight-bold.mb-0 Profile picture

View File

@ -2,8 +2,6 @@
.card-body
= bootstrap_form_for(current_user, url: { action: :edit }, html: { multipart: true }, method: :patch) do |f|
= f.text_field :display_name, label: t('views.settings.profile.displayname')
.media#profile-picture-media
.pull-left
%img.avatar-lg.mr-3{ src: current_user.profile_picture.url(:medium) }
@ -38,14 +36,6 @@
%button.btn.btn-inverse#cropper-header-zoom-in{ type: :button }
%i.fa.fa-search-plus
= f.text_field :motivation_header, label: t('views.settings.profile.motivation'), placeholder: t('views.settings.profile.placeholder.motivation')
= f.text_field :website, label: t('views.settings.profile.website'), placeholder: 'https://example.com'
= f.text_field :location, label: t('views.settings.profile.location'), placeholder: t('views.settings.profile.placeholder.location')
= f.text_area :bio, label: t('views.settings.profile.bio'), placeholder: t('views.settings.profile.placeholder.bio')
= f.check_box :show_foreign_themes, label: 'Render other user themes when visiting their profile'
- %i[profile_picture_x profile_picture_y profile_picture_w profile_picture_h].each do |attrib|
@ -55,3 +45,19 @@
= f.hidden_field attrib, id: attrib
= f.submit t('views.actions.save'), class: 'btn btn-primary'
.card
.card-body
= bootstrap_form_for(current_user.profile, url: { action: :update_profile }, html: { multipart: true }, method: :patch) do |f|
= f.text_field :display_name, label: t('views.settings.profile.displayname')
= f.text_field :motivation_header, label: t('views.settings.profile.motivation'), placeholder: t('views.settings.profile.placeholder.motivation')
= f.text_field :website, label: t('views.settings.profile.website'), placeholder: 'https://example.com'
= f.text_field :location, label: t('views.settings.profile.location'), placeholder: t('views.settings.profile.placeholder.location')
= f.text_area :description, label: t('views.settings.profile.bio'), placeholder: t('views.settings.profile.placeholder.bio')
= f.submit t('views.actions.save'), class: 'btn btn-primary'

View File

@ -3,9 +3,9 @@
.card-body
%img.userbox__avatar{ src: current_user.profile_picture.url(:small) }
.profile__name
- unless current_user.display_name.blank?
- unless current_user.profile.display_name.blank?
.profile__display-name
= current_user.display_name
= current_user.profile.display_name
.profile__screen-name
= current_user.screen_name
- unless @list.nil?

View File

@ -4,9 +4,9 @@
.card-body
%img.userbox__avatar{ src: user.profile_picture.url(:small) }
%a.profile__name{ href: show_user_profile_path(user.screen_name) }
- unless user.display_name.blank?
- unless user.profile.display_name.blank?
.profile__display-name
= user.display_name
= user.profile.display_name
.profile__screen-name
= user.screen_name
= render 'user/actions', user: user, type: type

View File

@ -2,9 +2,9 @@
%img.profile__avatar{ src: user.profile_picture.url(:large) }
.card-body
.profile__name
- unless user.display_name.blank?
- unless user.profile.display_name.blank?
.profile__display-name
= user.display_name
= user.profile.display_name
.profile__screen-name
= user.screen_name
.profile__badge-container
@ -24,15 +24,15 @@
%span.badge.badge-success
%i.fa.fa-fw.fa-users
= t 'views.user.title.moderator'
- unless user.bio.blank?
- unless user.profile.description.blank?
.profile__biography
= markdown user.bio
- unless user.website.blank?
= markdown user.profile.description
- unless user.profile.website.blank?
.profile__website
%i.fa.fa-fw.fa-globe
%a{ href: user.website }= user.display_website
- unless user.location.blank?
%a{ href: user.profile.website }= user.profile.display_website
- unless user.profile.location.blank?
.profile__location
%i.fa.fa-fw.fa-location-arrow
= user.location
= user.profile.location
= render 'user/actions', user: user, type: :follower

View File

@ -2,7 +2,7 @@ require 'exporter'
class ExportWorker
include Sidekiq::Worker
sidekiq_options queue: :export, retry: false
sidekiq_options queue: :export, retry: 0
# @param user_id [Integer] the user id
def perform(user_id)

View File

@ -62,6 +62,7 @@ Rails.application.routes.draw do
match '/settings/profile', to: 'user#edit', via: 'get', as: :edit_user_profile
match '/settings/profile', to: 'user#update', via: 'patch', as: :update_user_profile
match '/settings/profile_info', to: 'user#update_profile', via: 'patch', as: :update_user_profile_info
match '/settings/theme', to: 'user#edit_theme', via: 'get', as: :edit_user_theme
match '/settings/theme', to: 'user#update_theme', via: 'patch', as: :update_user_theme

View File

@ -0,0 +1,24 @@
class CreateProfiles < ActiveRecord::Migration[5.2]
def change
create_table :profiles do |t|
t.references :user, index: true, foreign_key: true
t.string :display_name, length: 50
t.string :description, length: 200, null: false, default: ''
t.string :location, length: 72, null: false, default: ''
t.string :website, null: false, default: ''
t.string :motivation_header, null: false, default: ''
t.timestamps
end
transaction do
execute 'INSERT INTO profiles (user_id, display_name, description, location, website, motivation_header, created_at, updated_at) SELECT users.id as user_id, users.display_name, users.bio as description, users.location, users.website, users.motivation_header, users.created_at, users.updated_at FROM users;'
remove_column :users, :display_name
remove_column :users, :bio
remove_column :users, :location
remove_column :users, :website
remove_column :users, :motivation_header
end
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_08_11_133004) do
ActiveRecord::Schema.define(version: 2021_12_19_153054) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -116,6 +116,18 @@ ActiveRecord::Schema.define(version: 2021_08_11_133004) do
t.datetime "updated_at"
end
create_table "profiles", force: :cascade do |t|
t.bigint "user_id"
t.string "display_name"
t.string "description", default: "", null: false
t.string "location", default: "", null: false
t.string "website", default: "", null: false
t.string "motivation_header", default: "", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_profiles_on_user_id"
end
create_table "questions", id: :bigint, default: -> { "gen_timestamp_id('questions'::text)" }, force: :cascade do |t|
t.string "content"
t.boolean "author_is_anonymous"
@ -296,4 +308,5 @@ ActiveRecord::Schema.define(version: 2021_08_11_133004) do
t.index ["user_id"], name: "index_users_roles_on_user_id"
end
add_foreign_key "profiles", "users"
end

View File

@ -34,17 +34,21 @@ class Exporter
private
def collect_user_info
%i(answered_count asked_count ban_reason banned_until bio comment_smiled_count commented_count
confirmation_sent_at confirmed_at created_at crop_h crop_h_h crop_h_w crop_h_x crop_h_y
crop_w crop_x crop_y current_sign_in_at current_sign_in_ip display_name email follower_count friend_count
id last_sign_in_at last_sign_in_ip locale location motivation_header permanently_banned
%i(answered_count asked_count ban_reason banned_until comment_smiled_count commented_count
confirmation_sent_at confirmed_at created_at profile_header profile_header_h profile_header_w profile_header_x profile_header_y
profile_picture_w profile_picture_h profile_picture_x profile_picture_y current_sign_in_at current_sign_in_ip display_name follower_count friend_count
id last_sign_in_at last_sign_in_ip locale permanently_banned
privacy_allow_anonymous_questions privacy_allow_public_timeline privacy_allow_stranger_answers
privacy_show_in_search profile_header_content_type profile_header_file_name profile_header_file_size
profile_header_updated_at profile_picture_content_type profile_picture_file_name profile_picture_file_size
profile_picture_updated_at screen_name show_foreign_themes sign_in_count smiled_count updated_at website).each do |f|
privacy_show_in_search profile_header_file_name profile_picture_file_name
screen_name show_foreign_themes sign_in_count smiled_count updated_at).each do |f|
@obj[f] = @user.send f
end
@obj[:profile] = {}
%i(display_name motivation_header website location description).each do |f|
@obj[:profile][f] = @user.profile.send f
end
EXPORT_ROLES.each do |role|
@obj[role] = @user.has_role?(role)
end
@ -228,11 +232,16 @@ class Exporter
def user_stub(user)
uobj = {}
%i(answered_count asked_count bio comment_smiled_count commented_count created_at display_name follower_count
friend_count id location motivation_header permanently_banned screen_name smiled_count website).each do |f|
%i(answered_count asked_count comment_smiled_count commented_count created_at follower_count
friend_count id permanently_banned screen_name smiled_count).each do |f|
uobj[f] = user.send f
end
uobj[:profile] = {}
%i(display_name motivation_header website location description).each do |f|
uobj[:profile][f] = user.profile.send f
end
EXPORT_ROLES.each do |role|
uobj[role] = user.has_role?(role)
end

View File

@ -43,6 +43,28 @@ describe UserController, type: :controller do
end
end
describe "#update_profile" do
subject { patch :update_profile, params: { profile: profile_params } }
let(:profile_params) do
{
display_name: 'sneaky cune'
}
end
context "user signed in" do
before(:each) { sign_in user }
it "updates the user's profile" do
expect { subject }.to change{ user.profile.reload.display_name }.to('sneaky cune')
end
it "redirects to the edit_user_profile page" do
subject
expect(response).to redirect_to(:edit_user_profile)
end
end
end
describe "#update" do
subject { patch :update, params: { user: header_params } }
let(:header_params) do

View File

@ -6,16 +6,20 @@ FactoryBot.define do
sequence(:email) { |i| "#{i}#{Faker::Internet.email}" }
password { 'P4s5w0rD' }
confirmed_at { Time.now.utc }
display_name { Faker::Name.name }
transient do
roles { [] }
profile { { display_name: Faker::Name.name } }
end
after(:create) do |user, evaluator|
evaluator.roles.each do |role|
user.add_role role
end
evaluator.profile.each do |key, value|
user.profile.public_send(:"#{key}=", value)
end
end
end
end

View File

@ -55,8 +55,8 @@ describe ApplicationHelper, :type => :helper do
describe "#user_opengraph" do
context "sample user" do
let(:user) { FactoryBot.create(:user,
display_name: 'Cunes',
bio: 'A bunch of raccoons in a trenchcoat.',
profile: { display_name: 'Cunes',
description: 'A bunch of raccoons in a trenchcoat.' },
screen_name: 'raccoons') }
subject { user_opengraph(user) }
@ -79,12 +79,12 @@ EOS
describe "#user_twitter_card" do
context "sample user" do
let(:user) { FactoryBot.create(:user,
display_name: '',
bio: 'A bunch of raccoons in a trenchcoat.',
profile: {
display_name: '',
description: 'A bunch of raccoons in a trenchcoat.'},
screen_name: 'raccoons') }
subject { user_twitter_card(user) }
it 'should generate a matching OpenGraph structure for a user' do
expect(subject).to eq(<<-EOS.chomp)
<meta name="twitter:card" content="summary" />

View File

@ -1,3 +1,4 @@
# frozen_string_literal: true
require "factory_bot_rails"