class Revelry::Content::PseudoContentVersion include ActiveModel::Model attr_accessor :id, :model, :rollback_version_id def whodunnit model.last_whodunnit end def event 'update' end def item_type model.class.name end def item_id model.id end def reify model end def created_at model.updated_at + 1.second end def as_json(opts = {}) Jbuilder.new do |json| json.(self, :id, :item_type, :item_id, :event, :whodunnit, :rollback_version_id, :created_at, :reify, :errors) end.attributes! end end class Revelry::Content::Content < ActiveRecord::Base include ERB::Util has_paper_trail class_name: 'Revelry::Content::ContentVersion' mount_uploader :src, SrcUploader has_many :versions, class_name: 'Revelry::Content::ContentVersion', :as => :item validates :key, presence: true, uniqueness: true scope :prefix, ->(prefix) { where("key LIKE '#{prefix}%'") } scope :excluding_prefix, ->(prefix) { where.not("key LIKE '#{prefix}%'") } def html_content return if content.nil? Revelry::Content.config.sanitizer.call(markdown_engine.render(content)) end def as_json(opts = {}) Jbuilder.new do |json| json.(self, :id, :type, :key, :content, :updated_at, :created_at) json.src src.url json.html_content html_content end.attributes!.as_json(opts) end def as_serialized_json(opts = {}) Jbuilder.new do |json| json.(self, :id, :type, :key, :content, :updated_at, :created_at) if src.present? json.src_data do json.src_data_uri src_data_uri json.src_filename File.basename(src.path) end end end.attributes!.as_json(opts) end def src_data_uri # Take the file stored in src and turn it into a data URI, for serializing "data:#{ src.file.content_type };base64,#{ Base64.encode64(src.file.read()).chop }" if src.present? end # Stash a copy of the ActiveRecord implementation of model[index], because # we need to override the behavior (see below) alias_method :old_brackets, :[]= # Override the default model[index] behavior, because papertrail tries to do # content['src_data'] = value, which you might assume would just call # content.src_data=, but it does not. Since 'src_data' is not a real AR # attribute, it barfs instead. Catch this case, and handle appropriately, # otherwise, let the AR version do its work. def []=(key, value) if key.to_sym == :src_data self.src_data=(value) else old_brackets(key, value) end end # Convert the data URI to a tempfile, then upload it to carrierwave def src_data=(value) self.src = data_uri_to_file(value['src_filename'], value['src_data_uri']) end def data_uri_to_file(filename, s) begin # The gsub strips interwhitespace, which I guess is a thing that the YAML # serializer introduces which is valid YAML, but not valid data uri uri = URI::Data.new(s.gsub(/\s+/, "")) open(uri) do |io| tempfile = Tempfile.new(filename) tempfile.binmode tempfile << io.read tempfile.rewind file_ext = io.content_type.split('/')[-1] return ActionDispatch::Http::UploadedFile.new( filename: filename, type: file_ext, tempfile: tempfile ) end rescue URI::InvalidURIError # This is not a data URI-- return nothing! end end def src_data_uri=(v) self.remote_src_url = v end def attributes_before_change attrs = attributes.tap do |prev| enums = self.respond_to?(:defined_enums) ? self.defined_enums : {} changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each do |attr, before| before = enums[attr][before] if enums[attr] prev[attr] = before end end if src.present? attrs.merge src_data: { src_data_uri: src_data_uri, src_filename: File.basename(src.path) } end attrs end def self.from_import_hash(content_object) content_model = Revelry::Content::Content.find_or_initialize_by(key: content_object['key']) # Mark as an import event for papertrail to distinguish from a normal create/update if content_model.new_record? content_model.paper_trail_event = 'import create' else content_model.paper_trail_event = 'import update' end # whitelist a set of fields to change using slice and then update/save the # model with them content_model.update(content_object.slice('type', 'src', 'src_data', 'content', 'updated_at', 'created_at')) content_model end def markdown_engine @markdown ||= Redcarpet::Markdown.new(Revelry::Content.config.markdown_renderer, underline: true) end def self.as_lookup_table(content_scope = nil) content_scope = all unless content_scope.present? Hash[content_scope.map { |c| [c.key, c.as_json] }] end def self.as_serialized_table Hash[all.map { |c| [c.key, c.as_serialized_json] }] end if self.respond_to?(:rails_admin) rails_admin do object_label_method { :key } include_fields :key, :content, :src field :src do label 'Image' end end end end