Who can explain how this code block works: Carrierwave MD5 as filename - ruby

I am using the Carrierwave gem with a Sinatra app to upload and store images.
I am following How to: Use file's MD5 as filename and everything works as expected. However I do not understand how the following piece of code provided on the how-to page works:
class ImageUploader < CarrierWave::Uploader::Base
storage :file
def md5
chunk = model.send(mounted_as)
#md5 ||= Digest::MD5.hexdigest(chunk.read)
end
def filename
#name ||= "#{md5}#{File.extname(super)}" if super
end
end
I in particular do not understand what model.send(mounted_as)does, what the ||= operator means, and why the if super conditional is used (and what it does).
Could somebody please explain this to me?

Say, for example the model is Person and the ImageUploader is mounted as avatar.
class Person < ActiveRecord::Base
mount_uploader :avatar, ImageUploader
end
Then, the md5 method would be calling something to the affect of chunk = person.avatar and using this to calculate the hash of the file contents, which you want for the name.
The filename method checking to see if there is a #filename instance variable, as defined in the CarrierWave::Uploader::Store class.
class CarrierWave::Uploader::Store
def filename
#filename
end
end
Then calling this again to get the filename extension to use in the constructed filename. The #name variable is then just a temporary cache of the name, so that future calls to the method do not require the whole thing to be calculated again.
Edit:
The carrierwave uploader has methods/instance variables for the model (eg Person instance) and declared mount point in the model (eg avatar). These are from the mount_uploader declaration in your active record model.
module CarrierWave::Uploader::Mountable
attr_reader :model, :mounted_as
def initialize(model=nil, mounted_as=nil)
#model = model
#mounted_as = mounted_as
end
end
These are used for various things, as well as being made available for us to do things just such as you are trying. It is just an abstract way to call person.avatar, which returns the file (File instance not string path). This is then read into the MD5 lib, which gives the hexdigest.
Rewriting this in more plain terms
class ImageUploader < CarrierWave::Uploader::Base
def md5
uploaded_file = model.send(mounted_as) # person.avatar (File instance)
#md5 ||= Digest::MD5.hexdigest(uploaded_file.read) # hexdigest of file content
end
end

Related

Where does CarrierWave stores uploads

Please help me understand how does CarrierWave works.
I'm using minimal Sinatra/DataMapper app with following contents:
class VideoUploader < CarrierWave::Uploader::Base
storage :file
end
class Video
include DataMapper::Resource
property :id, Serial
property :name, String
property :desc, Text
mount_uploader :file, VideoUploader
end
get '/' do
slim :form
end
post '/' do
video = Video.new
video.name = params[:name]
video.desc = params[:desc]
video.file = params[:file]
video.save
redirect '/'
end
As I understood mount_uploader :file, VideoUploader string in Video definition adds .video method to Video instance, and I can store uploads assigning params[:file] to it. When I'm trying to send form from browser, the request successfully creates record in DB table, but I can't find any signs of file existence either in DB and public_directory. What I'm doing wrong?
You probably should define the store_dir method inside the VideoUploader class:
class VideoUploader < CarrierWave::Uploader::Base
storage :file
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
....
end

CarrierWave: Use of file name in store_dir

I'm saving images to S3 using CarrierWave. Before uploading the images are renamed to a random string. Now for faster lookups I wanted to put the files in directories named after the two first letters of the filenames. But doing so, Rails aborts with stack level too deep.
So I assume calling #{model.image[0, 2] leads to an infinite recursion. Is there a way to access just the file name as a string? Or an even simpler solution?
This is the uploader:
class ImageUploader < CarrierWave::Uploader::Base
storage :fog
def store_dir
"images/#{model.image[0, 2]}/"
end
def filename
"#{secure_token}.#{file.extension}" if original_filename.present?
end
protected
def secure_token
var = :"##{mounted_as}_secure_token"
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.hex(8))
end
end
try the :file_identifier method
class ImageUploader < CarrierWave::Uploader::Base
...
def store_dir
"images/#{model.file_identifier[0, 2]}/#{model.id}"
end
...
end
The easy answer is to add a proxy attribute such as filename in the Image model and access that using read_attribute:
class Image < ApplicationRecord
mount_uploader :image, ImageUploader
def filename
read_attribute(:image)
end
...
end
Then in your ImageUploader#store_dir method make reference to the proxy attribute filename:
def store_dir
"images/#{model.filename}"
end
This worked for me and eliminated the recursive error problem.

Issue loading classes order EDIT: works, although some odd behavior along the way

