Paperclip image.url in Backbone.js Views - ruby-on-rails

I am using paperclip to save images. Everything works fine and I am able to access the item's url with #item.image.url.
class Item
has_attached_file :image, :styles => {
:original => ['1920x1680>', :jpg],
:small => ['100x100>', :jpg],
:medium => ['250x250>', :jpg],
:large => ['500x500>', :jpg]
}
end
This is console:
> Item.last.image.url(:small)
=> "/system/images/items/1/small/chanel.jpg?1334005208"
This is straightforward and easy if I am templating HAML or ERB from the server and serving up the page to the user like this. items/show.html.haml:
.item
.item-image
= image_tag #item.image.url(:small)
However, with backbone.js, I am unable to construct the URL because I do not have the paperclip helpers in context. Essentially, I am sending the following attributes of the image to the page in json form.
#<Item:0x007fc97559b960> {
:id => 1,
:image_content_type => "image/jpeg",
:image_file_name => "chanel.jpg",
:image_file_size => 28880,
:image_updated_at => 2012-04-09 21:00:08 UTC
}
What is a ninja way to get the image.url included as an attribute on the item. How do I account for the style URLS? It would be nice to have an attribute like "image_small_url", "image_normal_url", etc predetermined and accessible. Thoughts?

I'm using Jbuilder to build the JSON views for a project I'm working on, so my index view, for example looks like this:
json.array!(#things) do |json, thing|
json.id thing.id
json.name thing.name
json.description thing.description
json.image_url thing.image.url
json.thumb_url thing.image.url(:thumb)
end
That way in my Backbone template, I can just say thing.get('image_url') and thing.get('thumb_url').
In brief, you'll want to use something like Jbuilder, or manually override as_json in your model. Personally, I like taking care of this at the view level, which is what Jbuilder allows you to do easily.

While generating JSON in controllers is doable, I greatly prefer wrapping up this kind of functionality in either decorators (e.g., Draper), or into frameworks designed for RESTful resources (e.g., Roar).
This keeps the mapping between models and their external representations highly localized, allows direct testing of representations outside of the web app framework, and so on.
For example, clem's answer would be isolated within a single class, roughly:
class ThingDecorator < Draper::Decorator
delegate_all
def image_url; object.image.url; end
def thumb_url; object.image.url(:thumb); end
end
Then in the controller, for example:
#things = Thing.some_scope.decorate # Or
#things = ThingDecorator.decorate_collection(Thing.all)
(Or whatever you need, and what works depends on the Rails version, see the Draper docs.)
Then expose the collection as JSON using normal means. IMO this is almost always cleaner.

Another simple example using jbuilder:
# index.json.jbuilder
json.array!(#shared_snap_casts) do |shared_snap_cast|
json.extract! shared_snap_cast, :id, :snap
end
Here the :snap is the paperclip attachment - the output from this example is:
[{"id":1,"snap":"/system/shared_snap_casts/snaps/000/000/001/original/some_icon.png?1388093936"}]
Hope this helps!

Related

Rails Paperclip dynamic styles - avoid scaling SVG

I want to provide my styles parameter with some lambda that checks if the file is an SVG file, scale it properly or not at all, I would like to communicate with the model as I do with all my other images, as when i render them (#image.image(:thumb). Is this possible?
Right now I attach my file as:
has_mongoid_attached_file :image,
:path => 'app/assets/images/library/:id/:style.:extension',
:styles => {:thumb => "216x162#", :medium => "400x300#", :scenario => "700x525#"},
:url => '/assets/library/:id/:style.:extension'
I've read about dynamic styles and did some trial and error with not success. My thought was that someone perhaps already have done this.
correct me if I misunderstood your question.
Please check https://github.com/thoughtbot/paperclip#dynamic-styles which says you can provide an lambda with attachment as argument of this lambda.
Inside the block you can use attachment.instance.#{any instance method of model}.

how to put a link_to internal resource in a model with rails 2.3.x

I have a requirement to include internal links in a DataTables report. Therefore I must return the report data from Model#as_json, e.g.:
class Error < ActiveRecord::Base
include ActionView::Helpers::UrlHelper # provides link_to
include ActionController::UrlWriter # provides *_path
def as_json(options={})
{
:date => self.created_at,
:level => self.level,
:ip => self.ip,
:title => truncate(self.title, :length => 100),
:show => link_to('Show', error_path(self)),
:hide => self.handled ? "" : "#{link_to 'Hide', handle_error_path(self)}"
}
end
...
What an effort figuring out what I needed to include. But now I get error: "can't convert String into Hash"
This is because 'link_to' uses 'url_for' which is a method that both UrlHelper and UrlWriter both have which actually behave differently.
So I'm at my wits end. If someone can help me figure out how to do this, or show me how to fulfill the requirements without breaking MVC I will be very grateful either way.
Try creating the link manually:
:hide => self.handled ? "" : "<a href='/handle_error?params=something'>Hide</a>"
I will follow up my own question with what turned out to be the best solution: use the json_builder gem and keep your json in the view where it belongs :)

Using Rails googlecharts gem on HTTPS/SSL site

I am using the googlecharts gem in my rails app for some simple charting. It works beautifully, except my app is required to be SSL encrypted at all times. In order to pull the google charts, the charts gem of course makes an http request to google which leads to a browser warning about some insecure content on the page for most users. Has anyone else faced this issue and devised a solution to avoid the warning? I am afraid I will need to figure out a way to make the http call, store the image google returns locally, and then display that in the app but figured someone else has already found a nice way to handle this.
The API Google Charts API endpoint is stored in the class variable ##url inside the Gchart class. So initially I thought of monkeypatching the class variable to set the url to https
# Put this in an initializer
Gchart.send :class_variable_set, :##url, "https://chart.apis.google.com/chart?"
alas Google Charts does not work via https. So we can't use that method. As the Gchart class methods just return a URL we can wrap the calls up in a proxy controller method that does the API call server side and proxies it to the client via the ActionController send_data method using your protocol of choice. That way you don't have to reinvent the Gchart library wheel.
class ChartsController < ApplicationController
require 'net/http'
require 'gchart'
def show
options = params.except :controller, :action
options[:data].map! { |x| x.to_i } if options[:data]
begin
chart = URI.parse(Gchart.send options.delete(:type), options)
send_data Net::HTTP.get(chart), :content_type => 'image/png', :disposition => 'inline'
rescue
raise ActiveRecord::RecordNotFound
end
end
end
The helper you can use in your views:
module ApplicationHelper
def chart_tag(options ={})
image_tag chart_path(options)
end
end
and the route
map.resource :chart, :only => :show
Usage:
<%= chart_tag :type => "line", :size => '200x300', :title => "example title", :bg => 'efefef', :legend => ['first data set label', 'second data set label'], :data => [10, 30, 120, 45, 72] %>
Code is untested but should give you a good start.
Google charts supports now ssl :
use
https://chart.googleapis.com/chart
instead of :
http://chart.apis.google.com/chart
I'm using the GchartRB gem, and a modified version of the first solution worked for me as well. You'll have to use the to_escaped_url method for URI.parse to handle it correctly.
I don't know of an existing plugin that will do this, but you can do it on your own. Simply write a new controller method that will get the chart via HTTP and then return it immediately (no need to save it to a file)
In controller:
require 'net/http'
def googlechart
send_data Net::HTTP.get("http://chart.apis.google.com/chart?#{params[:api]}"),
:content_type => 'image/png',
:disposition => 'inline'
end
In view:
<%= image_tag googlechart_path(:api=>'cht=p&chd=s:Uf9a&chs=200x100&chl=January') %>
Just set up your route and you're all set.

Understanding Routes in Rails

I actually have two questions. I've read the Rails guide and a couple of other articles, but I haven't been able to translate what I read into working routes. I have an application that allows the uploading of images from several different contexts. I'd like the URI to express the proper context so that the following URIs access the same page:
/images/upload
/photos/upload
In this example, I've overridden the new_image_path to use upload for descriptive purposes. I have the override working, but using :as to map images to photos only appears to work one way (with :as => 'photos' in place, the /images routes don't work). Is there a way to make multiple routes point to the same place?
I also have a couple of different ways to upload images/photos/etc. The standard method with a single image per form or a batch method where the user uploads a zip file and that archive is extracted and each of its images is saved.
It seems like the most semantic way to do this is by adding a handler component to the URI (e.g. /images/upload/batch), but I'm not sure how to handle this. The default route path seems pretty general for something that would only be required for images, but I also don't want to be so specific with a named route for the entire bit. What's the best way to do something like this?
Thanks.
Update: Based on jonnii's answer to my first question, I've added the following to my routes.rb file:
map.resources :images, :path_names => { :new => 'upload' }
map.resources :photos, :controller => 'Images', :path_names => { :new => 'upload' }
That seems to do the trick for allowing me to use /images/ and /photos/ interchangeably.
I'm assuming you're doing your photos routes using resources:
map.resources :photos
If that's the case you should be able to define a second resource pointing to the same controller as the photos resource:
map.resources :uploads, :controller => 'PhotosController'
I haven't tested this, but I don't see why something like this wouldn't work..
Question 2:
There are a few different ways you can do batch uploads, I think the best way is to have a separate resource as you're most likely going to have a different UI for it. For example you might do:
map.resources :batch_uploads
This would probably be enough if you were going to take batch uploads as a zip.
An option that's slightly more complicated but takes advantage of the rails niceties (and lets be honest, who doesn't want to take advantage of that??) is something with nested child forms and accepts_nested_attributes_for. This would be useful if you wanted to allow a user to attach more than one image to a form at time.
For example, if your model was something like:
class User < AR:B
has_many :photos
end
You could have a route like:
map.resources :users do |u|
u.resources :photos, :collection => {:get => :new_batch, :post => create_batch}
end
In your new_batch view you would have a form_for #user with a user_form.fields_for :photos. You can add a new form using ajax, whatever and post it all at once.
If you wanted to keep the same semantics as you have now and didn't want to add any more routes you could extend your model to do something different based on the filename of what is being uploaded.
For example if you were using paperclip for attachments you could stop processing the attachment if the filename ends with .zip (this code is not guaranteed to work, I'm doing it from memory):
def is_zip?
attachment.filename.ends_with?('.zip')
end
before_attachment_process do |attachment|
false if is_zip?
end
before_filter :process_bulk_attachment, :if => :is_zip?
def process_bulk_attachment
... extract the zip and save each image in it ...
false
end
The beauty of this is that it's part of the model. You should always aim for fat models and skinny controllers!
I hope this gives you a few ideas and/or points you in the right direction.
I've gotten a little closer to what I'm going for:
map.resources :images, :path_names => { :new => 'upload' }
map.resources :images, :new => { :batch => :get }
The former allows me to use /images/upload instead of /images/new, as shown in jonnii's answer to my first question. The latter allows me to specify a second route to "new" functionality via /images/new/batch which calls ImagesController#batch. I was hoping to be able to use /images/upload/batch, but this may have to do.
Clearly, I still have a long way to go before I really understand routing in Rails. jonnii, if I'm just rehashing part of what you've already said, I apologize. I may have to plead ignorance with respect to much of your answer to question 2.

Generating a unique file path with Polymorphic Paperclip

I'm running into an issue with different users uploading files with the same name being overwritten with the Polymorphic Paperclip plugin. What I'd like to do is inject the current user's ID into the URL/path. Is this possible? Would I be better off generating a random name?
Here are my current :url and :path parameter values in asset.rb:
:url => "/assets/:id/:style/:basename.:extension",
:path => ":rails_root/public/assets/:id/:style/:basename.:extension"
What I'd like to be able to do is this:
:url => "/assets/#{current_users_id}/:id/:style/:basename.:extension",
:path => ":rails_root/public/assets/#{current_users_id}/:id/:style/:basename.:extension"
Use Paperclip interpolations:
file config/initializers/paperclip.rb:
module Paperclip
module Interpolations
def user_id(attachment, style)
current_user.id
end
end
end
has_ attached_file option:
:url => "/assets/:user_id/:id/:style/:filename"
(The syntax changed from Paperclip 2.x to 2.3; :path is not necessary; Use the latest version and have a look at the source, it's quite well documented.)
Every time I see the word random and it relates to strings, I think GUID. Perhaps they could work for you.
for me it didnt work bypaperclip.rb but it works like this:
In the model class:
class Micropost < ApplicationRecord
Paperclip.interpolates :user_id do |attachment, style|
attachment.instance.user_id
end
has_attached_file :pic1,
:url => "/Microposts/:user_id/:style/:basename.:extension"
In case if you want to do it by Paperclip interpolations you should find a path like this:
first find gem file path. type this in your terminal:
$ gem env
Then, it will show you a path in "- GEM PATHS:"
in my case this was the path :
:/usr/local/lib/ruby/gems/2.4.0/gems/paperclip-5.0.0/lib/paperclip
In this direction you can find "paperclip.rb" .

Resources