How to make localized Paperclip attachments with Globalize3? - ruby-on-rails

I have a project using Paperclip gem for attachments and Globalize3 for attribute translation. Records need to have a different attachment for each locale.
I though about moving Paperclip attributes to translation table, and that might work, but I don't think that would work when Paperclip needs to delete attachments.
What's the best way to achieve something like that?
UPDATE: to be clear, I want this because my client wants to upload different images for each locale.

Unfortunately I didn't find a way to do this using Globalize3. In theory, I could have added a separate model for image and add image_id to list of translated columns (to have something like MainModel -> Translation -> Image), but it seems that Globalize has some migration issues with non-string columns.
Instead of using Globalize3, I did this with a separate Image model with locale attribute and main model which accepts nested attributes for it. Something along the lines of:
class MainModel < ActiveRecord::Base
has_many :main_model_images
accepts_nested_attributes_for :main_model_images
# return image for locale or any other as a fallback
def localized_image(locale)
promo_box_images.where(:locale => locale).first || promo_box_images.first
end
end
class MainModelImage < ActiveRecord::Base
belongs_to :main_model
has_attached_file :image
validates :locale,
:presence => true,
:uniqueness => { :scope => :main_model_id }
end
Tricky part was getting form to accept nested attributes only for one image, instead of all images in has_many relation.
=f.fields_for :main_model_images, #main_model.image_for_locale(I18n.locale) do |f_image|
=f_image.hidden_field :locale
=f_image.label :image

You could also try the paperclip-globalize3 gem, it should handle the case you describe. https://github.com/emjot/paperclip-globalize3

Ok since you asked me to share my solution to this problem even though I am using Carrierwave as a library for uploading here is it:
Ok so I would have a model setup like this:
class MyModel < ActiveRecord::Base
# ...
translates :attr_one, :attr_two, :uploaded_file
Now what I need for CarrierWave to work is place to attach the uploader to and that can be done on the Translation model
Translation.mount_uploader :uploaded_file, FileUploader
end
Now for your question about deleting, I think though I haven't needed to do it but it should work as the README says it should but on the translation model. https://github.com/jnicklas/carrierwave#removing-uploaded-files
MyModel.first.translation.remove_uploaded_file!
I haven't taken a look at paperclip for a good 2 years and if this is not applicable knowledge I suggest you try out carrierwave.

Related

rails admin + carrierwave updating breaks the image url

I'm using Rails 5 with rails_admin and carrierwave gems.
I have a model Photo and and image uploader mounted on it (as per carrierwave documentation), looks roughly like this:
class Photo < ActiveRecord::Base
mount_uploader :image, ImageUploader
belongs_to :project
validates :name, presence: true
validates :image, presence: true
end
Given I have already some Photo objects created I can see a list of them in the rails_admin admin view.
And I start editing one of them
And I edit name
And I proceed to save it
Then rails admin fires some of it's magic and photo is being saved but after this action the image dissapears.
I have been digging a little in what request are being fired and rails_admin fires a PUT request with such params:
{
"authenticity_token"=>"xxx",
"photo"=>{
"name"=>"test2",
"description"=>"ewdeeweeefxxxwefwe",
"project_id"=>"3",
"image_cache"=>"",
"main"=>"0",
"about_us"=>"0"
},
"return_to"=>"http://localhost:3000/panel-admin/photo?model_name=photo", "_save"=>"", "model_name"=>"photo", "id"=>"29"}
and my Photo object is being update with not only name but also with image_url that of course overrides Image that already was mounted to the Photo
I have no idea why this is happening and how to prevent it.
Anyone might have encountered this issue and knows how to resolve it?
I found that. When I uncomment my custom filename method in uploader, it works well.

creating an alias_attribute for a paperclip image in Rails

I'm currently building an app which has a model Post. I'm using the paperclip gem to upload images, and everything is going well.
class Post < ActiveRecord::Base
has_attached_file :headerimage, styles: {banner => "400x300#"}
end
As you can see by my class above, if I were to get a Post object, I could get the banner image with the following in my view:
image = Post.first.headerimage(:banner)
Alias
However, in my app, it must have the image attribute image refer to the thumbnail image. So, in my models class, I wrote
class Post < ActiveRecord::Base
has_attached_file :headerimage, styles: {banner => "400x300#"}
alias_attribute :image, :headerimage
end
which allows me to get an image by calling the following:
image = Post.first.image
This is what I want - however, it gets the original image from paperclip, so it is equivalent to writing the following:
image = Post.first.headerimage instead of image = Post.first.headerimage(:banner)
How can I set up a proper alias_attribute to access the paperclip thumnail? I can't seem to find an answer anywhere else, and I am unsure how paperclip is actually working.
I thought I might logically be able to do something like
alias_attribute :image, :headerimage(:banner)
but that does not work.
You can just try this - basically call the original with arguments since aliases won't take parameter but we know the fixed parameter to pass:
alias :image, :headerimage
def headerimage(name=nil)
headerimage(name || :thumb)
end

Carrierwave or Paperclip: Keep image/file when updating other form fields