I'm working on a project to recreate some of the functionality of ActiveRecord. Here's the portion that isn't working
module Associations
def belongs_to(name, params)
self.class.send(:define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
self.class.send(:define_method, :other_table_name) do |other_class|
other_class.table_name
end
.
.
.
o_c = other_class(name, params)
#puts this and other (working) values in a query
query = <<-SQL
...
SQL
#sends it off with db.execute(query)...
I'm building towards this testing file:
require 'all_files' #holds SQLClass & others
pets_db_file_name = File.expand_path(File.join(File.dirname(__FILE__), "pets.db"))
DBConnection.open(pets_db_file_name)
#class Person
#end
class Pet < SQLClass
set_table_name("pets")
set_attrs(:id, :name, :owner_id)
belongs_to :person, :class_name => "Person", :primary_key => :id, :foreign_key => :owner_id
end
class Person < SQLClass
set_table_name("people")
set_attrs(:id, :name)
has_many :pets, :foreign_key => :owner_id
end
.
.
.
Without any changes I received
.../active_support/inflector/methods.rb:230:in `block in constantize': uninitialized constant Person (NameError)
Just to make sure that it was an issue with the order of loading the classes in the file I began the file with the empty Person class, which, as predicted gave me
undefined method `table_name' for Person:Class (NoMethodError)
Since this is a learning project I don't want to change the test to make my code work (open all the classes, set all the tables/attributes then reopen them them for belongs_to. But, I'm stuck on how else to proceed.)
EDIT SQLClass:
class SQLClass < AssignmentClass
extend SearchMod
extend Associations
def self.set_table_name(table_name)
#table_name = table_name
end
def self.table_name
#table_name
end
#some more methods for finding rows, and creating new rows in existing tables
And the relevant part of AssignmentClass uses send on attr_accessor to give functionality to set_attrs and makes sure that before you initialize a new instance of a class all the names match what was set using set_attrs.
This highlights an important difference between dynamic, interpreted Ruby (et al) and static, compiled languages like Java/C#/C++. In Java, the compiler runs over all your source files, finds all the class/method definitions, and matches them up with usages. Ruby doesn't work like this -- a class "comes into existence" after executing its class block. Before that, the Ruby interpreter doesn't know anything about it.
In your test file, you define Pet first. Within the definition of Pet, you have belongs_to :person. belongs_to does :person.constantize, attempting to get the class object for Person. But Person doesn't exist yet! Its definition comes later in the test file.
There are a couple ways I can think that you could try to resolve this:
One would be to do what Rails does: define each class in its own file, and make the file names conform to some convention. Override constant_missing, and make it automatically load the file which defines the missing class. This will make load order problems resolve themselves automatically.
Another solution would be to make belongs_to lazy. Rather than looking up the Person class object immediately, it could just record the fact that there is an association between Pet and Person. When someone tries to call pet.person, use a missing_method hook to actually define the method. (Presumably, by that time all the class definitions will have been executed.)
Another way would be do something like:
define_method(belongs_to) do
belongs_to_class = belongs_to.constantize
self.class.send(:define_method, belongs_to) do
# put actual definition here
end
self.send(belongs_to)
end
This code is not tested, it's just to give you an idea! Though it's a pretty mind-bending idea, perhaps. Basically, you define a method which redefines itself the first time it is called. Just like using method_missing, this allows you to delay the class lookup until the first time the method is actually used.
If I can say one more thing: though you say you don't want to "overload" method_missing, I don't think that's as much of a problem as you think. It's just a matter of extracting code into helper methods to keep the definition of method_missing manageable. Maybe something like:
def method_missing(name,*a,&b)
if has_belongs_to_association?(name)
invoke_belongs_to_association(name,a,b)
elsif has_has_many_association?(name)
invoke_has_many_association(name,a,b)
# more...
else
super
end
end
Progress! Inspired by Alex D's suggestion to use method_missing to delay the creation I instead used define_methodto create a method for the name, like so:
define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
define_method(:other_table_name) do |other_class|
other_class.table_name
end
#etc
define_method(name) do #|params| turns out I didn't need to pass in `params` at all but:
#p "---#{params} (This is line 31: when testing this out I got the strangest error
#.rb:31:in `block in belongs_to': wrong number of arguments (0 for 1) (ArgumentError)
#if anyone can explain this I would be grateful.
#I had declared an #params class instance variable and a getter for it,
#but nothing that should make params require an argument
f_k = foreign_key(name, params)
p f_k
o_c = other_class(name, params)
o_t_n = other_table_name(o_c)
p_k = primary_key(params)
query = <<-SQL
SELECT *
FROM #{o_t_n}
WHERE #{p_k} = ?
SQL
row = DBConnection.execute(query, self.send(f_k))
o_c.parse_all(row)
end

Datamapper + Carrierwave store method not being called, only cache

