There are three major things occurring in this pull request: 1. Renaming `Article#update_cached_user` to `Article#set_cached_entities`. 2. Reducing an organization's direct knowledge of which of the org's attributes an article caches. 3. Removing duplicate calls to update the article associated with the organization. For renaming to `Article#set_cached_entities`, the prior method implied we were updating the persistence layer. However, we were not making any save nor update calls. This rename should clarify intention. For reducing knowledge, the comments for `Article::ATTRIBUTES_CACHED_FOR_RELATED_ENTITY` should explain the details. And last, removing the duplicate calls; we had three methods that were attempting to build and update the `Article#cached_organization`'s value. Closes forem/forem#17041
148 lines
4.8 KiB
Ruby
148 lines
4.8 KiB
Ruby
class Organization < ApplicationRecord
|
|
include CloudinaryHelper
|
|
|
|
include Images::Profile.for(:profile_image_url)
|
|
|
|
COLOR_HEX_REGEXP = /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/
|
|
INTEGER_REGEXP = /\A\d+\z/
|
|
SLUG_REGEXP = /\A[a-zA-Z0-9\-_]+\z/
|
|
|
|
acts_as_followable
|
|
|
|
before_validation :downcase_slug
|
|
before_validation :check_for_slug_change
|
|
before_validation :evaluate_markdown
|
|
|
|
before_save :remove_at_from_usernames
|
|
before_save :generate_secret
|
|
|
|
after_save :bust_cache
|
|
|
|
after_update_commit :conditionally_update_articles
|
|
after_destroy_commit :bust_cache
|
|
|
|
has_many :articles, dependent: :nullify
|
|
has_many :collections, dependent: :nullify
|
|
has_many :credits, dependent: :restrict_with_error
|
|
has_many :display_ads, dependent: :destroy
|
|
has_many :listings, dependent: :destroy
|
|
has_many :notifications, dependent: :delete_all
|
|
has_many :organization_memberships, dependent: :delete_all
|
|
has_many :profile_pins, as: :profile, inverse_of: :profile, dependent: :destroy
|
|
has_many :sponsorships, dependent: :destroy
|
|
has_many :unspent_credits, -> { where spent: false }, class_name: "Credit", inverse_of: :organization
|
|
has_many :users, through: :organization_memberships
|
|
|
|
validates :articles_count, presence: true
|
|
validates :bg_color_hex, format: COLOR_HEX_REGEXP, allow_blank: true
|
|
validates :company_size, format: { with: INTEGER_REGEXP, message: :integer_only, allow_blank: true }
|
|
validates :company_size, length: { maximum: 7 }, allow_nil: true
|
|
validates :credits_count, presence: true
|
|
validates :cta_body_markdown, length: { maximum: 256 }
|
|
validates :cta_button_text, length: { maximum: 20 }
|
|
validates :cta_button_url, length: { maximum: 150 }, url: { allow_blank: true, no_local: true }
|
|
validates :github_username, length: { maximum: 50 }
|
|
validates :location, :email, length: { maximum: 64 }
|
|
validates :name, :summary, :url, :profile_image, presence: true
|
|
validates :name, length: { maximum: 50 }
|
|
validates :proof, length: { maximum: 1500 }
|
|
validates :secret, length: { is: 100 }, allow_nil: true
|
|
validates :secret, uniqueness: true
|
|
validates :slug, exclusion: { in: ReservedWords.all, message: :reserved_word }
|
|
validates :slug, format: { with: SLUG_REGEXP }, length: { in: 2..18 }
|
|
validates :slug, presence: true, uniqueness: { case_sensitive: false }
|
|
validates :spent_credits_count, presence: true
|
|
validates :summary, length: { maximum: 250 }
|
|
validates :tag_line, length: { maximum: 60 }
|
|
validates :tech_stack, :story, length: { maximum: 640 }
|
|
validates :text_color_hex, format: COLOR_HEX_REGEXP, allow_blank: true
|
|
validates :twitter_username, length: { maximum: 15 }
|
|
validates :unspent_credits_count, presence: true
|
|
validates :url, length: { maximum: 200 }, url: { allow_blank: true, no_local: true }
|
|
|
|
validates :slug, unique_cross_model_slug: true, if: :slug_changed?
|
|
|
|
mount_uploader :profile_image, ProfileImageUploader
|
|
mount_uploader :nav_image, ProfileImageUploader
|
|
mount_uploader :dark_nav_image, ProfileImageUploader
|
|
|
|
alias_attribute :username, :slug
|
|
alias_attribute :old_username, :old_slug
|
|
alias_attribute :old_old_username, :old_old_slug
|
|
alias_attribute :website_url, :url
|
|
|
|
def self.integer_only
|
|
I18n.t("models.organization.integer_only")
|
|
end
|
|
|
|
def self.reserved_word
|
|
I18n.t("models.organization.reserved_word")
|
|
end
|
|
|
|
def check_for_slug_change
|
|
return unless slug_changed?
|
|
|
|
self.old_old_slug = old_slug
|
|
self.old_slug = slug_was
|
|
Organizations::UpdateOrganizationArticlesPathsWorker.perform_async(id, slug_was, slug)
|
|
end
|
|
|
|
def path
|
|
"/#{slug}"
|
|
end
|
|
|
|
def generate_secret
|
|
self.secret = generated_random_secret if secret.blank?
|
|
end
|
|
|
|
def generated_random_secret
|
|
SecureRandom.hex(50)
|
|
end
|
|
|
|
def approved_and_filled_out_cta?
|
|
cta_processed_html?
|
|
end
|
|
|
|
def profile_image_90
|
|
profile_image_url_for(length: 90)
|
|
end
|
|
|
|
def enough_credits?(num_credits_needed)
|
|
credits.unspent.size >= num_credits_needed
|
|
end
|
|
|
|
def destroyable?
|
|
organization_memberships.count == 1 && articles.count.zero? && credits.count.zero?
|
|
end
|
|
|
|
# NOTE: We use Organization and User objects interchangeably. Since the former
|
|
# don't have profiles we return self instead.
|
|
def profile
|
|
self
|
|
end
|
|
|
|
private
|
|
|
|
def evaluate_markdown
|
|
self.cta_processed_html = MarkdownProcessor::Parser.new(cta_body_markdown).evaluate_limited_markdown
|
|
end
|
|
|
|
def remove_at_from_usernames
|
|
self.twitter_username = twitter_username.delete("@") if twitter_username
|
|
self.github_username = github_username.delete("@") if github_username
|
|
end
|
|
|
|
def downcase_slug
|
|
self.slug = slug&.downcase
|
|
end
|
|
|
|
def conditionally_update_articles
|
|
return unless Article::ATTRIBUTES_CACHED_FOR_RELATED_ENTITY.detect { |attr| saved_change_to_attribute?(attr) }
|
|
|
|
articles.each(&:save)
|
|
end
|
|
|
|
def bust_cache
|
|
Organizations::BustCacheWorker.perform_async(id, slug)
|
|
end
|
|
end
|