docbrown/app/models/profile.rb
Jeremy Friesen ee0a521a83
Favoring delete_all on user relationships (#15443)
* Favoring delete_all on user relationships

Prior to this commit, several of the user's "has_many" were marked as
`depenedent: :destroy`.

In the case of :destroy, ActiveRecord instantiates each object and then
runs destroy. Whereas in the case of :delete, ActiveRecord issues a SQL
command to delete the related files.

It is often "safer" to use :destroy, as it guarantees that you'll
instantiate the record and run it's callbacks. But sometimes you have
to go with the speed of SQL.

And while not directly related to #15424 it is representative of our
callback ecosystem creating some unexpected computational loads.

Related to #15442 and #15424

* Noting models that user no longer cascade destroys
2021-11-23 11:50:12 -05:00

60 lines
2 KiB
Ruby

# @note When we destroy the related user, it's using dependent:
# :delete for the relationship. That means no before/after
# destroy callbacks will be called on this object.
class Profile < ApplicationRecord
belongs_to :user
validates :user_id, uniqueness: true
validates :location, :website_url, length: { maximum: 100 }
validates :website_url, url: { allow_blank: true, no_local: true, schemes: %w[https http] }
validates_with ProfileValidator
ATTRIBUTE_NAME_REGEX = /(?<attribute_name>\w+)=?/
CACHE_KEY = "profile/attributes".freeze
# Static fields are columns on the profiles table; they have no relationship
# to a ProfileField record. These are columns we can safely assume exist for
# any profile on a given Forem.
STATIC_FIELDS = %w[summary location website_url].freeze
# Update the Rails cache with the currently available attributes.
def self.refresh_attributes!
Rails.cache.delete(CACHE_KEY)
attributes
end
def self.attributes
Rails.cache.fetch(CACHE_KEY, expires_in: 24.hours) do
ProfileField.pluck(:attribute_name)
end
end
def self.static_fields
STATIC_FIELDS
end
def clear!
update(data: {})
end
# Lazily add accessors for profile fields on first use
def method_missing(method_name, *args, **kwargs, &block)
match = method_name.match(ATTRIBUTE_NAME_REGEX)
field = ProfileField.find_by(attribute_name: match[:attribute_name])
super unless field
self.class.instance_eval do
store_attribute :data, field.attribute_name.to_sym, :string
end
public_send(method_name, *args, **kwargs, &block)
end
# Defining this is not only a good practice in general, it's also necessary
# for `update` to work since the `_assign_attribute` helper it uses performs
# an explicit `responds_to?` check.
def respond_to_missing?(method_name, include_private = false)
match = method_name.match(ATTRIBUTE_NAME_REGEX)
return true if match[:attribute_name].in?(self.class.attributes)
super
end
end