Pergunta

I have a post model in my rails project,

First, I think that exposing the real db id is NOT good idea, am I right?

If I'm right, I hope to generate a unique progressively increasing number, use it in url, just link below line:

https://hostname.com/posts/unique_number

I searched in SO, from below link, SecureRandom can generate random token, but it can't generate progressively increasing number.

Best way to create unique token in Rails?

So, how to generate progressively increasing number before creating new record.

Thanks a lot.

Foi útil?

Solução

Depending on the frequency of posts created, you can use Time from epoch for a (almost) unique number which is progressively increasing, either in seconds:

Time.now.to_i

or in milliseconds:

(Time.now.to_f * 1000).to_i

Caveats:

  1. if you have a lot of traffic there is a chance that IDs will be duplicated - if unique is checked on insert, you can recreate the id on uniqueness failure.
  2. in a multi-machine topology, your machines might not be synchronized to the same time, which might create a situation where IDs will be created which are smaller than existing ones.

Outras dicas

You could (if it's satisfactory) create a unique number that's somewhere between 1 and 10_000 after the last number generated.

So that the 'to_param' can work properly, your token should be a String.

Migrate to create the column

add_column :posts, :unique_number, :string

Then modify the model...

class Post < ActiveRecord::Base
  before_create :new_unique_number

  def new_unique_number 
    last_number = Post.order('unique_number DESC').first.unique_number || '1'
    self.unique_number = (last_number.to_i + rand(1, 10_000)).to_s
  end

  #.....
end

If you use a Post model, I suppose we speak of blog articles or something ?

The question is : do you really need something incremental, or just something unique ?

If what matters is that your id is unique, you can use anything you want as id, not just an integer.

In model

For example, let say your Post model has a validation on title uniqueness. You can decide its title will be used in urls :

class Post
  validates_uniqueness :title

  def to_param
    title
  end
end

The #to_param method allows to decide which attribute will be used for url generation :

post = Post.create( title: 'my_post' )
post_path( post ) # => /posts/my_post

Note that if you want to use a human string like title, you probably will want to sanitize it in a before save callback and set it in an other field, like "url_title" (to avoid spaces and ugly looking characters in urls).

In controller

In controller, you do just as usual, getting the id in params[:id] :

class PostsController < ApplicationController
  before_action :find_post

  def show
  end

  private

  def find_post
    @post = Post.where( title: params[ :id ] ).first or redirect_to '/'
  end
end

First, I think that exposing the real db id is NOT good idea, am I right?

It depends.

If your object IDs come from a predictable sequence, that means someone using your application can guess likely valid IDs, and add them to URLs, POST requests etc.

If the objects are accessed read-only and publicly, then there is no problem at all just exposing the database as it was generated.

If you need to secure access, there is more than one way to do so. If you have secure authorisation in your web application, and can tell whether a current user should be able to see an object or not, then you already have sufficient security. Creating unguessable object ids (e.g. uuids) does not really help you. Using some other sequence to "hide" the id does not really add any security and could be a lot of work. Although you could go a step further and generate friendly URLs derived from other content (title), and that would be a similar effort, that is generally not a security concern.

You should only worry about exposing your DB ids directly if that would give a direct link via the API where unauthorised users could guess other IDs and access or change things they were not supposed to. If you cannot restrict access direct via the ID due to other constraints in the system, the answer is not to generate some other direct, predictable index to the same object (which will have the same problems with people being able to guess it), but do one of:

1) provide indirect access - e.g. user can only choose from objects that you have pre-selected for them, and you assign IDs to those choices instead, and only accept those choice_ids via your API, never the database ids of the objects being chosen.

2) generate unguessable IDs, possibly temporary, like SecureRandom.uuid

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top