mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Replace Paperclippable ContentConfig
The old Paperclip configuration was very clever and easy to use but it was also a complicated implementation building on the complicated Spree preference system. I simplified this with Active Storage, storing simple references to blob ids and default URLs as backup.
This commit is contained in:
@@ -10,14 +10,14 @@ module Admin
|
||||
|
||||
def update
|
||||
params.each do |name, value|
|
||||
if ContentConfig.has_preference?(name) || ContentConfig.has_attachment?(name)
|
||||
ContentConfig.public_send("#{name}=", value)
|
||||
if value.is_a?(ActionDispatch::Http::UploadedFile)
|
||||
blob = store_file(value)
|
||||
update_preference("#{name}_blob_id", blob.id)
|
||||
else
|
||||
update_preference(name, value)
|
||||
end
|
||||
end
|
||||
|
||||
# Save any uploaded images
|
||||
ContentConfig.save
|
||||
|
||||
flash[:success] =
|
||||
t(:successfully_updated, resource: I18n.t('admin.contents.edit.your_content'))
|
||||
|
||||
@@ -26,6 +26,22 @@ module Admin
|
||||
|
||||
private
|
||||
|
||||
def store_file(attachable)
|
||||
ActiveStorage::Blob.create_and_upload!(
|
||||
io: attachable.open,
|
||||
filename: attachable.original_filename,
|
||||
content_type: attachable.content_type,
|
||||
service_name: :local,
|
||||
identify: false,
|
||||
)
|
||||
end
|
||||
|
||||
def update_preference(name, value)
|
||||
return unless ContentConfig.has_preference?(name)
|
||||
|
||||
ContentConfig.public_send("#{name}=", value)
|
||||
end
|
||||
|
||||
def preference_sections
|
||||
[
|
||||
PreferenceSections::HeaderSection.new,
|
||||
|
||||
43
app/models/concerns/file_preferences.rb
Normal file
43
app/models/concerns/file_preferences.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module FilePreferences
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
@default_urls = {}
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def file_preference(name, default_url: nil)
|
||||
preference "#{name}_blob_id", :integer
|
||||
@default_urls[name] = default_url if default_url
|
||||
end
|
||||
|
||||
def default_url(name)
|
||||
@default_urls[name]
|
||||
end
|
||||
end
|
||||
|
||||
def preference_type(key)
|
||||
if has_preference?("#{key}_blob_id")
|
||||
:file
|
||||
else
|
||||
super(key)
|
||||
end
|
||||
end
|
||||
|
||||
def url_for(name)
|
||||
blob = blob_for(name)
|
||||
|
||||
if blob
|
||||
Rails.application.routes.url_helpers.url_for(blob)
|
||||
else
|
||||
self.class.default_url(name)
|
||||
end
|
||||
end
|
||||
|
||||
def blob_for(name)
|
||||
blob_id = get_preference("#{name}_blob_id")
|
||||
ActiveStorage::Blob.find_by(id: blob_id) if blob_id
|
||||
end
|
||||
end
|
||||
@@ -1,23 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'open_food_network/paperclippable'
|
||||
|
||||
class ContentConfiguration < Spree::Preferences::FileConfiguration
|
||||
include OpenFoodNetwork::Paperclippable
|
||||
class ContentConfiguration < Spree::Preferences::Configuration
|
||||
include FilePreferences
|
||||
|
||||
# Header
|
||||
preference :logo, :file
|
||||
preference :logo_mobile, :file
|
||||
preference :logo_mobile_svg, :file
|
||||
has_attached_file :logo, default_url: "/default_images/ofn-logo.png"
|
||||
has_attached_file :logo_mobile
|
||||
has_attached_file :logo_mobile_svg, default_url: "/default_images/ofn-logo-mobile.svg"
|
||||
file_preference :logo, default_url: "/default_images/ofn-logo.png"
|
||||
file_preference :logo_mobile
|
||||
file_preference :logo_mobile_svg, default_url: "/default_images/ofn-logo-mobile.svg"
|
||||
|
||||
# Home page
|
||||
preference :home_page_alert_html, :text
|
||||
preference :home_hero, :file
|
||||
file_preference :home_hero, default_url: "/default_images/home.jpg"
|
||||
preference :home_show_stats, :boolean, default: true
|
||||
has_attached_file :home_hero, default_url: "/default_images/home.jpg"
|
||||
|
||||
# Map
|
||||
preference :open_street_map_enabled, :boolean, default: false
|
||||
@@ -66,8 +60,7 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration
|
||||
preference :menu_7_icon_name, :string, default: "ofn-i_013-help"
|
||||
|
||||
# Footer
|
||||
preference :footer_logo, :file
|
||||
has_attached_file :footer_logo, default_url: "/default_images/ofn-logo-footer.png"
|
||||
file_preference :footer_logo, default_url: "/default_images/ofn-logo-footer.png"
|
||||
|
||||
# Other
|
||||
preference :footer_facebook_url, :string, default: "https://www.facebook.com/OpenFoodNet"
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Preferences
|
||||
class FileConfiguration < Configuration
|
||||
def self.preference(name, type, *args)
|
||||
if type == :file
|
||||
# Active Storage blob id:
|
||||
super "#{name}_blob_id", :integer, *args
|
||||
|
||||
# Paperclip attachment attributes:
|
||||
super "#{name}_file_name", :string, *args
|
||||
super "#{name}_content_type", :string, *args
|
||||
super "#{name}_file_size", :integer, *args
|
||||
super "#{name}_updated_at", :string, *args
|
||||
|
||||
else
|
||||
super name, type, *args
|
||||
end
|
||||
end
|
||||
|
||||
def get_preference(key)
|
||||
if !has_preference?(key) && has_attachment?(key)
|
||||
# Call Paperclip's attachment method:
|
||||
public_send key
|
||||
elsif key.ends_with?("_blob")
|
||||
# Find referenced Active Storage blob:
|
||||
blob_id = super("#{key}_id")
|
||||
ActiveStorage::Blob.find_by(id: blob_id)
|
||||
else
|
||||
super key
|
||||
end
|
||||
end
|
||||
alias :[] :get_preference
|
||||
|
||||
def preference_type(name)
|
||||
if has_attachment? name
|
||||
:file
|
||||
else
|
||||
super name
|
||||
end
|
||||
end
|
||||
|
||||
# Spree's Configuration responds to preference methods via method_missing, but doesn't
|
||||
# override respond_to?, which consequently reports those methods as unavailable. Paperclip
|
||||
# errors if respond_to? isn't correct, so we override it here.
|
||||
def respond_to?(method, include_all = false)
|
||||
name = method.to_s.delete('=')
|
||||
reference_name = "#{name}_id"
|
||||
|
||||
super(self.class.preference_getter_method(name), include_all) ||
|
||||
super(reference_name, include_all) ||
|
||||
super(method, include_all)
|
||||
end
|
||||
|
||||
def has_attachment?(name)
|
||||
self.class.respond_to?(:attachment_definitions) &&
|
||||
self.class.attachment_definitions.key?(name.to_sym)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,5 +8,8 @@
|
||||
- text = t(key)
|
||||
.field
|
||||
= label_tag(key, text + ': ') + tag(:br) if type != :boolean
|
||||
= preference_field_tag(key, ContentConfig[key], :type => type)
|
||||
- if type == :file
|
||||
= file_field_tag(key, type: type)
|
||||
- else
|
||||
= preference_field_tag(key, ContentConfig[key], type: type)
|
||||
= label_tag(key, text) + tag(:br) if type == :boolean
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
:css
|
||||
#tagline:before { background-image: url("#{ContentConfig.home_hero.url}") }
|
||||
#tagline:before { background-image: url("#{ContentConfig.url_for(:home_hero)}") }
|
||||
|
||||
|
||||
- content_for :page_alert do
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
%meta{name: 'viewport', content: "width=device-width,initial-scale=1.0"}/
|
||||
%meta{property: "og:title", content: content_for?(:title) ? yield(:title) : t(:title)}
|
||||
%meta{property: "og:description", content: content_for?(:description) ? yield(:description) : t(:site_meta_description)}
|
||||
%meta{property: "og:image", content: content_for?(:image) ? yield(:image) : ContentConfig.logo.url}
|
||||
%meta{property: "og:image", content: content_for?(:image) ? yield(:image) : ContentConfig.url_for(:logo)}
|
||||
- if !Rails.env.production? || @noindex_meta_tag
|
||||
%meta{name: "robots", content: "noindex"}
|
||||
%title= content_for?(:title) ? "#{yield(:title)} - #{t(:title)}".html_safe : "#{t(:welcome_to)} #{t(:title)}"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
%table{:bgcolor => "#f2f2f2"}
|
||||
%tr
|
||||
%td
|
||||
%img{src: ContentConfig.footer_logo.url, width: "144", height: "50"}/
|
||||
%img{src: ContentConfig.url_for(:footer_logo), width: "144", height: "50"}/
|
||||
%td{:align => "right"}
|
||||
%h6.collapse
|
||||
= Spree::Config[:site_name]
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
.row.legal
|
||||
.small-12.medium-3.medium-offset-2.columns.text-left
|
||||
%a{href: main_app.root_path}
|
||||
%img{src: ContentConfig.footer_logo.url, width: "220"}
|
||||
%img{src: ContentConfig.url_for(:footer_logo), width: "220"}
|
||||
.small-12.medium-5.columns.text-left
|
||||
%p.text-small
|
||||
= t '.footer_legal_call'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
%ul.nav-logo
|
||||
%li.ofn-logo
|
||||
%a{href: main_app.root_path}
|
||||
%img{src: ContentConfig.logo.url}
|
||||
%img{src: ContentConfig.url_for(:logo)}
|
||||
%li.powered-by
|
||||
%img{src: '/favicon.ico'}
|
||||
%span
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
%section.left
|
||||
.ofn-logo
|
||||
%a{href: main_app.root_path}
|
||||
%img{src: ContentConfig.logo_mobile.url, srcset: ContentConfig.logo_mobile_svg.url, width: "75", height: "26"}
|
||||
%img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"}
|
||||
|
||||
%section.right{"ng-cloak" => true}
|
||||
%span.cart-span{"ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%ul.off-canvas-list
|
||||
%li.ofn-logo
|
||||
%a{href: main_app.root_path}
|
||||
%img{src: ContentConfig.logo_mobile.url, srcset: ContentConfig.logo_mobile_svg.url, width: "75", height: "26"}
|
||||
%img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"}
|
||||
- [*1..7].each do |menu_number|
|
||||
- menu_name = "menu_#{menu_number}"
|
||||
- if ContentConfig[menu_name].present?
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Allow use of Paperclip's has_attached_file on non-ActiveRecord classes
|
||||
# https://gist.github.com/basgys/5712426
|
||||
|
||||
module OpenFoodNetwork
|
||||
module Paperclippable
|
||||
def self.included(base)
|
||||
base.extend(ActiveModel::Naming)
|
||||
base.extend(ActiveModel::Callbacks)
|
||||
base.include(ActiveModel::Validations)
|
||||
base.include(Paperclip::Glue)
|
||||
|
||||
# Paperclip required callbacks
|
||||
base.define_model_callbacks(:save, only: [:after])
|
||||
base.define_model_callbacks(:commit, only: [:after])
|
||||
base.define_model_callbacks(:destroy, only: [:before, :after])
|
||||
|
||||
# Initialise an ID
|
||||
base.__send__(:attr_accessor, :id)
|
||||
base.instance_variable_set :@id, 1
|
||||
end
|
||||
|
||||
# ActiveModel requirements
|
||||
def to_model
|
||||
self
|
||||
end
|
||||
|
||||
def valid?() true end
|
||||
|
||||
def new_record?() true end
|
||||
|
||||
def destroyed?() true end
|
||||
|
||||
def save
|
||||
run_callbacks :save do
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def errors
|
||||
obj = Object.new
|
||||
def obj.[](_key) [] end
|
||||
|
||||
def obj.full_messages() [] end
|
||||
|
||||
def obj.any?() false end
|
||||
obj
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,10 +5,10 @@ require 'spec_helper'
|
||||
describe ContentConfiguration do
|
||||
describe "default logos and home_hero" do
|
||||
it "sets a default url with existing image" do
|
||||
expect(image_exist?(ContentConfig.logo.options[:default_url])).to be true
|
||||
expect(image_exist?(ContentConfig.logo_mobile_svg.options[:default_url])).to be true
|
||||
expect(image_exist?(ContentConfig.home_hero.options[:default_url])).to be true
|
||||
expect(image_exist?(ContentConfig.footer_logo.options[:default_url])).to be true
|
||||
expect(image_exist?(ContentConfig.url_for(:logo))).to be true
|
||||
expect(image_exist?(ContentConfig.url_for(:logo_mobile_svg))).to be true
|
||||
expect(image_exist?(ContentConfig.url_for(:home_hero))).to be true
|
||||
expect(image_exist?(ContentConfig.url_for(:footer_logo))).to be true
|
||||
end
|
||||
|
||||
def image_exist?(default_url)
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
module Spree
|
||||
module Preferences
|
||||
class TestConfiguration < FileConfiguration
|
||||
preference :name, :string
|
||||
|
||||
include OpenFoodNetwork::Paperclippable
|
||||
preference :logo, :file
|
||||
has_attached_file :logo
|
||||
end
|
||||
|
||||
describe FileConfiguration do
|
||||
let(:c) { TestConfiguration.new }
|
||||
|
||||
describe "getting preferences" do
|
||||
it "returns regular preferences" do
|
||||
c.name = 'foo'
|
||||
expect(c.get_preference(:name)).to eq('foo')
|
||||
end
|
||||
|
||||
it "returns file preferences" do
|
||||
expect(c.get_preference(:logo)).to be_a Paperclip::Attachment
|
||||
end
|
||||
|
||||
it "returns regular preferences via []" do
|
||||
c.name = 'foo'
|
||||
expect(c[:name]).to eq('foo')
|
||||
end
|
||||
|
||||
it "returns file preferences via []" do
|
||||
expect(c[:logo]).to be_a Paperclip::Attachment
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting preference types" do
|
||||
it "returns regular preference types" do
|
||||
expect(c.preference_type(:name)).to eq(:string)
|
||||
end
|
||||
|
||||
it "returns file preference types" do
|
||||
expect(c.preference_type(:logo)).to eq(:file)
|
||||
end
|
||||
end
|
||||
|
||||
describe "respond_to?" do
|
||||
it "responds to preference getters" do
|
||||
expect(c.respond_to?(:name)).to be true
|
||||
end
|
||||
|
||||
it "responds to preference setters" do
|
||||
expect(c.respond_to?(:name=)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user