diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-06-26 01:59:57 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-06-26 01:59:57 +0200 |
| commit | c06723ee715512c2033c7786c48f15674585b56b (patch) | |
| tree | 46d074bde9a4fc61f0a76cbc601007ed4412ec61 /app/models | |
| parent | 0818a3057b0a91e422158d828026c941b4e10622 (diff) | |
Stage 4: Rails 5.2 -> 6.1 on Ruby 2.7.2
- routing-filter 0.6.3 -> 0.7.0 (Rails 6.1 compatibility)
- RSS named routes rss_xml/rss_rdf added
- RouteWithParams workarounds: will_paginate_patch, content_path shim, safe_path helper
- Paperclip removed, replaced with FileAttachment concern (preserves URL scheme)
- Assets resource moved to /admin/assets (Sprockets middleware conflict)
- ApplicationRecord base class added, all models migrated
- Strong parameters added to Assets, Occurrences, Events, MenuItems controllers
- update_attributes -> update throughout
- render :nothing -> head :ok/:not_found throughout
- language_selector rewritten (removes :overwrite_params)
- Environment files updated for Rails 6.1 (eager_load, public_file_server, ActionMailer)
- Arel::Visitors::DepthFirst and Integer/Float duration patches removed from test_helper
- AssetsController tests added (10 tests covering upload, variants, destroy, auth)
- ImageMagick geometry: 460x250! for headline crop (not # which is invalid in IM6)
129 runs, 311 assertions, 5 failures (all pre-existing), 0 errors
Diffstat (limited to 'app/models')
| -rw-r--r-- | app/models/application_record.rb | 3 | ||||
| -rw-r--r-- | app/models/asset.rb | 17 | ||||
| -rw-r--r-- | app/models/concerns/file_attachment.rb | 124 | ||||
| -rw-r--r-- | app/models/event.rb | 2 | ||||
| -rw-r--r-- | app/models/menu_item.rb | 2 | ||||
| -rw-r--r-- | app/models/node.rb | 2 | ||||
| -rw-r--r-- | app/models/occurrence.rb | 2 | ||||
| -rw-r--r-- | app/models/page.rb | 2 | ||||
| -rw-r--r-- | app/models/permission.rb | 2 | ||||
| -rw-r--r-- | app/models/related_asset.rb | 2 | ||||
| -rw-r--r-- | app/models/user.rb | 2 |
11 files changed, 139 insertions, 21 deletions
diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000..10a4cba --- /dev/null +++ b/app/models/application_record.rb | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | class ApplicationRecord < ActiveRecord::Base | ||
| 2 | self.abstract_class = true | ||
| 3 | end | ||
diff --git a/app/models/asset.rb b/app/models/asset.rb index f6526f2..aca0ee8 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb | |||
| @@ -1,20 +1,11 @@ | |||
| 1 | class Asset < ActiveRecord::Base | 1 | class Asset < ApplicationRecord |
| 2 | |||
| 3 | include FileAttachment | ||
| 2 | 4 | ||
| 3 | has_many :related_assets, :dependent => :destroy | 5 | has_many :related_assets, :dependent => :destroy |
| 4 | has_many :pages, :through => :related_assets | 6 | has_many :pages, :through => :related_assets |
| 5 | 7 | ||
| 6 | has_attached_file( | 8 | scope :images, -> { where(:upload_content_type => ["image/gif", "image/jpeg", "image/png", "image/webp"]) } |
| 7 | :upload, | ||
| 8 | :path => ":rails_root/public/system/:attachment/:id/:style/:filename", | ||
| 9 | :url => "/system/:attachment/:id/:style/:filename", | ||
| 10 | :styles => { | ||
| 11 | :medium => "300x300", | ||
| 12 | :thumb => "100x100", | ||
| 13 | :headline => "460x250#" | ||
| 14 | } | ||
| 15 | ) | ||
| 16 | |||
| 17 | scope :images, -> { where(:upload_content_type => ["image/gif", "image/jpeg", "image/png"]) } | ||
| 18 | scope :documents, -> { where(:upload_content_type => ["application/pdf", "text/plain", "text/rtf"]) } | 9 | scope :documents, -> { where(:upload_content_type => ["application/pdf", "text/plain", "text/rtf"]) } |
| 19 | scope :audio, -> { where(:upload_content_type => ["audio/mpeg", "audio/x-m4a", "audio/wav", "audio/x-wav"]) } | 10 | scope :audio, -> { where(:upload_content_type => ["audio/mpeg", "audio/x-m4a", "audio/wav", "audio/x-wav"]) } |
| 20 | 11 | ||
diff --git a/app/models/concerns/file_attachment.rb b/app/models/concerns/file_attachment.rb new file mode 100644 index 0000000..b3ff0f1 --- /dev/null +++ b/app/models/concerns/file_attachment.rb | |||
| @@ -0,0 +1,124 @@ | |||
| 1 | # FileAttachment — minimal drop-in replacement for Paperclip's has_attached_file. | ||
| 2 | # | ||
| 3 | # Provides the same interface used throughout this codebase: | ||
| 4 | # asset.upload.url -> "/system/uploads/:id/original/:filename" | ||
| 5 | # asset.upload.url(:thumb) -> "/system/uploads/:id/thumb/:filename" | ||
| 6 | # asset.upload.content_type -> string | ||
| 7 | # asset.upload.size -> integer (bytes) | ||
| 8 | # | ||
| 9 | # Files are stored at: | ||
| 10 | # Rails.root/public/system/uploads/:id/:style/:filename | ||
| 11 | # | ||
| 12 | # Image variants are generated via ImageMagick (convert) on upload. | ||
| 13 | # Non-image files get only an original, no variants. | ||
| 14 | # | ||
| 15 | # To replace an asset: assign a new file to asset.upload= and save. | ||
| 16 | # The filename is fixed on first upload and preserved on replacement, | ||
| 17 | # keeping all public URLs stable. | ||
| 18 | # | ||
| 19 | # Future: if more sophisticated asset management is needed (versioning, | ||
| 20 | # S3, on-demand resizing), replace this module and keep the interface. | ||
| 21 | |||
| 22 | module FileAttachment | ||
| 23 | extend ActiveSupport::Concern | ||
| 24 | |||
| 25 | STYLES = { | ||
| 26 | medium: { geometry: "300x300>", format: nil }, | ||
| 27 | thumb: { geometry: "100x100>", format: nil }, | ||
| 28 | headline: { geometry: "460x250!", format: nil } | ||
| 29 | }.freeze | ||
| 30 | |||
| 31 | IMAGE_CONTENT_TYPES = %w[image/jpeg image/gif image/png image/webp].freeze | ||
| 32 | |||
| 33 | included do | ||
| 34 | attr_reader :upload | ||
| 35 | |||
| 36 | after_initialize :build_upload_proxy | ||
| 37 | after_save :process_upload | ||
| 38 | before_destroy :delete_upload_files | ||
| 39 | end | ||
| 40 | |||
| 41 | def upload=(uploaded_file) | ||
| 42 | return if uploaded_file.blank? | ||
| 43 | @pending_upload = uploaded_file | ||
| 44 | # Populate the database columns immediately so validations can use them | ||
| 45 | self.upload_file_name = sanitize_filename(uploaded_file.original_filename) | ||
| 46 | self.upload_content_type = uploaded_file.content_type.to_s.split(';').first.strip | ||
| 47 | self.upload_file_size = uploaded_file.size | ||
| 48 | self.upload_updated_at = Time.current | ||
| 49 | build_upload_proxy | ||
| 50 | end | ||
| 51 | |||
| 52 | private | ||
| 53 | |||
| 54 | def build_upload_proxy | ||
| 55 | @upload = UploadProxy.new(self) | ||
| 56 | end | ||
| 57 | |||
| 58 | def process_upload | ||
| 59 | return unless @pending_upload | ||
| 60 | uploaded_file = @pending_upload | ||
| 61 | @pending_upload = nil | ||
| 62 | |||
| 63 | original_path = file_path(:original) | ||
| 64 | FileUtils.mkdir_p(File.dirname(original_path)) | ||
| 65 | FileUtils.cp(uploaded_file.tempfile.path, original_path) | ||
| 66 | |||
| 67 | if IMAGE_CONTENT_TYPES.include?(upload_content_type) | ||
| 68 | generate_variants(original_path) | ||
| 69 | end | ||
| 70 | end | ||
| 71 | |||
| 72 | def generate_variants(original_path) | ||
| 73 | STYLES.each do |style, options| | ||
| 74 | dest_path = file_path(style) | ||
| 75 | FileUtils.mkdir_p(File.dirname(dest_path)) | ||
| 76 | system("convert", original_path, "-resize", options[:geometry], dest_path) | ||
| 77 | end | ||
| 78 | end | ||
| 79 | |||
| 80 | def delete_upload_files | ||
| 81 | dir = Rails.root.join("public", "system", "uploads", id.to_s) | ||
| 82 | FileUtils.rm_rf(dir) if Dir.exist?(dir) | ||
| 83 | end | ||
| 84 | |||
| 85 | def file_path(style) | ||
| 86 | Rails.root.join( | ||
| 87 | "public", "system", "uploads", | ||
| 88 | id.to_s, style.to_s, upload_file_name | ||
| 89 | ).to_s | ||
| 90 | end | ||
| 91 | |||
| 92 | def sanitize_filename(filename) | ||
| 93 | File.basename(filename).gsub(/[^\w\.\-]/, '_') | ||
| 94 | end | ||
| 95 | |||
| 96 | # Proxy object returned by asset.upload, providing the Paperclip-compatible | ||
| 97 | # interface used in views: .url, .url(:style), .content_type, .size | ||
| 98 | class UploadProxy | ||
| 99 | def initialize(record) | ||
| 100 | @record = record | ||
| 101 | end | ||
| 102 | |||
| 103 | def url(style = :original) | ||
| 104 | return "" if @record.upload_file_name.blank? | ||
| 105 | "/system/uploads/#{@record.id}/#{style}/#{@record.upload_file_name}" | ||
| 106 | end | ||
| 107 | |||
| 108 | def content_type | ||
| 109 | @record.upload_content_type.to_s | ||
| 110 | end | ||
| 111 | |||
| 112 | def size | ||
| 113 | @record.upload_file_size.to_i | ||
| 114 | end | ||
| 115 | |||
| 116 | def present? | ||
| 117 | @record.upload_file_name.present? | ||
| 118 | end | ||
| 119 | |||
| 120 | def blank? | ||
| 121 | !present? | ||
| 122 | end | ||
| 123 | end | ||
| 124 | end | ||
diff --git a/app/models/event.rb b/app/models/event.rb index 23deed6..94a22e3 100644 --- a/app/models/event.rb +++ b/app/models/event.rb | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | class Event < ActiveRecord::Base | 1 | class Event < ApplicationRecord |
| 2 | 2 | ||
| 3 | # Associations | 3 | # Associations |
| 4 | 4 | ||
diff --git a/app/models/menu_item.rb b/app/models/menu_item.rb index eb82347..7769b7f 100644 --- a/app/models/menu_item.rb +++ b/app/models/menu_item.rb | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | class MenuItem < ActiveRecord::Base | 1 | class MenuItem < ApplicationRecord |
| 2 | 2 | ||
| 3 | default_scope -> { where(:type => "MenuItem") } | 3 | default_scope -> { where(:type => "MenuItem") } |
| 4 | 4 | ||
diff --git a/app/models/node.rb b/app/models/node.rb index d760f0a..f7a70d0 100644 --- a/app/models/node.rb +++ b/app/models/node.rb | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | class Node < ActiveRecord::Base | 1 | class Node < ApplicationRecord |
| 2 | # Mixins and Plugins | 2 | # Mixins and Plugins |
| 3 | acts_as_nested_set | 3 | acts_as_nested_set |
| 4 | 4 | ||
diff --git a/app/models/occurrence.rb b/app/models/occurrence.rb index 8457ffd..3baf447 100644 --- a/app/models/occurrence.rb +++ b/app/models/occurrence.rb | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | # TODO Make a gem out of the c wrapper | 1 | # TODO Make a gem out of the c wrapper |
| 2 | require 'chaos_calendar' | 2 | require 'chaos_calendar' |
| 3 | 3 | ||
| 4 | class Occurrence < ActiveRecord::Base | 4 | class Occurrence < ApplicationRecord |
| 5 | 5 | ||
| 6 | # Associations | 6 | # Associations |
| 7 | 7 | ||
diff --git a/app/models/page.rb b/app/models/page.rb index 93debf8..d1e7439 100644 --- a/app/models/page.rb +++ b/app/models/page.rb | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | require 'xml' | 1 | require 'xml' |
| 2 | 2 | ||
| 3 | class Page < ActiveRecord::Base | 3 | class Page < ApplicationRecord |
| 4 | 4 | ||
| 5 | PUBLIC_TEMPLATE_PATH = File.join(%w(custom page_templates public)) | 5 | PUBLIC_TEMPLATE_PATH = File.join(%w(custom page_templates public)) |
| 6 | FULL_PUBLIC_TEMPLATE_PATH = Rails.root.join('app', 'views', PUBLIC_TEMPLATE_PATH) | 6 | FULL_PUBLIC_TEMPLATE_PATH = Rails.root.join('app', 'views', PUBLIC_TEMPLATE_PATH) |
diff --git a/app/models/permission.rb b/app/models/permission.rb index f304538..1383a4b 100644 --- a/app/models/permission.rb +++ b/app/models/permission.rb | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | class Permission < ActiveRecord::Base | 1 | class Permission < ApplicationRecord |
| 2 | # Validations | 2 | # Validations |
| 3 | validates_presence_of :user_id, :node_id, :granted | 3 | validates_presence_of :user_id, :node_id, :granted |
| 4 | validates_inclusion_of :granted, :in => [true, false] | 4 | validates_inclusion_of :granted, :in => [true, false] |
diff --git a/app/models/related_asset.rb b/app/models/related_asset.rb index 2b61c51..8f16460 100644 --- a/app/models/related_asset.rb +++ b/app/models/related_asset.rb | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | class RelatedAsset < ActiveRecord::Base | 1 | class RelatedAsset < ApplicationRecord |
| 2 | belongs_to :page | 2 | belongs_to :page |
| 3 | belongs_to :asset | 3 | belongs_to :asset |
| 4 | 4 | ||
diff --git a/app/models/user.rb b/app/models/user.rb index a2540b5..92ac33a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | require 'digest/sha1' | 1 | require 'digest/sha1' |
| 2 | 2 | ||
| 3 | class User < ActiveRecord::Base | 3 | class User < ApplicationRecord |
| 4 | # Mixins and Plugins | 4 | # Mixins and Plugins |
| 5 | include Authentication | 5 | include Authentication |
| 6 | include Authentication::ByPassword | 6 | include Authentication::ByPassword |
