How to dynamically determining carrierwave uploader column name? - ruby-on-rails

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

Related

How Do I get the class to which the current class belongs to?

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.

CarrierWave uploader default image using parent model attributes

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

Where/how to include geography helper method - Rails?

I have a helper method, states_list, that is returning an array of US states that I want to access in a few different places of my Rails app including:
User model: validates :state, inclusion: { in: states_list }
User model spec: test for this validation
These will be reused elsewhere in addition to the User model. I am wondering where the proper place to store this helper method is, and how to access it from the model and tests. (My initial thought was in a GeographyHelper file inside the helpers directory, but I read that those are meant specifically to be view helpers...) Thanks!
You'd probably be best served by putting your states_list method in its own module and including it in your User model. The advantage of creating a module is that your concerns are nicely separated and reusable (in case you want to validate states in other models.
1) Create a place to put your module by going into your /lib directory and creating a directory for your custom modules (we'll call it custom_modules here).
2) Create your module file: /lib/custom_modules/States.rb
3) Write your module:
module CustomModules
module States
def states_list
#your logic here
end
end
end
4) Include your new States module in your User model or any other model where you'd like this functionality.
class User < ActiveRecord::Base
include CustomModules::States
validates :state, inclusion: { in: states_list }
end
You can store this method in either application helper or in user model it self.

Rails ActiveRecord non db attributes?

I've got an ActiveRecord model, Instance, which is based in the database, but has some non-database attributes.
One example is 'resolution'.
I need to be able to set/get the resolution, but this attribute needs custom non-db setters/getters. Where do I put these & how do I structure my model?
I also need to be able to validate resolutions as they are set via regex. Can I use validates_format_of or do I need to code a custom Validator?
If you need standard reader/writer methods, you can use attr_accessor:
class Instance
attr_accessor :resolution
end
You can also write the reader and writer method by yourself:
class Instance
def resolution
#resolution
end
def resolution=(value)
#resolution = value
validate! # this will raise RecordInvalid if the validation fails
end
end

Ruby (Rails) Delegate attributes to another model's method?

-EDIT-
After reading about the Delegate method from the first answer, my question is this, is it possible to delegate two different methods to another single method.
IE: I currently have: #photo.attachment.file.url, and #photo.attachment.height, and #photo.attachment.width
I'd like to be able to access all of these via #photo.file.url, #photo.file.height, #photo.file.width.
The reason for the syntax is Attachment is a model that uses Paperclip to manage files, Paperclip is generating the .file method (the model is called Attachment, the model uses Paperclip's has_attached_file :file).
-ORIGINAL QUESTION-
I was wondering about aliasing methods and attributes in Ruby (I think this is a general ruby question, although my application is in Rails 3):
I have two models: Photo has_one Attachment.
Attachment has "height" and "width" attributes, and a "file" method (from Paperclip).
So by default I can access bits of the Attachment model like so:
photo.attachment.width # returns width in px
photo.attachment.height # returns height in px
photo.attachment.file # returns file path
photo.attachment.file.url #returns url for the default style variant of the image
photo.attachment.file.url(:style) #returns the url for a given style variant of the image
Now, in my photo class I have created this method:
def file(*args)
attachment.file(*args)
end
So, now I can simply use:
photo.file # returns file path
photo.file.url # returns file url (or variant url if you pass a style symbol)
My question is, I was able to direct photo.attachment.file to just photo.file, but can I also map height and width to photo.file, so that, for the sake of consistency, I could access the height and width attributes through photo.file.height and photo.file.width?
Is such a thing possible, and if so what does it look like?
So what you are asking is that
photo.file --> photo.attachment.file
photo.file.url --> photo.attachment.file.url
photo.file.width --> photo.attachment.width
You can't solve this with delegates, because you want that file to mean different things based on what follows next. To achieve this you would need to reopen paperclip, which i would not recommend (because i believe the api is good the way it is).
The only way i can think of to solve this, is to add eliminate the file level too. Like so:
photo.width --> photo.attachment.width
photo.file --> photo.attachment.file
photo.url --> photo.attachment.file.url
This you could then solve by using a delegate for each of the wanted methods.
So you write
class Photo
delegate :width, :height, :file, :to => :attachment
delegate :url, :to => :'attachment.file'
end
Hope this helps.
You can use Rails 'delegate' method. Have a look at my answer for this question:
What is a more Ruby-like way of doing this command?
The simplest way that comes to mind is to delegate url method in attachment to file:
class Attachment < ActiveRecord::Base
delegate :url, :to => :file
end
This way you can call photo.attachment.url, photo.attachment.width, photo.attachment.height, which for me seems pretty consistent. You could optionally alias attachment to file - this way you'd get the exact method names you asked for (photo.file.width, photo.file.url), but I would not recommend that, because it seems confusing (calling an attachment "file").
class Photo < ActiveRecord::Base
def file
attachment
end
end
With plain Ruby you can use Forwardable:
require 'forwardable'
class RecordCollection
attr_accessor :records
extend Forwardable
def_delegator :#records, :[], :record_number
end

Resources