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 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 commented Oct 7, 2015

Flawless. Thank you.

@pvijayror

This comment has been minimized.

Copy link

pvijayror 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 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 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 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
You can’t perform that action at this time.