Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3b06bd0
install active storage and migrate
jmilljr24 Sep 12, 2025
fd85f1f
temp fix user update
jmilljr24 Sep 12, 2025
12c1500
add feature flag for active_storage
jmilljr24 Sep 12, 2025
03b8632
ignore local active storage attachments
jmilljr24 Sep 12, 2025
d25e803
add feature flag to workshop attachments
jmilljr24 Sep 12, 2025
9ba7714
add feature flag to all models using paperclip
jmilljr24 Sep 13, 2025
142bfad
check if avatar is attached
jmilljr24 Sep 13, 2025
435d504
add active storage validations gem
jmilljr24 Sep 13, 2025
c36870f
add attachment validations
jmilljr24 Sep 13, 2025
f8ac938
add feature flag to test config
jmilljr24 Sep 13, 2025
c0011a1
fix jpeg content type
jmilljr24 Sep 13, 2025
285f004
add check for attached avatar
jmilljr24 Sep 13, 2025
cf15487
update tests
jmilljr24 Sep 13, 2025
67bde84
add validation gem to test group
jmilljr24 Sep 13, 2025
ab44db3
fix merge
jmilljr24 Sep 13, 2025
db9d047
add validation tests
jmilljr24 Sep 13, 2025
b92bf76
move spec
jmilljr24 Sep 13, 2025
4ac2603
fix rails 8.1 upgrade issues with enums
jmilljr24 Sep 13, 2025
2bd31c6
revert
jmilljr24 Sep 14, 2025
a9c036d
clean up
jmilljr24 Sep 14, 2025
0791a8e
ignore local storage
jmilljr24 Sep 14, 2025
866d5d3
update schema
jmilljr24 Sep 16, 2025
00d9dc0
clean up
jmilljr24 Sep 23, 2025
5fb0b7f
update set_has_attachment
jmilljr24 Sep 23, 2025
61a751b
Remove duplicate gem
maebeale Oct 27, 2025
f497e02
Remove active storage conditionals
maebeale Oct 27, 2025
ddf6ada
Remove more active_storage env var conditionals
maebeale Oct 27, 2025
518a870
Remove merge conflict text
maebeale Oct 27, 2025
d3a05d2
Remove 'image/jpg' type bc it's invalid
maebeale Oct 27, 2025
770177f
Make migration reversible
maebeale Oct 27, 2025
7ebcdee
WIP: testing image usage
maebeale Oct 27, 2025
2c0ea41
Update tests to activestorage (and always show image in navbar)
maebeale Oct 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ node_modules
*.local

.vite
# Ignore local Active Storage
storage
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ gem "country_select"
gem "turbo-rails", "~> 2.0"
gem "stimulus-rails", "~> 1.3"

gem "active_storage_validations", "~> 3.0"

group :development, :test do
gem "better_errors"
gem "brakeman", require: false
Expand Down
7 changes: 7 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ GEM
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
active_storage_validations (3.0.2)
activejob (>= 6.1.4)
activemodel (>= 6.1.4)
activestorage (>= 6.1.4)
activesupport (>= 6.1.4)
marcel (>= 1.0.3)
activejob (8.1.0.beta1)
activesupport (= 8.1.0.beta1)
globalid (>= 0.3.6)
Expand Down Expand Up @@ -446,6 +452,7 @@ PLATFORMS
ruby

DEPENDENCIES
active_storage_validations (~> 3.0)
apipie-rails (~> 1.5.0)
aws-sdk-s3
bcrypt (= 3.1.16)
Expand Down
11 changes: 5 additions & 6 deletions app/decorators/workshop_decorator.rb
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These method should probably be consolidated once the storage attachments have been migrated. The stakeholders only want to upload a single file that can display correctly in different places. legacy should be removed as it was used from a previous data transfer but we need to check what files are actually affected by that.

Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,17 @@ def main_image
end

def thumbnail_image
if legacy && !thumbnail.exists?
main_image
else
# TODO Figure out if we need main_image
if thumbnail.attached?
thumbnail
end
end

def header_image
if !header.exists?
thumbnail_image
else
if header.attached?
header
elsif thumbnail.attached?
thumbnail
end
end

Expand Down
7 changes: 4 additions & 3 deletions app/models/attachment.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
class Attachment < ApplicationRecord
belongs_to :owner, polymorphic: true

has_attached_file :file
validates_attachment :file, content_type: { content_type: %w(application/pdf application/msword image/gif image/jpg image/jpeg image/png) }
ACCEPTED_CONTENT_TYPES = %w[application/pdf application/msword image/gif image/jpeg image/png].freeze
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might we want to expand the list?

here's what i ended up using in another application to support additional document spreadsheet types and avoid upload errors users were having.

for workshops, there's a need to support uploading a video, so i added video types in here too

UPLOAD_TYPES = [

Documents

'application/pdf',
'application/msword',
'application/vnd.ms-word',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/msexcel',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/mspowerpoint',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'text/plain',

Videos

'video/mp4',
'video/quicktime',
'video/x-msvideo',
'video/x-ms-wmv',
'video/mpeg',
'video/webm',
'video/ogg',

Images (regex)

/\Aimage/.*\Z/
]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That definitely makes sense! I was trying to keep this PR as close to a one for one change, at least until all the files are migrated to avoid any confusion on what actually needs to be changes vs. improvements.

Do you think we could open a draft issue for your suggested changes until the migration is complete?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YES, love this separation of concerns

has_one_attached :file
validates :file, content_type: ACCEPTED_CONTENT_TYPES

def name
'Pdf Attachment'
"Pdf Attachment"
end
end
9 changes: 2 additions & 7 deletions app/models/ckeditor/attachment_file.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
class Ckeditor::AttachmentFile < ApplicationRecord # Ckeditor::Asset
# has_attached_file :data,
# :url => "/ckeditor_assets/attachments/:id/:filename",
# :path => "/ckeditor_assets/attachments/:id/:filename"
#
# validates_attachment_presence :data
# validates_attachment_size :data, :less_than => 100.megabytes
# do_not_validate_attachment_file_type :data
has_one_attached :data
validates :data, size: {less_than: 100.megabytes}, attached: true

def url_thumb
@url_thumb ||= nil # Ckeditor::Utils.filethumb(filename)
Expand Down
12 changes: 3 additions & 9 deletions app/models/ckeditor/picture.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
class Ckeditor::Picture < ApplicationRecord # Ckeditor::Asset
# has_attached_file :data,
# :url => "/ckeditor_assets/pictures/:id/:style_:basename.:extension",
# :path => "/ckeditor_assets/pictures/:id/:style_:basename.:extension",
# :styles => { :content => '800>', :thumb => '118x100#' }
#
# validates_attachment_presence :data
# validates_attachment_size :data, :less_than => 2.megabytes
# validates_attachment_content_type :data, :content_type => /\Aimage/
ACCEPTED_CONTENT_TYPES = ["image/jpeg", "image/png"].freeze
has_one_attached :data
validates :data, size: {less_than: 2.megabytes}, content_type: ACCEPTED_CONTENT_TYPES, attached: true

def url_content
url(:content)
Expand All @@ -20,6 +15,5 @@ def url(param)
else
actual_url.gsub("/original", "/thumb")
end

end
end
10 changes: 3 additions & 7 deletions app/models/image.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ class Image < ApplicationRecord
belongs_to :owner, polymorphic: true
belongs_to :report, optional: true

has_attached_file :file,
styles: { medium: '300x300>', thumb: '100x100>' },
default_url:
ActionController::Base.helpers.asset_path(
'workshop_default.png'
)
validates_attachment :file, content_type: { content_type: ["image/jpg", "image/jpeg", "image/png", "image/gif"] }
ACCEPTED_CONTENT_TYPES = ["image/jpeg", "image/png", "image/gif"].freeze
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment -- how about this instead?

/\Aimage/.*\Z/

has_one_attached :file
validates :file, content_type: ACCEPTED_CONTENT_TYPES
end
16 changes: 7 additions & 9 deletions app/models/media_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ class MediaFile < ApplicationRecord
belongs_to :workshop, optional: true
belongs_to :workshop_log, optional: true

has_attached_file :file

