Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Polymorphic attachments with CarrierWave and nested_attributes
class Attachment < ActiveRecord::Base
mount_uploader :attachment, AttachmentUploader
# Associations
belongs_to :attached_item, polymorphic: true
# Validations
validates_presence_of :attachment
validates_integrity_of :attachment
# Callbacks
before_save :update_attachment_attributes
# Delegate
delegate :url, :size, :path, to: :attachment
# Virtual attributes
alias_attribute :filename, :original_filename
private
def update_attachment_attributes
if attachment.present? && attachment_changed?
self.original_filename = attachment.file.original_filename
self.content_type = attachment.file.content_type
end
end
end
# encoding: utf-8
require 'carrierwave/processing/mime_types'
class AttachmentUploader < CarrierWave::Uploader::Base
include CarrierWave::MimeTypes
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
process :set_content_type
end
class AttachmentsController < ApplicationController
before_action :find_attachable
def create
@attachable.attachments.create attachment_params
redirect_to @attachable
end
private
def attachment_params
params.require(:attachment).permit(:attachment)
end
# could be improve and include into concerns
# http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
def find_attachable
params.each do |name, value|
return @attachable = $1.classify.constantize.find(value) if name =~ /(.+)_id$/
end
end
end
# Required gem ryanb/nested_form
# https://github.com/ryanb/nested_form
<%= nested_form_for @post, html: { multipart: true } do |f| %>
<p><%= f.link_to_add "Add an attachment", :attachments %></p>
<%= f.fields_for :attachments do |attachment_form| %>
<%= attachment_form.file_field :attachment %>
<%= attachment_form.hidden_field :attachment_cache %>
<%= attachment_form.link_to_remove "Remove this attachment" %>
<% end %>
<% end %>
class Post < ActiveRecord::Base
has_many :attachments, as: :attached_item, dependent: :destroy
# http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for
accepts_nested_attributes_for :attachments, allow_destroy: true, reject_if: proc { |attributes| attributes[:attachment].nil? }
end
class PostsController < ApplicationController
def new
@post = Post.new
end
def create
@post = Post.create post_params
redirect_to @post
end
private
def post_params
params.require(:post).permit(attachments_attributes: [:id, :attachment, :attachment_cache, :_destroy])
end
end
App::Application.routes.draw do
# Concerns must be declared before
# http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Concerns.html
concern :attachable do
resources :attachments, only: :create
end
resources :posts, concerns: [:attachable]
end
create_table "attachments", force: true do |t|
t.integer "attached_item_id"
t.string "attached_item_type"
t.string "attachment", null: false
t.string "original_filename"
t.string "content_type"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "attachments", ["attached_item_type", "attached_item_id"]
@rajrathore

This comment has been minimized.

Copy link

@rajrathore rajrathore commented Dec 5, 2014

Form redisplay in case of validation errors doesn't work. I have has_one association.
What should i need to do for that.

Thanks

@jrasanen

This comment has been minimized.

Copy link

@jrasanen jrasanen commented Oct 7, 2015

Flawless. Thank you.

@pvijayfullstack

This comment has been minimized.

Copy link

@pvijayfullstack pvijayfullstack commented Dec 23, 2015

I am getting error if try to destroy image "undefined method `name' for nil:NilClass"

@forgotpw1

This comment has been minimized.

Copy link

@forgotpw1 forgotpw1 commented May 13, 2016

Thank you for sharing this.

In my case I did not use nested form. I used the standard form_for and fields_for helpers.

<%= form_for @organization, url: {action: "create"} do |f| %> <%= f.text_field :name %> <%= f.fields_for :image do |image_form| %> <%= image_form.file_field :image %> <%= image_form.hidden_field :image_cache %> <% end %> <%= f.submit "Create" %> <% end %>

For this to send the request with the expected 'image_attributes' parameters and not just a nested 'image' parameter, you need the parent object instance to initialize the nested object.

This means that in the controller or view, you will need to do something like this @organization.build_image

God Bless.

@acrolink

This comment has been minimized.

Copy link

@acrolink acrolink commented Aug 28, 2017

Does this code apply to Rails 5. I have tried this code but nothing gets stored in the database.

@potatowave

This comment has been minimized.

Copy link

@potatowave potatowave commented Sep 17, 2017

Works for me. My only challenge is I added a "Description" and "Order" to my attachments table. I'm trying to figure out how to 'edit' those fields with an edit view/update model without actually uploading a new image. (works if you update the image too)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment