Perform validations according to a param coming from the controller

151 views
Skip to first unread message

da...@neural.one

unread,
Nov 10, 2016, 1:07:34 PM11/10/16
to Shrine
Hi there,

In my application, a user must select a kind and add an attachment to create a new creative. According to the value of the kind, the uploader should perform different validations. I wonder if it's possible to use the params coming from the controller to perform these validations or there is a better way to do it.

I have included all the code relevant to my question.

Any help would be appreciated.

Thanks,
David

# config/initializers/shrine.rb
require 'shrine'
require 'shrine/storage/file_system'

Shrine.storages = {
  cache
: Shrine::Storage::FileSystem.new('public', prefix: 'uploads/cache'), # temporary
  store
: Shrine::Storage::FileSystem.new('public', prefix: 'uploads/store'), # permanent
}

# app/models/creative.rb
class Creative < ActiveRecord::Base
  extend ActiveHash::Associations::ActiveRecordExtensions

  belongs_to
:kind, class_name: 'CreativeKind', foreign_key: :creative_kind_id
  has_many
:attachments, inverse_of: :creative, dependent: :destroy
  accepts_nested_attributes_for
:attachments, allow_destroy: true, reject_if: :all_blank
end

# app/models/creative_kind.rb
class CreativeKind < ActiveHash::Base
  include ActiveHash::Associations
  include ActiveHash::Enum

  belongs_to :creatives

  self.data = [
    { id: 1, name: 'Image' },
    { id: 2, name: 'HTML' },
    { id: 3, name: 'Dynamic' }
  ]

  enum_accessor :name
end

# app/models/attachment.rb
class Attachment < ActiveRecord::Base
  include
FileUploader[:file]

  belongs_to
:creative, inverse_of: :attachments
end

# app/uploaders/file_uploader.rb
require 'image_processing/mini_magick'

class FileUploader < Shrine
  include
ImageProcessing::MiniMagick

  plugin
:activerecord
  plugin
:logging, logger: Rails.logger
  plugin
:validation_helpers
  plugin
:determine_mime_type
  plugin
:processing
  plugin
:versions
  plugin
:store_dimensions
  plugin
:cached_attachment_data
  plugin
:restore_cached_data
  plugin
:pretty_location
  plugin
:remove_attachment
  plugin
:remove_invalid
  plugin
:delete_promoted

  THUMB_VERSION_LONGEST_SIDE
= 150

 
Attacher.validate do
    validate_max_size
10.megabytes, message: 'is too large (max is 10 MB)'

    # kind = CreativeKind.find(kind_id).name 
# I don't know how to get the value of the kind id
    #
    # case kind
    # when 'Image'
    #   validate_mime_type_inclusion ['image/jpg', 'image/jpeg', 'image/png', 'image/gif']
    # when 'HTML'
    #   validate_mime_type_inclusion ['image/jpg', 'image/jpeg', 'image/png', 'image/gif', 'text/html']
    # when 'Dynamic'
    #   validate_mime_type_inclusion [] # Unknown at this moment
    # end
 
end

  process
(:store) do |io, context|
   
{ original: io, thumb: thumb_version(io) }
 
end

 
private

 
def thumb_version(io)
    resize_to_limit
!(io.download, THUMB_VERSION_LONGEST_SIDE, THUMB_VERSION_LONGEST_SIDE)
 
end
end

# app/controllers/creatives_controller.rb
class
CreativesController < ApplicationController
  def new
   
@creative = Creative.new
   
@creative.attachments.build
 
end

 
def create
   
@creative = Creative.new(creative_params)
    
if @creative.save
      
redirect_to creative_url(@creative), notice: 'Creative was successfully created.'
    else
      
@creative.attachments.build if @creative.attachments.empty?
      render :new
 
   end
 
end

 
def edit
    @creative = Creative.find(params[:id])
   
@creative.attachments.build
 
end

 
def update
    @creative = Creative.find(params[:id])
    
if @creative.update(creative_params)
      redirect_to creative_url(@creative), notice: 'Creative was successfully updated.'
   
else
      
@creative.attachments.build if @creative.attachments.all?(&:persisted?)
      render :edit

   
end
 
end

 
private

 
def creative_params
   
params.require(:creative).permit(:creative_kind_id, attachments_attributes: [:file, :id, :_destroy])
 
end
end

# app/views/creatives/_form.html.slim
= f.fields_for :attachments do |attachment|
 
- if attachment.object.persisted?
   
= image_tag attachment.object.file_url(:thumb)
   
| Remove?
   
= attachment.check_box :_destroy
 
- else
   
= attachment.label :file
   
= attachment.hidden_field :file, value: attachment.object.cached_file_data
 
  = attachment.file_field :file



Janko Marohnić

unread,
Nov 10, 2016, 10:29:15 PM11/10/16
to da...@neural.one, Shrine
The validations are performed inside a context of a Shrine::Attacher instance, which has access to the record which has the attachment. If CreativeKind is already created before creating Creative (and the `creative_kind_id` is always passed pointing to an existing CreativeKind), you can fetch it through the associations:

Attacher.validate do

  kind = record.creative.kind # record returns the #<Attachment> instance

  #...

end


Kind regards,
Janko


