1

I have a model Document in my rails application. It has columns name, and key.

In my controller for create action, I get document name from the frontend and dynamically set one value for key with securerandom.

The implementation I have for this case is:

Document model

class Document < ApplicationRecord
    belongs_to :user
    validates :key, presence: true, uniqueness: true
end

Documents Controller

class DocumentsController < ApplicationController
    def create
        current_user.documents.create(create_document_params)
        redirect_to '/'
    end

    private
    def create_document_params
        params.require(:document).permit(:name).merge(key: "#{SecureRandom.hex(6)}#{Time.now.to_i}")
    end
end

The problem with this approach is that the dynamic key logic is in the controller which I think should be the part of the Document model.

For that, I tried using Active Record Callbacks inside the Document model with before_create. I moved the securerandom key logic to the Document model like this:

class Document < ApplicationRecord
    belongs_to :user
    validates :key, uniqueness: true


    before_create do
        self.key = "#{SecureRandom.hex(6)}#{Time.now.to_i}"
    end
end

But now my problem is, whenever I call create or new key value is always the same. But it should be randomly generated before every create call.

In the rails console

u = User.find_by(user_name: "random")
u.documents.new(name: 'Yolo 1') // key: "89c9013c191a1589398865"
u.documents.new(name: 'Yolo 2') // key: "89c9013c191a1589398865"

What I am doing wrong?

Edit: Added Gemfile :

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.6.3'

gem 'rails', '~> 6.0.3'
gem 'sqlite3', '~> 1.4'
gem 'puma', '~> 4.1'
gem 'sass-rails', '>= 6'
gem 'webpacker', '~> 4.0'
gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.7'
gem 'bcrypt', '~> 3.1.7'
gem 'bootsnap', '>= 1.4.2', require: false

group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
gem 'web-console', '>= 3.3.0'
gem 'listen', '~> 3.2'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
gem 'capybara', '>= 2.15'
gem 'selenium-webdriver'
gem 'webdrivers'
end

gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

gem "rspec-rails", "~> 4.0"

DB Migration:

class CreateDocuments < ActiveRecord::Migration[6.0]
    def change
        create_table :documents do |t|
            t.string :name,
            t.string :key, index: {unique: true}, null: false
            t.references :user
            t.timestamps
        end
    end
end
Ashish
  • 4,206
  • 16
  • 45

2 Answers2

0

I replicate your scenario on my application and a can reproduce your error. This behaviour is about the validates method. I remove that and its works.

For stay the validates, i found this other answer

# frozen_string_literal: true

class Document < ApplicationRecord
  before_create :assing_key

  belongs_to :user

  validates :key, presence: true, uniqueness: true, allow_blank: true

  private

  def assing_key
    self.key = "#{SecureRandom.hex(6)}#{Time.now.to_i}"
  end
end

I add allow_blank: true.

You can test removing only presence instead of add allow_blank also. Enjoy.

Rafael Gomes Francisco
  • 2,193
  • 1
  • 15
  • 16
  • Hi, Thanks for your answer. I tried your solution, but it is not working, the problem still persists. Is it because of my db migration? `t.string :key, index: {unique: true}, null: false` ? – Ashish May 21 '20 at 19:57
  • your migration does not affect model scope. It is a rule on database. Otherwise, try remove your `validates` method and come back with result. – Rafael Gomes Francisco May 21 '20 at 22:02
  • You can check error message on model too. `doc = Document.create(name: "foo")`. After `doc.errors` to see especifc message. – Rafael Gomes Francisco May 21 '20 at 22:05
  • The error I am getting is with key: `:key=>[{:error=>:taken, :value=>"89c9013c191a1589398865"}]`, This is because it is not creating new keys. – Ashish May 21 '20 at 22:16
  • It is very strange... Do as my example class: Move `before_create` first line inside class, create a private method do assing your key e pass as symbol to `before_create` and remove `validates` method. Make class more clear for avoid suspicious parts. – Rafael Gomes Francisco May 21 '20 at 22:35
  • I copied exactly the same code in my file, saved it, and still no luck. – Ashish May 21 '20 at 22:36
  • If you run `"#{SecureRandom.hex(6)}#{Time.now.to_i}"` on rails console, brings differents results any time? – Rafael Gomes Francisco May 21 '20 at 23:04
  • yes `"#{SecureRandom.hex(6)}#{Time.now.to_i}"` returns new value every time in console – Ashish May 22 '20 at 01:18
  • Add your `Gemfile` content on your question. – Rafael Gomes Francisco May 22 '20 at 03:08
0

I'm not sure how you was able to create a doc because you had the validation of presence: true on the key. You would get error {:key=>["can't be blank"]} when creating the doc with doc = current_user.documents.create(name: 'some name')because the validation is being executed before the call back before_create. All you need to do is to remove presence: true since you always set it before creating a new record.

tkhuynh
  • 941
  • 7
  • 15
  • That is a different case, Even without the validation when I try `current_user.documents.create(name: 'some name')` in rails console, I am getting the same key every time, that is the major concern. – Ashish May 26 '20 at 10:13