Rails paperclip URL upload problem & How to make it DRY - ruby-on-rails

I am trying to create URL upload with paperclip.
I have followed this guide: http://trevorturk.com/2008/12/11/easy-upload-via-url-with-paperclip/
The problem is that nothing gets uploaded when I use the image_url fields. I know my code isnt very dry therefor it would be nice if someone had, some tips to rewrite the code.
I have 2 attached images and therefor 2 image URLs.
My konkurrancers table:
photo_file_name varchar(255)
photo_content_type varchar(255)
photo_file_size int(11)
photo_updated_at datetime
photo2_file_name varchar(255)
photo2_content_type varchar(255)
photo2_file_size int(11)
photo2_updated_at datetime
image_remote_url varchar(255)
image_remote_url_2 varchar(255)
My konkurrancer model:
class Konkurrancer < ActiveRecord::Base
has_attached_file :photo,
:url => "/public/images/billeder/photo/:id/:basename.:extension",
:path => ":rails_root/public/images/billeder/photo/:id/:basename.:extension"
has_attached_file :photo2,
:url => "/public/images/billeder/photo2/:id/:basename.:extension",
:path => ":rails_root/public/images/billeder/photo2/:id/:basename.:extension"
before_validation :download_remote_image, :if => :image_url_provided?
before_validation :download_remote_image_2, :if => :image_url_2_provided?
validates_presence_of :image_remote_url, :if => :image_url_provided?, :message => 'is invalid or inaccessible'
validates_presence_of :image_remote_url_2, :if => :image_url_2_provided?, :message => 'is invalid or inaccessible'
private
def image_url_provided?
!self.image_url.blank?
end
def image_url_2_provided?
!self.image_url_2.blank?
end
def download_remote_image
self.photo = do_download_remote_image
self.image_remote_url = image_url
end
def download_remote_image_2
self.photo2 = do_download_remote_image_2
self.image_remote_url_2 = image_url_2
end
def do_download_remote_image
io = open(URI.parse(image_url))
def io.original_filename; base_uri.path.split('/').last; end
io.original_filename.blank? ? nil : io
rescue # catch url errors with validations instead of exceptions (Errno::ENOENT, OpenURI::HTTPError, etc...)
end
def do_download_remote_image_2
io = open(URI.parse(image_url_2))
def io.original_filename; base_uri.path.split('/').last; end
io.original_filename.blank? ? nil : io
rescue # catch url errors with validations instead of exceptions (Errno::ENOENT, OpenURI::HTTPError, etc...)
end
end
My controller create action:
def create
#konkurrancer = Konkurrancer.new(params[:konkurrancer])
respond_to do |format|
if #konkurrancer.save
format.html { redirect_to(:admin_konkurrancers, :notice => 'Konkurrancer was successfully created.') }
format.xml { render :xml => :admin_konkurrancers, :status => :created, :location => #konkurrancer }
else
format.html { render :action => "new" }
format.xml { render :xml => #konkurrancer.errors, :status => :unprocessable_entity }
end
end
end
My form:
<%= simple_form_for [:admin, #konkurrancer], :html => { :multipart => true } do |f| %>
<%= f.label :upload_125x125 %>
<%= f.file_field :photo, :label => '125x125', :style => 'width:250;' %>
<%= f.input :image_url_2, :label => 'URL 125x125', :style => 'width:250;' %>
<%= f.label :upload_460x60 %>
<%= f.file_field :photo2, :label => '460x58', :style => 'width:250;' %>
<%= f.button :submit, :value => 'Create konkurrence' %>
<% end %>

In the latest version of paperclip (pull request has been merged but i'm not sure about the release) paperclip > 3.1.3 (maybe 3.2 is upcoming; maybe 3.1.4) this is become even easier.
self.photo = URI.parse("http://something.com/blah/image.png")
The above should take care of download/tempfile stuff/filename and filecontent type.
Enjoy! :)

To fix the problem of an repetitive model, you'll want to instead create a separate class for your photos that stores a foreign key to the konkurrence:
class Photo < ActiveRecord::Base
has_attached_file ...
belongs_to :konkurrence
...
end
class Konkurrence < ActiveRecord::Base
has_many :photos, :dependent => :destroy
accepts_nested_attributes_for :photos, :allow_destroy => true
...
end
Also, I think you're trying to download a remote image from a URL and then save this into Paperclip. Using open-uri (as I believe you already are), you can do this like so:
# open a tempfile using the last 14 chars of the filename
t = Tempfile.new(image_url.parameterize.slice(-14, 14))
t.write(open(image_url).read)
t.flush
t # return the File. You can then set the paperclip attribute to this File, eg, self.photo = t
This will save your URL as a temporary file which you can then pass on to Paperclip for regular processing.

Related

Return url from paperclip to json

I have a rails app that consists of a CMS system that I use in order to enter sights from a city to my database. I am using paperclip to upload images to amazon s3. All is working fine. Now I want my json files that an ios app will use to include the urls of the images uploaded in s3. I have seen some answers here but I cannot seem to make my code work. What I have is this..
place model
attr_accessible :assets_attributes, :asset
has_many :assets
accepts_nested_attributes_for :assets, :allow_destroy => true
def avatar_url
assets.map(&:asset_url)
end
asset model
class Asset < ActiveRecord::Base
attr_accessible :asset_content_type, :asset_file_name, :asset_file_size, :asset_updated_at, :place_id, :asset
belongs_to :place
has_attached_file :asset
validates_attachment :asset, :presence => true,
:content_type => { :content_type => ['image/jpeg', 'image/png'] },
:size => { :in => 0..1.megabytes }
def asset_url
asset.url(:original)
end
end
view code
<%= f.fields_for :assets do |asset_fields| %>
<% if asset_fields.object.new_record? %>
<p>
<%= asset_fields.file_field :asset %>
</p>
<% end %>
<% end %>
<br/>
<%= f.fields_for :assets do |asset_fields| %>
<% unless asset_fields.object.new_record? %>
<%= link_to image_tag(asset_fields.object.asset.url(:original), :class => "style_image"), (asset_fields.object.asset.url(:original)) %>
<%= asset_fields.check_box :_destroy %>
<% end %>
<% end %>
places controller
def index
#places = Place.all
render :json => #places.to_json(:methods => [:avatar_url])
end
Can anyone please help me?
In reference to the SO question you linked to (How can I get url for paperclip image in to_json), there are certain elements you'll need in order to get the image to render correctly
The problem you have is Paperclip's image method is actually an ActiveRecord object, and therefore you cannot just render it in a JSON request without doing some other stuff
The _url Method
The most important part of the process is to define the "_url" method in your asset model. This basically calls the .url function of Paperclip, allowing JSON to create the desired URL of the image on the fly (The url of the image is not an ActiveRecord object, and can therefore be sent via JSON)
As per the referenced SO question, you should put this action in your model:
#app/models/asset.rb
def asset_url
asset.url(:medium)
end
Now when you render the JSON request in your controller, you can use this type of setup:
#app/controllers/places_controller.rb
render :json => #places.to_json(:methods => [:asset_url])
Because your asset model is an associate of places, this might not work straight away. However, it's definitely in the right direction, because I can remember doing this exact thing myself
The important thing to note here, is that you're actually passing the naked "URL" of the image through JSON, not the image object itself
Update
Here's an example from our video conference demo app we made:
#app/controllers/profiles_controller.rb
def update
#profile = User.find(current_user.id)
#profile.profile.update(upload_params)
respond_to do |format|
format.html { render :nothing => true }
format.js { render :partial => 'profiles/update.js' }
format.json { render :json => #profile.profile.as_json(:only => [:id, :avatar], :methods => [:avatar_url])
}
end
end
#app/models/profile.rb
def avatar_url
avatar.url(:original)
end
So for you, I'd try this:
def index
#places = Place.all
render :json => #places.assets.as_json(:only => [:id, :asset], :methods => [:asset_url])
end
You could also try something like this:
#app/models/profile.rb
def avatar_url
self.asset.avatar.url(:original)
end

NoMethodError when using paperclip for nested resource

Hi I am pretty new to Rails, and I managed to get a few classes working together until I tried to use Paperclip.
Everything works fine until I try to upload a picture. I get this error:
undefined method `business_locations_path' for #<#:0x4eecc88>
These are the 2 models:
class Business < ActiveRecord::Base
attr_accessible :bizName, :contact, :email, :ownerName, :signupDate, :website
has_many :businessLocations
end
class BusinessLocation < ActiveRecord::Base
belongs_to :business
attr_accessible :address1, :address2, :contact, :description, :email, :latitude, :longitude, :postal, :price, :mainpic
has_attached_file :mainpic, :styles => {:large => "640x640>", :medium => "320x320>", :thumb => "100x100>"}
end
To create a new location, the path would be something like:
http://localhost:3000/businesses/8/businessLocations/new
The form:
<%
if params[:action] == "new"
urlpath = business_businessLocations_path(#business)
else
if params[:action] == "edit"
urlpath = business_businessLocation_path(#business,#businessLocation)
end
end
%>
<%= form_for #businessLocation, :url=>urlpath, :html =>{ :multipart => true } do |f| %>
...
<div class="field">
<%= f.label :mainpic %><br />
<%= f.file_field :mainpic %>
</div>
The BusinessLocation Controller:
def create
respond_to do |format|
#business = Business.find(params[:business_id])
#businessLocation = #business.businessLocations.build(params[:business_location])
if #businessLocation.save
format.html { redirect_to(business_businessLocation_path(#business,#businessLocation),
:notice => 'Post was successfully created.') }
format.json { render :json => #businessLocation,
:status => :created, :location => #business }
else
format.html { render :action => "new" }
format.json { render :json => #businessLocation.errors,
:status => :unprocessable_entity }
end
end
end
There is no such path business_locations_path because BusinessLocation is nested in Business.
EDIT: here are my rake routes:
business_businessLocations GET /businesses/:business_id/businessLocations(.:format) businessLocations#index
POST /businesses/:business_id/businessLocations(.:format) businessLocations#create
new_business_businessLocation GET /businesses/:business_id/businessLocations/new(.:format) businessLocations#new
edit_business_businessLocation GET /businesses/:business_id/businessLocations/:id/edit(.:format) businessLocations#edit
business_businessLocation GET /businesses/:business_id/businessLocations/:id(.:format) businessLocations#show
PUT /businesses/:business_id/businessLocations/:id(.:format) businessLocations#update
DELETE /businesses/:business_id/businessLocations/:id(.:format) businessLocations#destroy
businesses GET /businesses(.:format)
businesses#index
POST /businesses(.:format)
businesses#create
new_business GET /businesses/new(.:format)
businesses#new
edit_business GET /businesses/:id/edit(.:format)
businesses#edit
business GET /businesses/:id(.:format)
businesses#show
PUT /businesses/:id(.:format)
businesses#update
DELETE /businesses/:id(.:format)
businesses#destroy
In your controller, you have the create action set to redirect to business_businessLocation_path. This is autogenerated by whatever you used to genereate your nested scaffold. Fixing this will probably fix all your problems.

can't change text area style to field_with_error

I'm dealing with this legacy form for creating a new conversation. It has two fields : Name and description (the first comment of a conversation)
Here are the fields :
_fields.haml
.conversation_title= f.label :name, t('.name')
.clear
= f.text_field :name, :style => 'width: 230px'
= errors_for f.object, :name
if f.object.new_record?
= f.fields_for :comments, f.object.comments.build do |comment_fields|
.conversation_title= comment_fields.label :description, t('.description')
= comment_fields.text_area :body, :placeholder => t("comments.new.conversation"), :style => 'width: 545px'
= errors_for f.object, :comments
from the new view for conversations
= form_for [#current_project, #conversation], :html => { 'data-project-id' => #current_project.id, :name => 'form_new_conversation', :multipart => true } do |f| #, :onsubmit => 'return validate_form_new_conversation(form_new_conversation)'
= render 'fields', :f => f, :project => #current_project
= render 'watcher_fields', :f => f, :project => #current_project
The associated validations are :
conversation.rb
validates_presence_of :name, :message => :no_title, :unless => :simple?
validates_presence_of :comments, :message => :must_have_one, :unless => :is_importing
comment.rb
validates_presence_of :body, :unless => lambda { |c| c.task_comment? or c.uploads.to_a.any? or c.google_docs.any? }
For some reason, the proc associated to fields with error from base.rb
##field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
doesn't get called for the text area, so it doesn't change its style to make it turn red. It does for the :name field. Error messages get displayed properly
What am I missing?
Thanks!
The validation would be for the Comment model (rather than the Conversation model) on the body field. Check to make sure that validation exists. You can debug this to make sure that comment_fields.object has an error set on the body field, too.
I failed to notice one important part of this line in your code:
= f.fields_for :comments, f.object.comments.build do |comment_fields|
You call f.object.comments.build which means that you will always end up with a new instance of Comment (rather than the instance that was validated in the controller).
To avoid this you can build a comment in the controller. If you are using the normal restful actions you probably have two places where you want to build a comment. First in the new action and second, in the create action.
def new
#conversation = Conversation.new
#conversation.comments.build # Create a blank comment so that the fields will be shown on the form
end
def create
#conversation = Conversation.new(params[:conversation])
respond_to do |format|
if #conversation.save
format.html { redirect_to conversations_path }
else
format.html {
#conversation.comments.build if #conversation.comments.blank? # Create a blank comment only if none exists
render :action => "new"
}
end
end
end

Paperclip: "missing" image

I'm working on a website that allows people who run bed and breakfast businesses to post their accommodations.
I would like to require that they include a "profile image" of the accommodation when they post it, but I also want to give them the option to add more images later (this will be developed after).
I thought the best thing to do would be to use the Paperclip gem and have a Accommodation and a Photo in my application, the later belonging to the first as an association.
A new Photo record is created when they create an Accommodation. It has both id and accommodation_id attributes. However, the image is never uploaded and none of the Paperclip attributes get set (image_file_name: nil, image_content_type: nil, image_file_size: nil), so I get Paperclip's "missing" photo.
Any ideas on this one? It's been keeping me stuck for a few days now.
Accommodation
models/accommodation.rb
class Accommodation < ActiveRecord::Base
validates_presence_of :title, :description, :photo, :thing, :location
attr_accessible :title, :description, :thing, :borough, :location, :spaces, :price
has_one :photo
end
controllers/accommodation_controller.erb
class AccommodationsController < ApplicationController
before_filter :login_required, :only => {:new, :edit}
uses_tiny_mce ( :options => {
:theme => 'advanced',
:theme_advanced_toolbar_location => 'top',
:theme_advanced_toolbar_align => 'left',
:theme_advanced_buttons1 => 'bold,italic,underline,bullist,numlist,separator,undo,redo',
:theme_advanced_buttons2 => '',
:theme_advanced_buttons3 => ''
})
def index
#accommodations = Accommodation.all
end
def show
#accommodation = Accommodation.find(params[:id])
end
def new
#accommodation = Accommodation.new
end
def create
#accommodation = Accommodation.new(params[:accommodation])
#accommodation.photo = Photo.new(params[:photo])
#accommodation.user_id = current_user.id
if #accommodation.save
flash[:notice] = "Successfully created your accommodation."
render :action => 'show'
else
render :action => 'new'
end
end
def edit
#accommodation = Accommodation.find(params[:id])
end
def update
#accommodation = Accommodation.find(params[:id])
if #accommodation.update_attributes(params[:accommodation])
flash[:notice] = "Successfully updated accommodation."
render :action => 'show'
else
render :action => 'edit'
end
end
def destroy
#accommodation = Accommodation.find(params[:id])
#accommodation.destroy
flash[:notice] = "Successfully destroyed accommodation."
redirect_to :inkeep
end
end
views/accommodations/_form.html.erb
<%= form_for #accommodation, :html => {:multipart => true} do |f| %>
<%= f.error_messages %>
<p>
Title<br />
<%= f.text_field :title, :size => 60 %>
</p>
<p>
Description<br />
<%= f.text_area :description, :rows => 17, :cols => 75, :class => "mceEditor" %>
</p>
<p>
Photo<br />
<%= f.file_field :photo %>
</p>
[... snip ...]
<p><%= f.submit %></p>
<% end %>
Photo
The controller and views are still the same as when Rails generated them.
models/photo.erb
class Photo < ActiveRecord::Base
attr_accessible :image_file_name, :image_content_type, :image_file_size
belongs_to :accommodation
has_attached_file :image,
:styles => {
:thumb=> "100x100#",
:small => "150x150>" }
end
To create an upload with paperclip, you need to use the name you provided for the has_attached_file line, on the model you defined it on. In your case, this will result in this view code:
<%= form_for #accommodation, :html => { :multipart => true } do |f| %>
<%= f.fields_for :photo do |photo_fields| %>
<p>
Photo<br />
<%= photo_fields.file_field :image %>
</p>
<% end %>
<% end %>
In the controller:
class AccommodationsController < ApplicationController
# also protect create and update actions!
before_filter :login_required, :only => [ :new, :create, :edit, :update ]
def new
# always make objects through their owner
#accommodation = current_user.accommodations.build
#accommodation.build_photo
end
def create
#accommodation = current_user.accommodations.build(params[:accommodation])
if #accommodation.save
# always redirect after successful save/update
redirect_to #accommodation
else
render :new
end
end
end
Tell Rails to handle the nested form:
class Accommodation
has_one :photo
accepts_nested_attributes :photo
attr_accessible :photo_attributes, :title, :description, :etc
end
And make sure to set the accessible attributes right in your photo model:
class Photo
attr_accessible :image # individual attributes such as image_file_name shouldn't be accessible
has_attached_file :image, :styles => "etc"
end
Be sure to watch your log files to spot things that are protected by attr_accessible, but still are in your form.

Ruby on rails nested form model

I'm trying to use rails nested form_for helper, but I am getting the following error:
BlogPage(#49859550) expected, got Array(#31117360)
Here are my model objects:
class Blog < ActiveRecord::Base
# Table Configuration
set_table_name "blog"
# Model Configuration
belongs_to :item
has_many :blog_pages
accepts_nested_attributes_for :blog_pages, :allow_destroy => true
end
class BlogPage < ActiveRecord::Base
# Table Configuration
set_table_name "blog_page"
# Model Configuration
belongs_to :blog
end
Here is the form I generated (left out unnecessary HTML):
<% form_for :blog, :url => { :action => :create } do |blog_form| %>
<%= blog_form.text_field :title, :style => "width: 400px" %>
<% blog_form.fields_for :blog_pages do |page_fields| %>
<% #blog.blog_pages.each do |page| %>
<%= page_fields.text_area :content, :style => "width: 100%",
:cols => "10", :rows => "20" %>
<% end %>
<% end %>
<% end %>
Here are the parameters that are sent to the controller:
{"commit"=>"Save",
"blog"=>{"blog_pages"=>{"content"=>"This is the new blog entries contents."},
"title"=>"This is a new blog entry.",
"complete"=>"1"},
"authenticity_token"=>"T1Pr1g9e2AjEMyjtMjLi/ocrDLXzlw6meWoLW5LvFzc="}
Here is the BlogsController with the create action that gets executed:
class BlogsController < ApplicationController
def new
#blog = Blog.new # This is the line where the error gets thrown.
# Set up a page for the new blog so the view is displayed properly.
#blog.blog_pages[0] = BlogPage.new
#blog.blog_pages[0].page_number = 1
respond_to do |format|
format.html # Goes to the new.html.erb view.
format.xml { render :xml => #blog }
format.js { render :layout => false}
end
end
def create
#blog = Blog.new(params[:blog])
respond_to do |format|
if #blog.save
render :action => :show
else
flash[:notice] = "Error occurred while saving the blog entry."
render :action => :new
end
end
end
end
If anyone can help me with this I would greatly appreciate it. I'm still pretty new to ruby and the rails framework and couldn't solve the problem on my own by googling.
Thanks.
Have you seen this?
http://media.pragprog.com/titles/fr_arr/multiple_models_one_form.pdf
Change your form to this:
<% form_for :blog, :url => { :action => :create } do |blog_form| %>
<%= blog_form.text_field :title, :style => "width: 400px" %>
<% blog_form.fields_for :blog_pages do |page_fields| %>
<%= page_fields.text_area :content, :style => "width: 100%",
:cols => "10", :rows => "20" %>
<% end %>
<% end %>
If you use fields_for it iterates over blog_pages automaticaly. However I'm not sure if this caused errors.

Resources