When using either Carrierwave or Paperclip everything works as expected with the exception of edit/update. When an image or file exists for a record it's set to nil on save if you don't explicitly upload another file.
What I've been looking for, but haven't found, is a solution that allows other form fields to be updated but keeps the existing file. Unless, of course, the file is updated as well.
My setup is using nested models so for both Carrierwave and Paperclip I've tried the following no success.
https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-keep-%28not-replace%29-files-on-nested-edits
Apparently I'm missing something but I can't for the life of me figure out what.
Any help is greatly appreciated.
I use Paperclip with nested_form and it's straight forward, no surprises.
Did you make sure to check if your nested record is a new record or already existing? You only want to display the file_field if it's actually a new record, otherwise display the image (or anything but the file_field). However that's what your link already explained.
Additionally could you post which params are sent on submit - check your logs to do that.
Ok, so after some digging with pry I found a/the solution. First, here's the setup.
The models
class Machine < ActiveRecord::Base
has_many :parts
accepts_nested_attributes_for :parts
end
class Part < ActiveRecord::Base
belongs_to :machine
end
The form
= simple_form_for #machine do |m|
..... A bunch of inputs left out for brevity...
= m.simple_fields_for :parts do |p|
= p.input :name
= p.input :location
= p.input :price
= p.file_field :image
In the original Machine Controller - machine_posting_params
def machine_posting_params
params.require(:machine).permit(:name, part_attributes: [:name, :location, :price, :image])
end
In the working Machine Controller - machine_posting_params
def machine_posting_params
params.require(:machine).permit(:name, part_attributes: [:name, :location, :price, :image, :id])
end
The problem, which I couldn't find any documentation for, is that :id is being passed into the part_attributes by, I believe, carrierwave. When it isn't accounted for by part_attributes it causes carrierwave to dump the image reference on update.
So, if you have a nested form and your images keep disappearing on update try adding :id to xxxxx_attributs. It works for Carrierwave but I see no reason this wouldn't work for Paperclip. If I have time I'll switch and report back.

Server side validation of file size

I am trying to find a way to limit the size of files that users can upload, and it seems to be possible with javascript, but what bothers me is that what happens if a user just turns javascript off and upload like 1GB file to the server, and I did find a way to validate it on server side but it takes place once the file has been uploaded, which isnt an option. So I am trying to find the right way to do it but the best thing I came up with is to create a form for file uploading with javascript and make validations on client side but I guess there is still a way around it...
I am using RoR and CarrierWave if that matters ...
EDIT
1)Everything I can find on this page is to use size validation which happens after the file has been uploaded
2) I use that validation by carrierwave but again, it takes place once the file has been uploaded, it doesnt make sense to wait an hour till some huge file gets uploaded just to know its too big
You can use a Rails custom validator to verify your attachment meets specific file size requirements.
Grab a copy of the validator from https://gist.github.com/1009861 and save it to your lib/ folder as file_size_validator.rb. Add the error translations to config/locales/en.yml or wherever is appropriate for your setup. Then do this in your parent model:
# app/models/brand.rb
require 'file_size_validator'
class Brand < ActiveRecord::Base
mount_uploader :logo, BrandLogoUploader
validates :logo,
:presence => true,
:file_size => {
:maximum => 0.5.megabytes.to_i
}
end
Like validates_length_of, validates_file_size accepts :maximum, :minimum, :in [range], and :is options.
Another solution
A custom validator could also be used like this.
app/models/user.rb
class User< ActiveRecord::Base
attr_accessible :product_upload_limit
has_many :products
end
app/models/brand.rb
class Product < ActiveRecord::Base
mount_uploader :file, FileUploader
belongs_to :user
validate :file_size
def file_size
if file.file.size.to_f/(1000*1000) > user.product_upload_limit.to_f
errors.add(:file, "You cannot upload a file greater than #{upload_limit.to_f}MB")
end
end
end
Here, the upload limit varies from user to user & is saved in the user model.
The problem was solved by nginx, which works like a buffer so if somebody is trying to upload a 1GB file, it will go to your buffer first so it won't block your unicorn instance until it's completely uploaded, so that's one good thing. The other is that you can set the max content-length parameter in your nginx config file, so if you set to 5 Megabytes and somebody is trying to upload 1 GB file, it'll be denied. But again, I am not sure how it works, if it just checks the http header, then I have a feeling that somebody can simply tamper with this value and fool your nginx.

Elasticsearch Tire Carrierwave remote upload

I'm desperately don't success to make Carrierwave working with Tire (Elasticsearch gem).
I have a Question model which was an ActiveRecord one, but I migrated it on Elasticsearch with Tire. Until it was on ActiveRecord everything was working great. But not anymore.
What I want is to upload a remote file (from Facebook) to a S3 bucket. All config files are correct (as it was working under ActiveRecord model)
Here is my Question model:
class Question
include ActiveModel::MassAssignmentSecurity
include ActiveModel::Validations
extend CarrierWave::Mount
include Tire::Model::Callbacks
include Tire::Model::Persistence
# set fields for carrierwave uploader
mount_uploader :path, QuestionUploader
validates_presence_of :question
attr_accessible :path
attr_accessor :remote_path_url, :remove_path
property :difficulty
property :question
property :path
end
And then in my questions_controller:
class QuestionsController < ApplicationController
def create
#question = Question.new question: "How are you ?", difficulty: 3
#question.remote_path_url = "http://domain.com/file.jpg"
#question.save
render nothing: true
end
end
Elasticsearch record works, but no upload happens...
Someone has an idea ?
Cheers
After save callback should be explicitly called for non active record models, as mentioned here.
Like #question.store_image!
Also it may not be good idea to use path as file name, as it is confusing.
For example: #question.store_path!
I had a similar problem and ended up using Dragonfly for Models which integrated very well with a non-ActiveRecord model. I am using elasticsearch-persistence with a standalone persistence layer for models.

Resources