I have a really simple sinatra app which uses datamapper and carrierwave.
I've reduced the uploader to its minimum:
class PhotoUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
end
And have one simple model that uses it:
class Photo
include DataMapper::Resource
property :id, Serial
property :caption, String
property :position, Integer
property :created_at, DateTime
property :updated_at, DateTime
mount_uploader :source, PhotoUploader
belongs_to :album
end
It happens that when I use it:
post '/admin/album/:id/photo/create' do
album = Album.get(params[:id])
photo = Photo.create(params[:photo])
album.photos << photo
album.save!
redirect '/admin/album'
end
photo is being created normally on the database and the file is being uploaded normally to the cache directory but not being moved to the final storage destination.
I've got into the carrierwave code and could verify that the cache! method is being called but not the store! method.
I've tried to use the uploader without any association to the object:
get '/lorem' do
f = File.open('test.png')
u = PhotoUploader.new
u.store!(f)
"ok"
end
Everything works as expected in this case.
==================== UPDATE ====================
I belive I've found the source of the problem. While running debugger I got to this:
gems/dm-core-1.2.0/lib/dm-core/query.rb:1014
conditions.each do |condition|
(rdb:1)
gems/carrierwave-0.6.2/lib/carrierwave/sanitized_file.rb:290
def sanitize(name)
(rdb:4)
gems/carrierwave-0.6.2/lib/carrierwave/sanitized_file.rb:299
def split_extension(filename)
(rdb:4)
gems/carrierwave-0.6.2/lib/carrierwave/sanitized_file.rb:29
self.file = file
(rdb:4)
gems/carrierwave-0.6.2/lib/carrierwave/sanitized_file.rb:269
if file.is_a?(Hash)
(rdb:4)
gems/carrierwave-0.6.2/lib/carrierwave/sanitized_file.rb:274
#file = file
(rdb:4) pp file
"/Users/jvalente/Projects/dummy/public/uploads/{:filename=>\"Screen Shot 2013-01-24 at 4.57.15 PM.png\", :type=>\"image/png\", :name=>\"photo[source]\", :tempfile=>#<File:/var/folders/jp/wm1gxk8558d36rh9vzkmx4jw0000gn/T/RackMultipart20130128-27245-105ybbf>, :head=>\"Content-Disposition: form-data; name=\\\"photo[source]\\\"; filename=\\\"Screen Shot 2013-01-24 at 4.57.15 PM.png\\\"\\r\\nContent-Type: image/png\\r\\n\"}"
This clearly is wrong since it will fail the file.is_a?(Hash) condition and get into:
#file = file
#original_filename = nil
#content_type = nil
Still, I can't understand why this happens.

Should the Applicant class "require 'mad_skills'" or "include 'mad_skills'"?

Also, what does "self.send attr" do? Is attr assumed to be a private instance variable of the ActiveEngineer class? Are there any other issues with this code in terms of Ruby logic?
class Applicant < ActiveEngineer
require 'ruby'
require 'mad_skills'
require 'oo_design'
require 'mysql'
validates :bachelors_degree
def qualified?
[:smart, :highly_productive, :curious, :driven, :team_player ].all? do
|attr|
self.send attr
end
end
end
class Employer
include TopTalent
has_millions :subscribers, :include=>:mostly_women
has_many :profits, :revenue
has_many :recent_press, :through=>[:today_show, :good_morning_america,
:new_york_times, :oprah_magazine]
belongs_to :south_park_sf
has_many :employees, :limit=>10
def apply(you)
unless you.build_successful_startups
raise "Not wanted"
end
unless you.enjoy_working_at_scale
raise "Don't bother"
end
end
def work
with small_team do
our_offerings.extend you
subscribers.send :thrill
[:scaling, :recommendation_engines, : ].each do |challenge|
assert intellectual_challenges.include? challenge
end
%w(analytics ui collaborative_filtering scraping).each{|task|
task.build }
end
end
end
def to_apply
include CoverLetter
include Resume
end
require 'mad_skills' loads the code in mad_skills.rb (or it loads mad_skills.so/.dll depending on which one exists). You need to require a file before being able to use classes, methods etc. defined in that file (though in rails files are automatically loaded when trying to access classes that have the same name as the file). Putting require inside a class definition, does not change its behaviour at all (i.e. putting it at the top of the file would not make a difference).
include MadSkills takes the module MadSkills and includes it into Applicant's inheritance chain, i.e. it makes all the methods in MadSkills available to instances of Applicant.
self.send attr executes the method with the name specified in attr on self and returns its return value. E.g. attr = "hello"; self.send(attr) will be the same as self.hello. In this case it executes the methods smart, highly_productive, curious, driven, and team_player and checks that all of them return true.

Resources