I'm using CarrierWave uploader for user avatars in my rails project and I'm currently providing a default URL (randomly chosen from 4 choices) for users who didn't upload their pictures. As suggested by the CarrierWave README, I've implemented it as thus:
class UserAvatarUploader < ImageUploader
def default_url(*args)
ActionController::Base.helpers.asset_path("avatars/default_avatar_#{rand(4).to_s}.png")
end
end
Since it is a random function, the problem here is that the user's avatar changes every time the page is reloaded. Ideally I would like to derive a number from the username of the user so that his/her default avatar stays consistent. How might I do that? Thanks in advance!
Ok I solved it. Essentially I was stuck because I couldn't find a way to get user information from within the default_url method. I later found out that the variable model is available and it refers to the user. Here's my final code:
class UserAvatarUploader < ImageUploader
def default_url(*args)
number = model.id % 4
ActionController::Base.helpers.asset_path("avatars/default_avatar_#{number}.png")
end
end
Related
I am using rails_admin gem for the rails api application for the backend admin side.
I am using rails 6 with active_storage and storing attachments on the S3.
On the admin side, I need to display the list of attachments which might be images or files anything.
My question is How to show those in index method, do I need to show images then what to show in pdf/docs, do I need to show the only the link of s3?
currently, it looks like these broken images are the images and other one were files
My model
class Attachment < AttachmentBlock::ApplicationRecord
self.table_name = :attachments
include Wisper::Publisher
has_one_attached :attachment
belongs_to :account, class_name: 'AccountBlock::Account'
has_and_belongs_to_many :orders , class_name: 'BxBlockOrdermanagement::Order'
scope :not_expired, -> {where('is_expired = ?',false)}
end
What should I use here to list the attachment that the user upload?
how to check the attachment type and then if its image then shows the image and if it files then show file url from s3?
thanks.
Yours is a two part question:
To add links to the side menu on rails admin you need to define models so if you wanted an index for all the attachments of type 'pdf' you could use rails STI (single table inheritance) or define a custom default_scope like this:
class ImageAttachment < Attachment
def self.default_scope
where('attachments.attachment LIKE ?', '%png') }
end
end
To customize the row of each individual attachment record you need to defined that behavior for the field on the model rails admin config.
For example you can put this block of code inside your ImageAttachment class.
class ImageAttachment < Attachment
def self.default_scope
where('attachments.attachment LIKE ?', '%png') }
end
rails_admin do
configure :attachment do
view = bindings[:view]
attachment = bindings[:object]
if view
view.content_tag(:img, attachment.attachment.url)
else
''
end
end
list do
field :attachment
end
end
end
As you can see inside the attachment field configuration block you have access to the attachment object and of course all its methods, you can determine the type of attachment and do some special html rendering.
I have Rails api, and I use it to upload photos from the mobile app. Is there a way to set something like a timer from Rails back-end to let say allow one user only upload twice in an hour and/or another user twice in a day? I am a little puzzled with logic of how it can work from the back-end since I don't wanna do it from the front-end. Any advice is appreciated. Thanks!
Simple. Just check how many records the user has created in the alloted time frame. Lets say you have the following associations:
class User
has_many :photos
end
class Photo
belongs_to :user
end
And a current_user method that returns the authenticated user.
To query based on a time frame you use a range:
def create
#photo = current_user.photos.new(photo_params)
unless current_user.photos.where(created_at: (1.hour.ago..Time.now)).count <= 2
#photo.errors.add(:base, 'You have reached the upload limit')
end
# ...
end
Later when you refactor you can move this into the model as a custom validation.
I have some meta-programming that condenses multiple direct S3 upload processes into a single controller. Then, those actions are shared across many models. To accomplish this, among many other details, I need to know the column name of a given models uploader. I facilitate this by having an 'uploader_name' method in each of my models that use the shared actions. For example, I have an Expense model that has a receipts uploader...
def uploader_name
'receipt'
end
mount_uploader :receipt, ReceiptUploader
So, now I can call Expense.new.uploader_name to return a string, or in a shared action I would use #obj.send(#obj.uploader_name) to get the uploader object (where #obj is any one of the affiliated models using the shared actions). This works fine. However, I think I can clean it up. One way that would help me refactor is not needing the uploader_name method. Or, being able to use a single uploader_name method in a shared module that is able to dynamically determine the column name of the uploader.
Is there a method within Carrierwave where I can access the column name on a models uploader without know what the uploader is called? Since the module is shared across multiple classes, i have to figure it out dynamically---I have read through the carrierwave, but not finding a solution.
Something like this...for example:
obj = self.class.name
obj.new.uploader # would return the mounted :receipts uploader
obj.new.uploader_column # would return the uploader column, in this case :receipt
There are a few methods provided by the uploader class. So, to solve this I just delegate the :mounted_as method from the uploader to the class that calls it. Then, without knowing the name of the actual uploader I can just call self.mounted_as.to_s to return the name of the uploader.
So, for a User model with an avatar uploader I do this.
class User
mount_uploader :avatar, AvatarUploader
delegate :mounted_as, to: :avatar
So, if I call #user.mounted_as I get :avatar. Or, more importantly...if I call self.mounted as I get :avatar when self is a User. I have a module that is shared between all models that use an uploader. In that module I include
def uploader_name
self.mounted_as.to_s
end
Now, I can do some meta-programming since I do not know who self is, and I do not know what self's uploader is called. But, now I can get access to both dynamically. This is helpful because I can share one uploader controller and one uploader form across multiple classes who use an uploader.
I can also do things like pass accepted extensions to the form input (see html5 accepts option) by simply grabbing the extensions in the uploader's extension_whitelist method.
self.send(uploader_name).extension_whitelist
# this is dynamically getting #user.avatar.extension_whitelist
# or #company.logo.extension_whitelist
I can also set uploader attributes using, for example...
obj.send(obj.uploader_name).content_type = 'image/jpeg'
# this is dynamically setting #user.avatar.content_type = 'image/jpeg'
# or #company.logo.content_type = 'image/jpeg'
obj.class.uploaders.keys.first
=> :receipt
I have an uploader (carrierewave) to save several files, but I'm afraid that some days, some files will have the same name and will cause a problem. Moreover, I'd want the folders to keep a semblance of organization.
So, I have a first scaffold, "magazine" that allows me to create a magazine with its title and several images. The second scaffold that I use, "page", allows the multi upload system.
With this method, my models look like this:
magazine.rb
has_many :pages, :inverse_of => :magazine, :dependent => :destroy
pages.rb
belongs_to :magazine
Now in my uploader, I have the following:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}"
end
which creates folders like this : "uploads / page / image", then uploads every single images inside it, even thought if it's for an other magazine, so the images get mixed up.
My question is the following: is there a way to get to write the magazine's ID instead ? This implies to find the class "pages" belongs to, but I didn't find anything answering my question on google.
Thank you in advance
If you have a custom uploader mounted in the model Page you could access the model attributes normally
PageImageUploader.rb
def store_dir
"uploads/magazines/#{model.magazine.id}/#{model.id}"
end
However, you can't access the models IDs unless your models are persisted. A workaround for that can be find here.
I'm somehow newly intermediate(:D ) in rails and i'm starting to tackle more complex project that require multiple class and interaction with my model and i'm kind of lost in how to design/order my code.
I have a product_table and a product_details_table.
Every time a product is created an image is uploaded with it.
In the class Product , i have created a few methods that populate virtual attributes for that product related to that image ( size , etc. ).
This is all working with paperclip callback after upload.
My issue is that from that image size i would like to automatically generate attributes values in my product_details table.
Product_details.new(product_id:current_product_id(**is it self.id here?**),size:product.virtual_attribut_size,x:virtual_attribut_x)
How would you do that ?
I would have done it in my controller the thing is it has to been done automatically after the file upload and not before and i don't know how to do that.
If i do it in my model i'm guessing it can work ( as a normal class ) but is that the way to do it ?
Thanks to those who try to help
edit :
Basically my Product model would look like this :
class Product < ActiveRecord::Base
def image_to_inch
#return "30x30" (in inch) for an image uploaded in pixel (divide the number of pixel by 300dpi to get a good quality print )
end
def image_printable_size
#use previous method result to output an array of all printable size from a list of predifined sizes. example : 30x30 can be printed in 30x30,20x20,10x10 but not 40x40.
#output ["30x30","20x20","10x10"]
end
##Here i should iterate over the array and create a product_details line for that product for each size :
## simplified version of what i was going for and that look really really ugly :
["30x30","20x20","10x10"].each do |size|
ProductDetail.create!(product_id:self.id,size:size)
end
end
i've left out the callbacks,validation,etc. so that it's easier to read.
Your requirements aren't clear, but here's some strategy tips.
Use before_save or after_save callbacks to automate code.
Use attr_accessor variables to hold temporary objects which are used by before_save and after_save callbacks
Make simple methods to do simple things. Remember that you can write your own custom getter and setter methods.
So, your approach could be something like this: i'm guessing at your schema so don't get too hung up on the details.
class Product
has_one :product_detail
after_save :update_product_details
def update_product_detail
product_detail = self.product_detail || self.product_detail.build
if self.image
product_detail.update_from_image(self.image)
end
product.save
end
class ProductDetail
belongs_to :product
def update_from_image(image)
self.size = image.size
#... any other settings taken from the image
end