Advertencia legal: Este mensaje y, en su caso, los ficheros anexos son confidenciales, especialmente en lo que respecta a los datos personales, y se dirigen exclusivamente al destinatario referenciado. Si usted no lo es y lo ha recibido por error o tiene conocimiento del mismo por cualquier motivo, le rogamos que nos lo comunique por este medio y proceda a destruirlo o borrarlo, y que en todo caso se abstenga de utilizar, reproducir, alterar, archivar o comunicar a terceros el presente mensaje y ficheros anexos, todo ello bajo pena de incurrir en responsabilidades legales. El emisor no garantiza la integridad, rapidez o seguridad del presente correo, ni se responsabiliza de posibles perjuicios derivados de la captura, incorporaciones de virus o cualesquiera otras manipulaciones efectuadas por terceros.

Disclaimer: This message and any attached files transmitted with it, is confidential, especially as regards personal data. It is intended solely for the use of the individual or entity to whom it is addressed. If you are not the intended recipient and have received this information in error or have accessed it for any reason, please notify us of this fact by email reply and then destroy or delete the message, refraining from any reproduction, use, alteration, filing or communication to third parties of this message and attached files on penalty of incurring legal responsibilities. The sender does not guarantee the integrity, the accuracy, the swift delivery or the security of this email transmission, and assumes no responsibility for any possible damage incurred through data capture, virus incorporation or any manipulation carried out by third parties.

--
You received this message because you are subscribed to the Google Groups "Shrine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/470aafe3-3126-4b78-b1cb-e6ba20a9206a%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

David Montesdeoca

unread,
Nov 11, 2016, 3:53:17 AM11/11/16
to Shrine
Hi Janko,

Thanks for your reply.

I had already tried the solution you mentioned, but it doesn't work. Although the creative kind exists before loading the creative form, the creative is nil in the context of Attacher.validate block. I thought that at this point, I could access the creative stored in memory.

There is any misconfiguration you can see with models, controller and/or uploader? Any alternative approach?

Thanks in advance.

Kind regards,
David

To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine...@googlegroups.com.

Janko Marohnić

unread,
Nov 11, 2016, 4:11:37 AM11/11/16
to David Montesdeoca, Shrine
Oh, you're right. Well, since file validation is just part of the Attachment validation, and it needs to know about the CreativeKind type, perhaps it would make sense to add an additional attribute to Attachment.

class Attachment < ActiveRecord::Base

  attr_accessor :kind

end


The simplest way would probably be to pass the value in a form as a hidden field:

# app/views/creatives/_form.html.slim

= f.fields_for :attachments do |attachment|

  - if attachment.object.persisted?

    = image_tag attachment.object.file_url(:thumb)

    | Remove?

    = attachment.check_box :_destroy

  - else

    = attachment.label :file

    = attachment.hidden_filed :kind, value: creative_kind.name # <== new field

    = attachment.hidden_field :file, value: attachment.object.cached_file_data

    = attachment.file_field :file


And then in your uploader you could do

Attacher.validate do

  case record.kind

  when "Image" # ...

  when "HTML" # ...

  when "Dynamic" # ...

  end

end


I can't think of any other solutions at the moment. Whatever you decide, just note that Shrine runs file validation already when the file is assigned (before ActiveRecord calls `#valid?`), so this parameter needs to be present already on mass assignment, and before the attachment attribute.

Kind regards,
Janko

To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.

To post to this group, send email to ruby-...@googlegroups.com.

David Montesdeoca

unread,
Nov 11, 2016, 5:58:07 AM11/11/16
to Shrine
It doesn't work. I don't know why all fields of the attachment, including virtual attributes, are nil except file_data when I add a binding.pry inside Attacher.validate block and print the record in the console. Is that normal?

However, into the process(:store) block, through the context variable, I can access to all the fields of the attachment, including virtual attributes and its associations.

janko.m...@citrusbyte.com

unread,
Nov 11, 2016, 6:16:20 AM11/11/16
to Shrine
Oh, I think I know why. Remember that I said previously that the order of the attributes hash is important, because the Shrine validations are run immediately on attachment assignment. Well, even though the virtual attribute is sent before the attachment, this order also needs to be kept in strong parameters filtering (`creative_params`). I bet that when you permitted `kind`, you put it at the end, which altered the received attributes order. Does that resolve the issue?

In the `process(:store)` you will naturally have all the parameters, because at that point the Attachment and Creative records will already be saved.

I don't like that this was so difficult to debug. That wouldn't happen if Shrine ran validations only when `#valid?` is called. However, running file validations on attachment assignment was a conscious design decision, it made plugins like `remove_invalid` more natural to implement. But I will seriously rethink this decision now, because I don't like that using other model attributes on file validations requires that attributes are assigned in certain order. I will let you know once I reached some decision.

Kind regards,
Janko

David Montesdeoca

unread,
Nov 11, 2016, 8:37:59 AM11/11/16
to Shrine, janko.m...@citrusbyte.com
Yes, the issue is resolved adding the creative kind at the beginning of attachment's strong parameters.

Thank you so much for your incredible support, Janko. You have created a great gem.

Kind regards,
David

Janko Marohnić

unread,
Nov 13, 2016, 3:56:19 AM11/13/16
to David Montesdeoca, Shrine
Thank you, it feels so great to receive this feedback! :)

In the meanwhile I thought more about the validations, I think they should definitely be run separately from assigning the file. The only problem is that people might be relying on the current behaviour, and I don't know how to easily deprecate it and introduce the new behaviour (especially taking into account the `remove_invalid` plugin). It will likely be a temporary option, which will then be removed in Shrine 3.

Kind regards,
Janko

To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.

To post to this group, send email to ruby-...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages