如何在Ruby on Rails中实现Hashids

3
我想事先向您道歉,因为我对Ruby和Rails不熟悉,所以我无法弄清楚如何在我的项目中使用hashids。该项目是一个简单的图像主机。我已经使用Base58对SQL ID进行编码并在控制器中解码。但是,我希望使URL更加随机,因此切换到了hashids。
我已将hashids.rb文件放置在lib目录中,链接在这里:https://github.com/peterhellberg/hashids.rb 现在有些混淆开始了。我是否需要在每个使用hashids.encode和hashids.decode的页面上初始化hashids?
hashids = Hashids.new("mysalt")

我发现了这篇文章(http://zogovic.com/post/75234760043/youtube-like-ids-for-your-activerecord-models),它让我相信我可以将其放入初始化程序中,但是在那之后,我仍然得到NameError(未定义本地变量或方法“hashids” ImageManager:Class)。
所以在我的ImageManager.rb类中,我有:
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

并且在我的控制器中,我有:

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

然后在我的image.html.erb视图中,我有这个:
<%
   @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
%>

现在如果我添加

hashids = Hashids.new("mysalt")

在ImageManager.rb、main_controller.rb和我的image.html.erb中,我遇到了以下错误:
ActionView::Template::Error (undefined method `id' for #<Array:0x000000062f69c0>)

总的来说,实现hashids.encode/decode并不像实现Base58.encode/decode那么容易,我对如何使其正常工作感到困惑...任何帮助将不胜感激。

3个回答

1
我正在寻找类似的东西,可以掩盖我的记录ID。我发现了act_as_hashids

https://github.com/dtaniwaki/acts_as_hashids

这个小工具能够无缝集成。您仍然可以通过id或哈希找到记录。对于嵌套记录,您可以使用方法with_hashids
要获取哈希值,请在对象本身上使用to_param,它将生成类似于ePQgabdg的字符串。
由于我刚实现了这个功能,无法确定这个工具有多有用。到目前为止,我只需要稍微调整一下我的代码。
我还给记录添加了虚拟属性hashid,以便可以轻松访问它。
attr_accessor :hashid
after_find :set_hashid

private
    def set_hashid
        self.hashid = self.to_param
    end

1
我建议将其作为gem加载,通过将其包含在您的中并运行来实现。这样可以避免在每个文件中都需要引用它,并允许您使用Bundler管理更新。
是的,您确实需要在使用它的任何地方初始化相同的盐。建议您将盐定义为常量,例如在中。
您提供的链接将注入ActiveRecord,这意味着它在其他任何地方都不起作用。我不建议采用相同的方法,因为这将需要对Rails有很高的熟悉度。
您可能需要花些时间了解ActiveRecord和ActiveModel。这将节省您大量重复造轮子的时间。 :)

即使我在每个文件中初始化了hashids方法,仍然会出现我无法解决的问题。 使用Base58类时,我可以在控制器中编码和解码而没有任何问题。 当使用hashids.decode时,它似乎不起作用,这导致我出现“未定义方法'id'错误”。看起来它没有被解码... 我该如何在控制器中正确解码id?一旦解决了这个id问题,我计划回去做railstutorials.org上的教程,更好地理解ruby on rails。 - mroth7684
@mroth7684:在 image.html.erb 中你缺少了一个闭合的大括号,这是有意为之吗? - fylooi
是的,它在页面下面。我终于让它工作了。问题是hashids返回一个数组而不仅仅是整数。我只需要调整代码,在解码后使用数组即可。 - mroth7684
@mroth7684:你能添加一下你找到的解决方案吗? - agbodike

1

在开始之前,您应该测试一下您的项目中是否包含了Hashlib。您可以在项目文件夹中运行命令rails c,并进行一个小测试:

>> my_id = ImageLink.last.id
>> puts Hashids.new(my_id)

如果不起作用,可以在gemfile中添加gem(这样更有意义)。

那么,我认为您应该在ImageLink模型中添加一个getter来获取您的hash_id。即使您不想将哈希保存在数据库中,这个哈希也有它在模型中的位置。有关更多信息,请参见虚拟属性。

记住“瘦控制器,胖模型”。

class ImageLink < ActiveRecord::Base 

    def hash_id()
        # cache the hash
        @hash_id ||= Hashids.new(id)
    end

    def extension()
        # you could add the logic of extension here also.
        ext = image.mimetype.split('/')[1]
        if ext == 'jpeg'
           'jpg'
        else
            ext
        end
    end
end

在ImageManager#save_image中更改返回值

link = ImageLink.new
link.image = image
# Be sure your image have been saved (validation errors, etc.)
if link.save 
    { status: 'success', message: link.hash_id }
else
    {status: 'failure', message: link.errors.join(", ")}
end

在你的模板中。
<%
   @imglist.each_with_index do |link, i|
    puts link.hash_id + '.' + link.extension
   end # <- I prefer the do..end to not forgot the ending parenthesis
%> 

所有这些代码都没有经过测试...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接