Does validates_uniqueness_of work for generating a random token? - ruby-on-rails

If I want to generate a random string for a token in rails, can I use validates_uniqueness_of on it? Given that this isn't something a user will input or get an error back for it needs to be unique straight away. Or am I just being stupid?

how about:
class Token < ActiveRecord::Base
validates_uniqueness_of :random_key
before_validation_on_create :create_key_until_valid
def create_key_until_valid
self.random_key = rand.to_s.slice(2,10)
while Token.find_by_random_key(self.random_key)
self.random_key = rand.to_s.slice(2,10)
end
end
end

validates_uniqueness_of will just make sure that the attribute is unique - it won't generate the value.
I'd use before_validation to create the unique value.

Related

Setting default username in Rails

I am using devise gem for user management. So, I have a User model.
What I want to be able to do is have a username column in the User model. By default, I want to be able to set the default username for users as 'user'+id where id is unique for every user and a column in User model.
I want to be able to do something like:
class AddColsToUser < ActiveRecord::Migration[6.1]
def change
add_column :users, :username, :string, default: 'user'+id
end
end
Is that possible? Thanks for reading :))
I would recommend using a before_validation callback to create a new username. You can expand this a bit to keep retrying in case of failure, but this should hopefully help you get started.
Make sure to add a unique constraint or validation as well!
before_validation :set_username
private
def set_username
return if user.blank?
username = "{user}_#{rand(100)}"
end
Another option if you just want a prettier parameter in your url's is to override the to_param method in your user model. This will give you a friendly url like "1-my-name", but when parsed to an integer will just be "1" so rails will still find it when doing lookups.
def to_param
[id, name.parameterize].join("-")
end

setting mandatory field automatically in Rails from Model callback, when and how?

I have this model which has a mandatory field which needs to be automatically set just before save. I'm struggling with the correct way to implement this:
build the logic in the controller before the save (and have validates rule in model)
build the logic in a before_save callback and have validates rule in model, but this seems to late in the flow? I do get validation errors this way.
build the logic in a before_save callback and don't define validation for this particular field
do it any of the ways above and don't assign a validates rule for the particular field
I was working on 2 since this seems like the correct way to implement this. Was considering the usage of before_validation, but I don't know what would happen when my other fields don't get validated... this could cause double assignment of the same value..
code for 2 which gives a basic idea of what I'm trying to achieve:
#category.rb
class Category < ActiveRecord::Base
before_create :set_position_number
def set_position_number
highest = Category.maximum(:position)
self.position = highest.to_i + 1
end
end
I'm struggling with the correct way to implement this
The most efficient way will be to use an ActiveRecord callback hook, such as you've posted:
#app/models/category.rb
class Category < ActiveRecord::Base
before_create :your_action
private
def your_action
#fires before create
end
end
but this seems to late in the flow
As mentioned in the comments, you can see the order of the callbacks (and thus their order in the flow):
Thus, if you want to populate some data before you validate, and then validate that data, you'll be best using the before_validation callback:
#app/models/category.rb
class Category < ActiveRecord::Base
before_validation :set_position_number, on: :create
validates :position, ______________
private
def set_position_number
highest = Category.maximum(:position)
self.position = highest.to_i + 1
end
end
Remember, a Rails model just populates certain attributes which are then to be either saved to the db, or validated. Rails does not care where those attributes come from; populating them before_validation is a good a source as the controller.
If you are setting a value automatically and don't take user input, you don't need validation. Write a unit test.
If the field is something like a position value, then you should indeed set it in a before_create callback.

In Rails, is it possible to repair model validation errors inside the model?

Let's say a model catches a validation error, usually this is handled by the controller, but is it possible to handle it automatically by the model?
Practically I want to generate a unique id uid for each Note, the model looks like this:
class Note < ActiveRecord::Base
validates_uniqueness_of :uid
# ... some code to generate uid on after_initialize
end
The closest I got is:
class Note < ActiveRecord::Base
validates_uniqueness_of :uid
# ... some code to generate uid on after_initialize
after_rollback :repair
protected
def repair
if self.errors[:uid].size > 0
self.uid = generate_uid
end
self.save # Try again
end
end
Some immediate problems with my solution: (1) The model instance still has errors that the controller can see, I'm not sure how to clear the errors. (2) The repair method is recursive.
While I'm sure there is a way to catch and handle the errors in the model (maybe the after_validation callback could be of use), perhaps you can avoid the issue in this case by ensuring that the uid you generate is unique when you create it.
Ryan Bates offered this method for generating unique tokens in a RailsCast:
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
With the use of a before_create callback, i.e. before_create { generate_token(:uid) }, each model will have a unique id.
All this said, #Beerlington raises a really good point about UUIDs.
Update: Note that the method given is expecting to be defined in a User model. For your example, you'd want to change it to ...while Note.exists?....
I would use a true UUID that is guaranteed to be unique, and not add the overhead to your model. Having a uniqueness validation in the model adds some overhead because it has to hit the database to figure out if something exists, and it's still not even guaranteed.
Check out this Ruby project to generate UUIDs: https://github.com/assaf/uuid/

Rails - validate Float type when it's still a String

I have User model with :balance field (it is float type). I want to write my own validation for this field, but i want to get access to this value when it's still a string and Rails didn't yet converted it to the float. I mean, i want to do something like this:
class User < ActiveRecord::Base
validate :check_balance
def check_balance
if balance.is_a?(String)
# do my validation, add errors and etc.
end
end
end
How can i do that?
P.S. validates_format_of is not answer in my current situation.
<attribute>_before_type_cast returns the original value for attribute:
def check_balance
if balance_before_type_cast.is_a?(String)
// do my validation, add errors and etc.
end
end

Rails how to set a temporary variable that's not a database field

For my app, I have different signup entry points that validate things differently.
So in the main signup, nothing is required except for the email and password field. In an alternative signup field, many more are required. So in the user model I have
validate_presence_of :blah, :lah, :foo, :bah, :if => :flag_detected
def flag_detected
!self.flag.nil?
end
I want to set that flag through the controller. However that flag isn't a database field. I'm just wondering if this is achievable in Rails or there is something wrong with the way that I am thinking about this? If so, what's the best way to achieve this? Thanks.
What you need is attr_accessor
class User < ActiveRecord::Base
attr_accessor :flag
attr_accessible :flag # if you have used attr_accessible or attr_protected else where and you are going to set this field during mass-assignment. If you are going to do user.flag = true in your controller's action, then no need this line
end
basically attr_accessor :flag create the user.flag and user.flag = ... methods for your model.
and attr_accessible is for mass-assignment protection.
Following up on the best practice debate:
Create a method that does what you want. I.e. save_with_additional_validation. This is much more clear and self-documenting code and works the same way. Just call this method instead of save()
It seems like you need to define setter method
class User < ActiveRecord::Base
attr_accessible :flag
def flag=(boolean)
boolean
end
end

Resources