mroth7684 mroth7684 - 4 months ago 9
Ruby Question

How do I implement hashids in ruby on rails

I will go ahead and apologize upfront as I am new to ruby and rails and I cannot for the life of me figure out how to implement using hashids in my project. The project is a simple image host. I have it already working using Base58 to encode the sql ID and then decode it in the controller. However I wanted to make the URLs more random hence switching to hashids.

I have placed the hashids.rb file in my lib directory from here: https://github.com/peterhellberg/hashids.rb

Now some of the confusion starts here. Do I need to initialize hashids on every page that uses hashids.encode and hashids.decode via

hashids = Hashids.new("mysalt")


I found this post (http://zogovic.com/post/75234760043/youtube-like-ids-for-your-activerecord-models) which leads me to believe I can put it into an initializer however after doing that I am still getting NameError (undefined local variable or method `hashids' for ImageManager:Class)

so in my ImageManager.rb class I have

require 'hashids'

class ImageManager
class << self
def save_image(imgpath, name)

mime = %x(/usr/bin/exiftool -MIMEType #{imgpath})[34..-1].rstrip
if mime.nil? || !VALID_MIME.include?(mime)
return { status: 'failure', message: "#{name} uses an invalid format." }
end

hash = Digest::MD5.file(imgpath).hexdigest
image = Image.find_by_imghash(hash)

if image.nil?
image = Image.new
image.mimetype = mime
image.imghash = hash
unless image.save!
return { status: 'failure', message: "Failed to save #{name}." }
end

unless File.directory?(Rails.root.join('uploads'))
Dir.mkdir(Rails.root.join('uploads'))
end
#File.open(Rails.root.join('uploads', "#{Base58.encode(image.id)}.png"), 'wb') { |f| f.write(File.open(imgpath, 'rb').read) }
File.open(Rails.root.join('uploads', "#{hashids.encode(image.id)}.png"), 'wb') { |f| f.write(File.open(imgpath, 'rb').read) }
end

link = ImageLink.new
link.image = image
link.save

#return { status: 'success', message: Base58.encode(link.id) }
return { status: 'success', message: hashids.encode(link.id) }
end

private

VALID_MIME = %w(image/png image/jpeg image/gif)
end
end


And in my controller I have:

require 'hashids'

class MainController < ApplicationController
MAX_FILE_SIZE = 10 * 1024 * 1024
MAX_CACHE_SIZE = 128 * 1024 * 1024

@links = Hash.new
@files = Hash.new
@tstamps = Hash.new
@sizes = Hash.new
@cache_size = 0

class << self
attr_accessor :links
attr_accessor :files
attr_accessor :tstamps
attr_accessor :sizes
attr_accessor :cache_size
attr_accessor :hashids
end

def index
end

def transparency
end

def image
#@imglist = params[:id].split(',').map{ |id| ImageLink.find(Base58.decode(id)) }
@imglist = params[:id].split(',').map{ |id| ImageLink.find(hashids.decode(id)) }
end

def image_direct
#linkid = Base58.decode(params[:id])
linkid = hashids.decode(params[:id])

file =
if Rails.env.production?
puts "#{Base58.encode(ImageLink.find(linkid).image.id)}.png"
File.open(Rails.root.join('uploads', "#{Base58.encode(ImageLink.find(linkid).image.id)}.png"), 'rb') { |f| f.read }
else
puts "#{hashids.encode(ImageLink.find(linkid).image.id)}.png"
File.open(Rails.root.join('uploads', "#{hashids.encode(ImageLink.find(linkid).image.id)}.png"), 'rb') { |f| f.read }
end

send_data(file, type: ImageLink.find(linkid).image.mimetype, disposition: 'inline')
end

def upload
imgparam = params[:image]

if imgparam.is_a?(String)
name = File.basename(imgparam)
imgpath = save_to_tempfile(imgparam).path
else
name = imgparam.original_filename
imgpath = imgparam.tempfile.path
end

File.chmod(0666, imgpath)
%x(/usr/bin/exiftool -all= -overwrite_original #{imgpath})
logger.debug %x(which exiftool)
render json: ImageManager.save_image(imgpath, name)
end

private

def save_to_tempfile(url)
uri = URI.parse(url)

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.start do
resp = http.get(uri.path)
file = Tempfile.new('urlupload', Dir.tmpdir, :encoding => 'ascii-8bit')
file.write(resp.body)
file.flush
return file
end
end
end


Then in my image.html.erb view I have this:

<%
@imglist.each_with_index { |link, i|
id = hashids.encode(link.id)
ext = link.image.mimetype.split('/')[1]
if ext == 'jpeg'
ext = 'jpg'
end
puts id + '.' + ext
%>


Now if I add

hashids = Hashids.new("mysalt")


in ImageManager.rb main_controller.rb and in my image.html.erb I am getting this error:

ActionView::Template::Error (undefined method `id' for #<Array:0x000000062f69c0>)


So all in all implementing hashids.encode/decode is not as easy as implementing Base58.encode/decode and I am confused on how to get it working... Any help would be greatly appreciated.

Answer

I would suggest loading it as a gem by including it into your Gemfile and running bundle install. It will save you the hassle of requiring it in every file and allow you to manage updates using Bundler.

Yes, you do need to initialize it wherever it is going to be used with the same salt. Would suggest that you define the salt as a constant, perhaps in application.rb.

The link you provided injects hashids into ActiveRecord, which means it will not work anywhere else. I would not recommend the same approach as it will require a high level of familiarity with Rails.

You might want to spend some time understanding ActiveRecord and ActiveModel. Will save you a lot of reinventing the wheel. :)