Skip to main content

How to write a conditional in a create action?

Rails • Asked by Adrian DeGus

I recently worked through the 4 episodes here on direct uploads to S3 with shrine.

I have things setup to upload both images and documents and am now trying to write a conditional to create either an associated image or document record depending on whether the uploaded file is an image or a document (so that I only need one form for users). This is what I have so far, but I feel I might be working in the wrong direction.

Models:

class Project < ApplicationRecord
  has_many :project_images, as: :attachable
  has_many :project_documents, as: :attachable
end

class ProjectImage < ApplicationRecord
  include ImageUploader[:image]
  belongs_to :attachable, polymorphic: true
end

class ProjectDocument < ApplicationRecord
  include DocumentUploader[:image]
  belongs_to :attachable, polymorphic: true
end

Project Images Controller:

def create
    @project = Project.find(params[:project_id])
    @project_id = params[:project_id]
    @project_image = @project.project_images.build(project_image_params)
    @project_image.update(user_id: current_user.id, project_id: @project_id)

    respond_to do |format|
      if @project_image.save
        format.html { redirect_to @project }
        format.json { render :show, status: :created, location: @project }
      else
        format.html { render :new }
        format.json { render json: @project_image.errors, status: :unprocessable_entity }
      end
    end
  end

Project Documents Controller:

def create
    @project = Project.find(params[:project_id])
    @project_id = params[:project_id]
    @project_document = @project.project_documents.build(project_document_params)
    @project_document.update(user_id: current_user.id, project_id: @project_id)

    respond_to do |format|
      if @project_document.save
        format.html { redirect_to @project }
        format.json { render :show, status: :created, location: @project }
      else
        format.html { render :new }
        format.json { render json: @project_document.errors, status: :unprocessable_entity }
      end
    end
  end

Project View:

<%= form_for ([@project, @project_image]) do |f| %>
    <p>
      <%= f.file_field :image, multiple: true, name: 'project_image[image]' %>
    </p>
<% end %>

I started by trying to write a conditional in project_images#create but couldn't define how an uploaded document would differ from an image. Maybe I can grab the file extension on the way in somehow?

Then I moved onto another approach, but I'm stuck here as well:

Attachment Model:

class Attachment < ApplicationRecord
  belongs_to :attachable, polymorphic: true
  belongs_to :project
end

Attachments Controller:

class ProjectAttachment < ApplicationRecord

  def create
    @project = Project.find(params[:project_id])
    @project_id = params[:project_id]
    @attachment = @project.attachments.build(attachment_params)   # @object inserts here somehow?
    @attachment.update(user_id: current_user.id, project_id: @project_id)

    respond_to do |format|
      if @attachment.save
        format.html { redirect_to @attachment.project }
        format.json { render :show, status: :created, location: @attachment }
      else
        format.html { render :new }
        format.json { render json: @attachment.errors, status: :unprocessable_entity }
      end
    end
  end

  private

  def create_object
    id = params[:project_document_id] || params[:project_image_id]

    model = ProjectDocument if params[:project_document_id]
    model = ProjectImage if params[:project_image_id]

    @object = model.find(id)
  end

end

Project View:

<%= form_for ([@project, @attachment]) do |f| %>
    <p>
      <%= f.file_field :image, multiple: true, name: 'attachment[image]' %>
    </p>
<% end %>

Am I on the right track with either of these attempts?


I can think of a couple ways to handle this but it mainly has to do with what the end product is trying to accomplish.

First I would use the shrine plugin, :determine_mime_type, to determine the file type.

Then you could either continue with your current approach of separating the files in to two separate models or you could set a field called "file_type" on your Attachment model. An attachment is either an "image" or a "document". Then you could create a scope on your Project model for attachments that queries file_type on the association, one for images and one for documents .

This allows you to then simply call project.images or project.documents.

Just my 2 cents and what came to mind when I read your question


Yeah I'm with David on this one. Using the mime type will be the best way to determine how to split these. They probably makes sense to be stored in a generic ProjectAttachment model and then filter then by scoping on the type.

If you combine the two models into a single attachments model, you'll want to update your uploader to be generic. I presume you'll be cropping images and obviously you can't crop documents. Your uploader will need to check the file type before doing the cropping so that only happens for images.


Okay thanks for the direction guys, I'll start looking into this to see how to get it done.


Decided to go with Refile instead of Shrine. I found it easier to work with and was able to implement everything I needed in two days, while I was still wrestling with Shrine after two weeks. I probably just lack the experience that Shrine requires.


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 27,623+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.