Ruby on Rails and Paperclip - ruby-on-rails

I have a polymorphic Attachment model to save images uploaded by users. Users can set any of their own as a profile pic.
To this, I created an "avatar_id" column in the User database to basically save the attachment id to create the reference. I also added an avatar action to the User model:
def avatar
self.attachments.first(:conditions => ['id = ?', self.avatar_id])
end
The problem is that if I try to make this work
<%= #user.avatar.url %>
It doesn't work because the url method doesn't exist. I need to specify that is an paperclip object but I don't get where and how I should do it. I'm probably missing something obviously here.

Your Attachment model should have a set of attachment fields: maybe attachment_file_name, attachment_content_size, etc.
So when you reference #user.avatar, you're really just referencing the entire Attachment record rather than the paperclip-specific columns.
Try this, substituting "attachment" for whatever you called your paperclip columns:
<%= #user.avatar.attachment.url %>

Related

ActiveStorage: checking if .attached? for a collection that has attachments preloaded (while avoiding n+1 queries of attached? method)

In my Rails 5 application, building a JSON API & using Activestorage for my attachments:
To avoid accessing an attachment that doesn't exist, I am using this if condition as shown in this answer but unfortunately this if condition makes a SQL query to check if the attachment is available or not, causing n+1 queries although I have already included (pre-loaded) the attachments for the records I have
How to check if the attachment exist on a record without hitting the DB, if I have preloaded the attachments already?
I tried to use .nil? but that will give a false positive that there is attachment even if there isn't (as shown below)
ActionView::Template::Error (to_model delegated to attachment, but attachment is nil):
if !product.logo.nil?
json.logo_img url_for(product.logo)
.attached? check works but causes the n+1 queries:
if product.logo.attached?
json.logo_img url_for(product.logo)
So while the below code looks very ugly, it works, preloading the attachments for all the records at once, but when checking if an attachment exists => this leads to n+1 queries
json.array!(#some_records.includes(integrated_product: [company: [ logo_attachment: :blob ]])) do |one_record|
product ||= one_record.integrated_product
if !product.logo.attached?
json.logo_img url_for(product.logo)
elsif !product.company.logo.attached?
json.logo_img url_for(product.company.logo)
end
....
Any thoughts on how to handle this in a proper way (preloading attachments & avoiding n+1) are appreciated.
Update:
I want to have the whole collection that I passed, including items with & without attachment, searching for a condition that will check if that already loaded item has an attachment or not to display an image if it has attachment
Rails generates a collection method for has_one_attached relationships. You can just do something like this:
Product.all.with_attached_logo.map{|product| product.logo.attached? }
Which will make a single eager loaded query.
Lets say you want to show the logo's of each product inside views/products/index.htm.erb
So let's set up the #products variable in the index action of your controllers/products_controller.rb
def index
#products = Product.all.with_attached_logo
end
This will eager load the logo attachment into each product if they have one. Now we can create the products index page without the N+1 problem like so:
<% #product.each do |product| %>
<% if product.logo.attached? %>
<%= image_tag product.logo %>
<% else %>
<%= image_tag "product_logo_placeholder.jpg" %>
<% end %>
... the rest of the product's information ...
<% end %>
The problem turned out to be that the complex includes I had was not actually preloading the attachments (it was preloading for the nested record but not the main one), therefore the .attached? method was making a SQL query to check.
However, when the correct preloading is done, the .attached? works correctly, checking the in-memory object without firing a seperate SQL query to check again
I am leaving the question for the reference of the community if someone face a similar issue.
If I understand you correctly, you might want do do something like this.
In your model:
class Product < ApplicationRecord
has_one_attached :logo
scope :with_eager_loaded_logo, -> { eager_load(logo_attachment: :blob) }
end
Then in your controller, for example:
def index
#products = current_user.products.with_eager_loaded_logo
end
Not a JSON, but still some view example(HTML):
<%= image_tag (product.logo) if product.logo.attached? %>
See more in this post.

Attachments to non-persisting record in Rails

So I have a items/new.html.erb page that has a form for a new Items record.
Now the page has an image uploads section also, beside the new record form, where the user can upload multiple images. This UI design suggests that the user should be able to upload images, or attach images to the to-be-created record.
My current setup is when the user uploads an image in the page(this is handled via AJAX/rails UJS), the id of the uploaded image/images will be added in a hidden field in the new Item form. Then only when the main form is submitted will the images be attached to the newly created Item resource. I feel that this is an unusual way of handling this issue and that there is a more easy, clear, Rails way for this.
I call the images like this:
item.images
The page also allows that the user be able to sort or update the order of the Image attachments, but this I think is a topic that should be in another discussion.
How do you handle this, the first issue, properly and clearly in Rails?
Nested attributes
Rails has a built in mechanism called accepts_nested_attributes. Which lets you create nested records in the same request:
class Item
has_many :images
accepts_nested_attributes_for :images
end
class Image
belongs_to :item
has_one_attached :file
end
This will let you create an item and images with:
Item.create(name: 'Foo', image_attributes: [{ file: 'foo.jpg'}, { file: 'bar.jpg'}])
ActiveRecord will handle inserting the records in the correct order.
This allows you to have a non-nullable foreign key (item_id) and avoid orphaned records which is a very real problem with your solution. Referential integrity should be pretty high on your list of priorities.
This is used in the model together with fields_for in the view (the form).
<%= form_with(model: #item) do |form| %>
<%= form.fields_for(:images) do |image_fields| %>
<%= image_fields.file_field :file %>
<% end %>
<% end %>
And by passing an array of keys in the strong parameters:
params.require(:item).permit(:name, image_attributes: [:file])
ActiveStorage's has_many_attached
ActiveStorage also lets you setup a one to many assocation without a model by using has_many_attached. The attachements are stored in the active_storage_attachments table. However there is no way as far as I know of attaching additional metadata (such as the ordering) to the attachements.

How to upload image using paperclip without model in Rails 3?

I want to implement Papereclip in Rails 3.2 without a model.
I am very new to RoR and Paperclip.
I followed through THIS tutorial to use paperclip. It is running successfully.
But in this tutorial they have shown use of the model, I dont want to use the model or dont want to modify the existing model.
Rails is object oriented.
# Article model
attr_accessible :image
# Paperclip attachment code and whatever rules and conditions you want.
The image attribute is actually a column from your database and an attribute accessible from your Article model. That image attribute will need to be a string type (varchar) to store the url where the image is saved.
For example if your article has an image attribute, in your view file Article#Show you can display your image through the article object with the image attribute as <%= image_tag #article.image.url %>.
If you want to have many images in a same article then you will want to create 2 models (Article and Image) and have an association where
# Article model
has_many :images
accepts_nested_attributes_for :images
# Image model
attr_accessible :image_url
belongs_to :article
# paperclip attachments...
With this association you can then fetch and display your images by doing
<% #article.images.each do |image| %>
<%= image_tag image.image_url.url %>
<% end %>
Technically you don't need to store the url of your image in the database, you can store it in variable, but then it will only be valid during the session and will disappear afterwards. If you want to view your image at a later date, then yes you need to store it in a database.
For not using a seperate model you can check Paperclip Docs. What is basically done here is that the image is stored as an atrribute of an existing model.
About not using a model at all, how do you want to associate the image object to some existing record ? Wont that be required in your project ?

Saving Images using Paperclip

I am just looking for some clarification with using paperclip for saving images. I am grabbing all my images stored in Flickr using the Flickraw Gem. I am grabbing the url for the image and saving that to my model,
Model
class Portfolio < ActiveRecord::Base
attr_accessible :taken, :title, :url, :url_large
end
then rendering using the image_tag helper..
Like so
<%= #portfolio.each do |p| %>
<%= image_tag(p.url_large, :size => "480x480") %>
<%= p.title %>
<% end %>
So this shows all my photos at 480 x 480.. However I understand that paperclip handles images better?
So i can install paperclip, add :avatar column to my Portfolio model (though ill prob call it photo) and its the next part i want to clarify.
Do i save the url to the image within the :avatar column and then use the paperclip helpers as normal? Im used to uploading physical images to my model using paperclip which generates a file name within that column (well thats from what I can see)
I save the attributes like so at the moment
flickr.photos.search(:user_id => FLICKR_USER_ID).each do |p|
info = flickr.photos.getInfo(:photo_id => p.id)
title = info.title
taken = info.dates.taken
square_url = FlickRaw.url_s(info)
original_url = FlickRaw.url_o(info)
Portfolio.where(title: title, url: square_url, taken: taken, url_large: original_url).first_or_create!
end
So where to save FlickRaw.url_o ?
Can anyone advise if im thinking about this correctly or do i have some things wrong?
Any help appreciated
You could check this other post save image from url by paperclip they explain how to insert a picture from an url.
But actually I think this will try to upload the image.

select field, text field and foreign key

I would like to know which way is the best to resolve my question :
I have a form in order to select people via a select field. If the name is missing in the select field, a text field is available to add directly the person's name.
- The form in new.html.erb is the format of the new action of the Team controller.
- The list of the people is extracted from the People model.
def new
#team = Team.new
#people = People.all
end
I created an attribute in the Team model to store the new_person text field :
class Team < ActiveRecord::Base
attr_accessor :new_person
...
end
Finally, here's an extract of my view :
<%= f.select :person_id, #people.map { |p| [p.name, p.id] } %>
<%= f.text_field :new_person %>
Obviously, I would like to save the new person in the table Person before saving the data from the form. As usual, the id are saved instead of the names
At this point, I've got two issues :
1/ The params array has the key new_person what doesn't have the table. So it is not possible to use the Team.new(params[:team]) method. Does exist an easy solution to avoid this problem ?
2/ As I need the person_id, how can I get it when the name comes from the new_person field? In using the before_filter method ?
Thanks a lot,
Camille.
1) You should consider using fields_for in your view within your form_for block. This will allow you to specify that the fields within the fields_for block are attributes of a different model, will generate the appropriately named input fields, and allow you to use params[:team] in your controller. See the FormHelper documentation for more on this.
2) While you could do something in your controller to first check for a value in the new_person field, create the record, update the contents of params[:team] with the value of the newly created person and create the team, this feels a bit like a hack to me. Another possible solution which may be less fragile would be to use some JavaScript on the page that would render some kind of modal dialog for the user to create the new person, submit the new person to the person#create controller method, then refresh your drop down. It would probably not be terribly difficult to do this using a jQuery UI modal form (very good example at that link to do what you need) with Rails remote form and unobtrusive JavaScript.
This is probably a more difficult solution to your second question than you are hoping for, but probably more useful in the long run.

Resources