Rails: randomly pick a image as background image - ruby-on-rails

I'm making a page with a background image, and I want to make the background image changes automatically.
The images have been saved in directory already.
One possible solution would be set up a BackgroundImage class, and make those images record in the database, so #backgroundimage = BackgroundImage.all.shuffle.first, and then use it in view page like img_tag #backgroundimage
Another approach would be something like <img src='backgroundimages/image#{#random-number}>? I think this is possible if I name those images like image1,image2,and get a random number each time in the controller. But it requires renaming images by hand.

You could put the images in one Folder then read all filenames and return a random image name so you whouldnt need a naming schema or a database backed model. Like this:
def random_image
blacklist = [".", ".."]
file_names = Dir.glob("/path/to/images/*")
blacklist.each do |blacklsited|
file_names.delete(blacklisted)
end
"/webserver/path/to/images/{files.shuffle.first}"
end
So in the view you could call the helper method random_image.

try this out
BackgroundImage.order("RAND()").limit(1)
depending on your database it could be ‘RANDOM()’ or something similar
it generates sql query like this
SELECT "background_images".* FROM "background_images" ORDER BY RAND() LIMIT 1

Related

How can I preserve storage space and load time with Active Storage?

I have a user submission form that includes images. Originally I was using Carrierwave, but with that the image is sent to my server for processing first before being saved to Google Cloud Services, and if the image/s is/are too large, the request times out and the user just gets a server error.
So what I need is a way to upload directly to GCS. Active Storage seemed like the perfect solution, but I'm getting really confused about how hard compression seems to be.
An ideal solution would be to resize the image automatically upon upload, but there doesn't seem to be a way to do that.
A next-best solution would be to create a resized variant upon upload using something like #record.images.first.variant(resize_to_limit [xxx,xxx]) #using image_processing gem, but the docs seem to imply that a variant can only be created upon page load, which would obviously be extremely detrimental to load time, especially if there are many images. More evidence for this is that when I create a variant, it's not in my GCS bucket, so it clearly only exists in my server's memory. If I try
#record.images.first.variant(resize_to_limit [xxx,xxx]).service_url
I get a url back, but it's invalid. I get a failed image when I try to display the image on my site, and when I visit the url, I get these errors from GCS:
The specified key does not exist.
No such object.
so apparently I can't create a permanent url.
A third best solution would be to write a Google Cloud Function that automatically resizes the images inside Google Cloud, but reading through the docs, it appears that I would have to create a new resized file with a new url, and I'm not sure how I could replace the original url with the new one in my database.
To summarize, what I'd like to accomplish is to allow direct upload to GCS, but control the size of the files before they are downloaded by the user. My problems with Active Storage are that (1) I can't control the size of the files on my GCS bucket, leading to arbitrary storage costs, and (2) I apparently have to choose between users having to download arbitrarily large files, or having to process images while their page loads, both of which will be very expensive in server costs and load time.
It seems extremely strange that Active Storage would be set up this way and I can't help but think I'm missing something. Does anyone know of a way to solve either problem?
Here's what I did to fix this:
1- I upload the attachment that the user added directly to my service provider ( I use S3 ).
2- I add an after_commit job that calls a Sidekiq worker to generate the thumbs
3- My sidekiq worker ( AttachmentWorker ) calls my model's generate_thumbs method
4- generate_thumbs will loop through the different sizes that I want to generate for this file
Now, here's the tricky part:
def generate_thumbs
[
{ resize: '300x300^', extent: '300x300', gravity: :center },
{ resize: '600>' }
].each do |size|
self.file_url(size, true)
end
end
def file_url(size, process = false)
value = self.file # where file is my has_one_attached
if size.nil?
url = value
else
url = value.variant(size)
if process
url = url.processed
end
end
return url.service_url
end
In the file_url method, we will only call .processed if we pass process = true. I've experimented a lot with this method to have the best possible performance outcome out of it.
The .processed will check with your bucket if the file exists or not, and if not, it will generate your new file and upload it.
Also, here's another question that I have previously asked concerning ActiveStorage that can also help you: ActiveStorage & S3: Make files public
I absolutely don't know Active Storage. However, a good pattern for your use case is to resize the image when it come in. For this
Let the user store the image in Bucket1
When the file is created in Bucket1, an event is triggered. Plug a function on this event
The Cloud Functions resizes the image and store it into Bucket2
You can delete the image in Bucket1 at the end of the Cloud Function, or keep it few days or move it to cheaper storage (to keep the original image in case of issue). For this last 2 actions, you can use Life Cycle to delete of change the storage class of files.
Note: You can use the same Bucket (instead of Bucket1 and Bucket2), but an event to resize the image will be sent every time that a file is create in the bucket. You can use PubSub as middleware and add filter on it to trigger your function only with the file is created in the correct folder. I wrote an article on this

Active Record/Ruby on Rails -> Delete image by click on link: Set attachment columns to NULL via