FORM_FILE_CONTENT_TYPES = ["image/jpeg", "image/jpg", "image/png",
"application/pdf", "application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]

validates_attachment :file, content_type: { content_type: FORM_FILE_CONTENT_TYPES }
FORM_FILE_CONTENT_TYPES = ["image/jpeg", "image/png",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you remove image/jpg on purpose?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! jpg is not a valid MIME type 😃

"application/pdf", "application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]
has_one_attached :file
validates :file, content_type: FORM_FILE_CONTENT_TYPES
end
2 changes: 1 addition & 1 deletion app/models/project_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class ProjectUser < ApplicationRecord
belongs_to :project
belongs_to :user

scope :liaisons, -> { where(position: 1) }
scope :liaisons, -> { where(position: "liaison") }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was changed for readability with @marc during debugging an enum issue with the rails 8.1 upgrade.

# Validations
validates_presence_of :project_id

Expand Down
52 changes: 25 additions & 27 deletions app/models/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ class Report < ApplicationRecord
belongs_to :windows_type

belongs_to :owner, polymorphic: true, optional: true
has_one :form, as: :owner
has_one :image
has_one :form, as: :owner
has_one :image
validate :image_valid?
has_attached_file :form_file

before_save :set_has_attachament
FORM_FILE_CONTENT_TYPES = %w[application/pdf application/msword
application/vnd.openxmlformats-officedocument.wordprocessingml.document application/vnd.ms-excel
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet]
has_one_attached :form_file
validates :form_file, content_type: FORM_FILE_CONTENT_TYPES

FORM_FILE_CONTENT_TYPES = %w[application/pdf application/msword application/vnd.openxmlformats-officedocument.wordprocessingml.document application/vnd.ms-excel application/vnd.openxmlformats-officedocument.spreadsheetml.sheet]

validates_attachment :form_file, content_type: { content_type: FORM_FILE_CONTENT_TYPES }
before_save :set_has_attachament # TODO verify set_has_attachament works as expected once this feature is enabled in the UI

has_many :images
has_many :form_fields, through: :form
Expand All @@ -23,15 +24,15 @@ class Report < ApplicationRecord
has_many :notifications, as: :noticeable, dependent: :destroy
has_many :sectorable_items, as: :sectorable, dependent: :destroy
has_many :sectors, through: :sectorable_items, dependent: :destroy
scope :in_month, -> (date) { where(created_at: date.beginning_of_month..date.end_of_month) }
scope :in_month, ->(date) { where(created_at: date.beginning_of_month..date.end_of_month) }

has_many :media_files, dependent: :destroy
accepts_nested_attributes_for :media_files

accepts_nested_attributes_for :report_form_field_answers,
reject_if: proc { |object|
(object['_create'].to_i == 0 && object['answer'].nil?)
}
reject_if: proc { |object|
object["_create"].to_i == 0 && object["answer"].nil?
}
accepts_nested_attributes_for :quotable_item_quotes

after_create :set_windows_type
Expand All @@ -45,13 +46,13 @@ def users_admin_type
when "MonthlyReport"
"#{type} - Monthly Report Date: #{date_label} - User: #{user.full_name if user}"
when "Report"
if owner_type and owner_type == 'Resource'
"#{type} - #{owner ? owner_type : '[ EMPTY ]'} - User: #{user.full_name if user}"
if owner_type and owner_type == "Resource"
"#{type} - #{owner ? owner_type : "[ EMPTY ]"} - User: #{user.full_name if user}"
else
"#{type} - #{owner ? owner.type : '[ EMPTY ]'} - User: #{user.full_name if user}"
"#{type} - #{owner ? owner.type : "[ EMPTY ]"} - User: #{user.full_name if user}"
end
else
"#{type} - #{owner ? owner.communal_label(self) : '[ EMPTY ]'} - User: #{user.full_name if user}"
"#{type} - #{owner ? owner.communal_label(self) : "[ EMPTY ]"} - User: #{user.full_name if user}"
end
end
end
Expand All @@ -61,7 +62,7 @@ def story?
end

def date_label
"#{date ? date.strftime('%m/%d/%y') : '[ EMPTY ]'}"
"#{date ? date.strftime("%m/%d/%y") : "[ EMPTY ]"}"
end

def delete_and_update_all(quotes_params, log_fields, image = nil)
Expand All @@ -73,7 +74,7 @@ def delete_and_update_all(quotes_params, log_fields, image = nil)

unless image.blank?
self.image.destroy if self.image
self.image = Image.new( file: image )
self.image = Image.new(file: image)
end

save
Expand All @@ -90,8 +91,8 @@ def image_valid?

def log_fields
if form_builder
form_builder.forms[0].form_fields.where('ordering is not null and status = 1').
order(ordering: :desc).all
form_builder.forms[0].form_fields.where("ordering is not null and status = 1")
.order(ordering: :desc).all
else
[]
end
Expand All @@ -105,7 +106,7 @@ def form_builder
FormBuilder.find(2)
end
elsif owner and owner_type.include? "FormBuilder"
FormBuilder.find(7)
FormBuilder.find(7)
else
nil
end
Expand All @@ -120,21 +121,18 @@ def title
end

def name
type ? type.titleize : 'Report'
type ? type.titleize : "Report"
end

def display_date
created_at.strftime('%B %e, %Y')
created_at.strftime("%B %e, %Y")
end

private

def set_has_attachament
self.has_attachment = false

unless self.image.blank? and self.media_files.empty? and self.form_file.blank?
self.has_attachment = true
end
self.has_attachment = image.attached? || form_file.attached? ||
media_files.any? { |media_file| media_file.file.attached? }
end

def set_windows_type
Expand Down
10 changes: 5 additions & 5 deletions app/models/resource.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class Resource < ApplicationRecord
include Rails.application.routes.url_helpers

# Associations
belongs_to :user
belongs_to :workshop, optional: true
Expand Down Expand Up @@ -81,12 +83,10 @@ def main_image
end

def main_image_url
return main_image.file.url if main_image

if self.kind == "Theme"
ActionController::Base.helpers.asset_path("workshop_default.png")
if main_image&.file&.attached?
url_for(main_image.file)
else
"/images/workshop_default.jpg"
ActionController::Base.helpers.asset_path("workshop_default.png")
end
end

Expand Down
18 changes: 10 additions & 8 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ class User < ApplicationRecord
after_create :set_default_values
before_destroy :reassign_reports_and_logs_to_orphaned_user

# Avatar
ACCEPTED_CONTENT_TYPES = ["image/jpeg", "image/png" ].freeze
has_one_attached :avatar
validates :avatar, content_type: ACCEPTED_CONTENT_TYPES

# Associations
belongs_to :facilitator, optional: true
has_many :workshops
has_many :workshop_logs
has_many :reports
has_many :communal_reports, through: :projects, source: :reports
has_many :bookmarks, dependent: :destroy
has_many :bookmarked_workshops, through: :bookmarks, source: :bookmarkable, source_type: 'Workshop'
has_many :bookmarked_resources, through: :bookmarks, source: :bookmarkable, source_type: 'Resource'
has_many :bookmarked_events, through: :bookmarks, source: :bookmarkable, source_type: 'Event'
has_many :bookmarked_workshops, through: :bookmarks, source: :bookmarkable, source_type: "Workshop"
has_many :bookmarked_resources, through: :bookmarks, source: :bookmarkable, source_type: "Resource"
has_many :bookmarked_events, through: :bookmarks, source: :bookmarkable, source_type: "Event"
has_many :project_users, dependent: :destroy
has_many :projects, through: :project_users
has_many :windows_types, through: :projects
Expand All @@ -28,10 +33,6 @@ class User < ApplicationRecord
has_many :colleagues, -> { select(:user_id, :position, :project_id).distinct }, through: :projects, source: :project_users
has_many :notifications, as: :noticeable

# Avatar
has_attached_file :avatar, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: "/images/missing.png"
validates_attachment_content_type :avatar, content_type: /\Aimage\/.*\z/

# Nested
accepts_nested_attributes_for :user_forms
accepts_nested_attributes_for :project_users, reject_if: :all_blank, allow_destroy: true
Expand Down Expand Up @@ -142,7 +143,7 @@ def name
end

def agency_name
agency ? agency.name : 'No agency.'
agency ? agency.name : "No agency."
end

def has_bookmarkable?(bookmarkable, type: nil)
Expand All @@ -168,6 +169,7 @@ def set_default_values
adult_perm = Permission.find_by(security_cat: "Adult Windows")
children_perm = Permission.find_by(security_cat: "Children's Windows")


self.permissions << combined_perm
self.permissions << adult_perm
self.permissions << children_perm
Expand Down
Loading