I am not sure how to do this. I have a table called "animals".
Then i can add an image to display the animal.
I use this statement in the form-view for changing the image:
f.file_field :attachment, :accept => 'image/jpeg'
When i submit the form i got a nice looking image in my show-view.
Like i said, i can easily change the image, but i do not know exactly how to delete it. In a perfect world i would prefer, i press a link and the image gets just deleted, but the rest of the animals attributes stay alife, so the following columns are set to NULL and the file gets deleted from the server:
"attachment_file_name"
"attachment_content_type"
"attachment_file_size"
"attachment_update_at" (should probably be set to the current date, or set to NULL)
Any help apreciated.
If you are using paperclip gem all you need to do is set attachment to nil and save. Like this:
#animal.attachment = nil
#animal.save
More info here
In your case, if you want to add a link to remove, you will create a route and an action just to do this.
I suggest adding a checkbox nearby the file field and verify it at update/create.

RoR + Paperclip - Have two objects reference same paperclip image

I'm creating a demo portion to my website. Where each new demo account generates dummy data. The dummy data includes images. It takes way too long and takes up unnecessary space when creating these dummy images though. They are just copies of images that already exist in the database.
I have an Image object with the relationship has_attached_file :pc_image. I want a new image to reference the pc_image in an old image, not create a duplicate. I've been trying something like this (p statements added to show the difference), but it just won't work:
p old_image
=> images/001/original/old_image.jpg
new_image.pc_image = old_image.pc_image
new_image.save
p new_image
=> images/002/original/old_image.jpg
Notice the ID after images... 001 and 002. Paperclip seems to automatically create a new URL using the ID of its parent object instead of using the old URL. It doesn't duplicate the images though, which is what I want. But I'm getting a missing image in the view because the pc_image URL is pointing to an image that was never created instead of pointing to the old_image URL, which is what I want. Is this possible? Is there a good workaround?

Find specific job in resque queue

In my application, I'm using Resque to resize images. If an image is in the resizing queue, I want to show a "resizing image" icon.
This means that I need to be able to find all current jobs relating to a specific model ID in the queue. Right now I do it like this:
Resque.peek(:resize, 0, 100).find_all { |job| /#{model.id}/.match(job["args"][0]) }
This is stupid. But is there any way to query the Resque queue to find all jobs where the first argument is equal to [id]?
Thanks in advance.
Give resque-status a try. It is an extension to Resque that adds job tracking.
resque-status provides a set of simple classes that extend resque’s default functionality (with 0% monkey patching) to give apps a way to track specific job instances and their status. It achieves this by giving job instances UUID’s and allowing the job instances to report their status from within their iterations.
Note: d11wtq mentioned this above as a comment, but is actually the best answer so far.
Instead of querying resque queue, you should store image meta-data along with your model.
Lets assume you are storing product images. You are likely using a Redis hash to store product details. Just add another flag like this -
hset product:123 is_resizing true
You can them perform a simple lookup to show the resizing image icon. At the end of your resque job, delete the is_resizing key, and add the resized_image_url key.
I think the easiest way might be to use a redis set to cache this information.
When you add an image to the 'resize' queue, also add the image id to the 'resize_in_progress' set using SADD. (I assume you have some kind of unique key or name to refer to the image, even if not stored in the db. Maybe the full path to the filename.)
In the 'resize' process, as one of the final actions after successfully resizing the image, remove it from the set using the SREM command.
When you want a list of all images, you can fetch that with SMEMBERS. If you want just the members for a specific model id, you might need to store a separate set for each model, named something like 'resize_in_progress_3451' where 3451 is the id of the model that has the images being resized.
See http://redis.io/commands#set for more set commands.

Multiple File Uploads for a new record

I have implemented multiple file uploads for existing records as seen here https://github.com/websymphony/Rails3-Paperclip-Uploadify
I would like to know if it is possible to implement multiple file uploads when creating a new record.
Since we need flash to do the multiple file uploads, how would it be possible to associate the uploaded files with the record if the record has not yet been created.
I have thought of a hack-ish way to essentially make a "draft" and update it. However, I hope there is a better way to do this.
There is no better than the kind of hackish way you present:
creating orphans objects and give them parents later or delete them (sad huh? :) )
creating parent object by default, add some confirmation field in the form so that you know what objects really have an owner, delete the rest.
BTW, you don't "need" flash for multiple uploads, see here: http://blueimp.github.com/jQuery-File-Upload/
Yes, you can use http://blueimp.github.com/jQuery-File-Upload/. But there are still some points you need to be careful.
Don't forget to remove appended array after you define the file field with "multiple". For example: replace "photo[image][]" with "photo[image]".Otherwise file uploaders like "carrierware" will not be working.
If you are using rails 3.2.13, the appended array will always appear no matter whether you set the name to be without appended array. You can use "file_field_tag" to resolve this problem. Please refer this issue to: https://github.com/tors/jquery-fileupload-rails/issues/36.
For the association:
You need to create a hidden text field which contains IDs of images that will be associated to the record you are going to create.
Upload images by "jquery-fileupload"(it is ajax), then get IDs of images from response bodies.
Set these IDs to the hidden field.

Resources