How to create serial number for ruby on rails? - ruby-on-rails

I want to create ticket number by serial number, eg. T-0001, T-0002, T-0003,
for ruby on rails project. How to make this?
Admission.transaction do
cus = #admission.customer
cus.inpatient_id = cus.inpatient_id || "I-%.6d" % cus.id
cus.save
end

Most rails servers are multi-threaded. Meaning many requests will be processed in parallel. You can imagine two processes trying to create a new serial number at the same point in time - duplicate ticket numbers! - not what we expect for sure.
It is better we delegate this task of creating ids to the database itself. So instead of the default auto-increment ids (1,2,3,4...), we will tell database to create ids in this format (T-0001, T-0002, ...). This can be achieved using custom sequences. I am assuming postgres database here, but should be same for mysql.
First create sequence
CREATE SEQUENCE ticket_seq;
But sequences don't allow strings so we convert them to strings and format them:
SELECT 'T-'||to_char(nextval('ticket_seq'), 'FM0000');
This will return values like T-0001, T-0002 ...
Note: We have just created a sequence, you need to tell database to use this sequence instead.
Check: https://stackoverflow.com/a/10736871/3507206

here is just sample to generate your required formatted series on range:
> (0..5).map{|e| "T-#{e.to_s.rjust(4, "0")}"}
#=> ["T-0000", "T-0001", "T-0002", "T-0003", "T-0004", "T-0005"]
If you are using PG / MySQL you can use object's id for unique number (ID- primary key is always serialize and unique)
UPDATE: as per OP's comment:
Admission.transaction do
cus = #admission.customer
cus.inpatient_id = cus.inpatient_id || "T-#{cus.id.to_s.rjust(4, "0")}"
cus.save
end

Related

How to do this computation-heavy query on millions of rows

I am using Idempotence to make sure the same Message doesn't get saved to the DB more than once. To ensure this, I need a combination of 3 columns. Instead of indexing on 3 columns where one might be null, I instead do a calculation and Digest and store that on a column that is indexed and unique.
I now need to apply this calculation to all previous messages, for which there are millions of rows.
Message.rb:
def set_unique_identifier
part_one = mm_id || SecureRandom.uuid
part_two = c_id
part_three = s_id
self.unique_identifier = Digest::SHA1.hexdigest("#{part_one}-#{part_two}-#{part_three}")
end
and then I have a migration like so:
Message.find_each.with_index do |message, index|
message.set_unique_identifier
message.save
puts "SETTING UNIQUE IDENTIFIER FOR #{index}" if index % 1000 == 0
end
however, obviously, this is going to take a really long time to compute. is there a faster way to do this using raw SQL?
Well you're going to have a certain level of computation involved no matter what the solution with a million rows. What you can do is reduce the movement of data. Postgresql's encrypt module has support for SHA1 hashing and UUID generation.
Using those you can use keep the logic in the server and execute it as single SQL statement, or multiple statements if you want to do it in chunks.
UPDATE message SET unique_identifier = encode(digest(
mm_id || gen_random_uuid() || '-' || c_id || '-' || s_id
,'sha1'),'hex');
However, what you're doing won't actually check for uniqueness because the random component means that two messages with same mm_id,c_id,s_id could be allowed.
You'd be best off using a unique database constraint. You can either create a unique index on the raw columns.
CREATE UNIQUE INDEX ON message(mm_id,c_id,s_id);
and rely on postgres to handle that. This is what I'd do first and not worry about performance issues until you've tried it that way and can measure performance.
An alternative is to create an index on a function. It will operate in about the same way:
CREATE UNIQUE INDEX ON message (encode(digest(mm_id || c_id || s_id,'sha1'),'hex'));

Find a record by bigint column with wildcard

I have an API that has a database with UPC-12 values in it. Sometimes API calls will come in with UPC-10 codes. My db upc column is bigint, so it removes the leading 0 on a UPC-12 value. That leaves the last digit as a wildcard when comparing to UPC-10.
I'd like to be able to check a UPC-10 value against records in the db to see if there's a match. Since I can't use LIKE, how do I do that?
The goal is to do something like:
def self.pull_product(upc)
upc_string = upc.to_s
if upc_string.length == 10
# product = Product.where... use a wildcard to try and match to existing record
else
product = Product.find_by_upc(upc)
end
end
This Rails 4 and Postgresql.
Just to clarify:
I might have a UPC-10 api call with a upc param like: 7618600002. My database has the UPC-12 equivalent: 76186000023. So if I just query for the param in the api call, I'll get nil.
I need a way to match the the UPC-10 param against my UPC-12 value in the database.
You need to use SQL like this:
upc between upc_string::int*10 and upc_string::int*10+9
I have no idea how to code it in Rails though.

how to use dynamic variable for symbols in ruby where statements

I dont how to accomplish this problem.
I faced with this problem 3 times and each time I put it in my todo list but even tho I tried to find a solution I couldnt.
For examples,
I m trying to create a query with dynamic variables of this example;
User.search(first_name_start: 'K')
there are 3 arguments in this example;
1)first_name - My model attribute
2)start - Query type (start/end/cont )
3)'3' - value
I was able to create dynamic ActiveRecord using static symbols but how am I suppose to make dynamic input
Thanks in advance
EDIT: ADDITIONAL INFORMATION
let me show you a some kind of pseudo-code
varArray.each_with_index |x,index|
queryString=varArray[i]+"_"+filterArray=[i] #lets say varArray[i], this will be first_name, an actual model attribute/a column in my db
#and filterArray like /start/end/with a filter type
#and finally valArray a string value like 'geo' or 'paul'
User.where(queryString valArray[i]).result
I tried to use send(variable) but that didnt help me either, so i dont how should i proceed,
This is one of a few cases where new fancy Ruby 1.9 syntax for defining hashes doesn't cut it. You have to use the traditional hashrocket (=>) that allows you to specify not only symbols, but any arbitrary values as hash keys:
column = "#{first_name}_size_#{query_type}".to_sym
User.where( column => value )
AFAIK, ActiveRecord is able to accept strings instead of symbols as column names, so you don't even need to call to_sym.

Ruby on Rails+PostgreSQL: usage of custom sequences

Say I have a model called Transaction which has a :transaction_code attribute.
I want that attribute to be automatically filled with a sequence number which may differ from id (e.g. Transaction with id=1 could have transaction_code=1000).
I have tried to create a sequence on postgres and then making the default value for the transaction_code column the nextval of that sequence.
The thing is, if I do not assign any value to #transaction.transaction_code on RoR, when I issue a #transaction.save on RoR, it tries to do the following SQL:
INSERT INTO transactions (transaction_code) VALUES (NULL);
What this does is create a new row on the Transactions table, with transaction_code as NULL, instead of calculating the nextval of the sequence and inserting it on the corresponding column. Thus, as I found out, if you specify NULL to postgres, it assumes you really want to insert NULL into that column, regardless of it having a default value (I'm coming from ORACLE which has a different behavior).
I'm open to any solution on this, either if it is done on the database or on RoR:
either there is a way to exclude attributes from ActiveRecord's
save
or there is a way to change a column's value before insert with a trigger
or there is a way to generate these sequence numbers within RoR
or any other way, as long as it works :-)
Thanks in advance.
For the moment, you might be stuck fetching and assigning the sequence in your ROR model like this:
before_create :set_transaction_code_sequence
def set_transaction_code_sequence
self.transaction_code = self.class.connection.select_value("SELECT nextval('transaction_code_seq')")
end
I'm not particularily fond of this solution, since I'd like to see this corrected in AR directly... but it does do the trick.
If you want to insert the default value in to a column in an INSERT statement, you can use the keyword DEFAULT - no quotes:
INSERT INTO mytable (col1, col2) VALUES (105, DEFAULT);
Or you could spell out the default, nextval(...) in your case. See the manual here.
A trigger for that case is simple. That's actually what I would recommend if you want to make sure that only numbers from your sequence are entered, no matter what.
CREATE OR REPLACE FUNCTION trg_myseq()
RETURNS trigger AS
$BODY$
BEGIN
NEW.mycol := nextval('my_seq');
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
CREATE TRIGGER myseq
BEFORE INSERT
ON mytable
FOR EACH ROW
EXECUTE PROCEDURE trg_myseq();
On a side note:
If you want to assign your own (non-sequential) numbers as 'sequence', I have written a solution for that in an answer a couple of days ago:
How to specify list of values for a postgresql sequence
I was still experiencing this issue with Rails7 - I could see that Rails was generating a NULL in the insert, but changing the column from integer to bigint solved it. - Rails then does not supply a value for my sequenced column and the DEFAULT nextval('number_seq') is used.

Generating unique 32-character strings in Rails 3

I have an Application model which has app_id and secret_key fields. What is the best way to generate unique app_ids?
I can use ActiveSupport:SecureRandom.hex(16) to generate an 32-char alpha-numeric string and there will probably be no other string like it. If done in this manner, should I also do a database check to see if there is a duplicate app_id or is this step unnecessary since the likelihood of that is infinitesimally small?
Or is there a better method?
Thanks!
Tim
I would always double check, just to be sure. Put a unique index on app_id and it's all set. It's hard to guarantee uniqueness
However, you could build a string that is guaranteed to be unique.
string = ActiveSupport::SecureRandom.hex(16)
append = #app.id.to_s
string = string.slice(0, string.length - append.length) + append
So the first part is random, but it always ends with the database id column, which would have to be unique.
There are also likely variations of this that keep the random looking structure, e.g. using a Caesar Cipher or a simple numeric to alphabetic cipher.
I would check first.
Here's some code I've seen in devise used when generating a unique token (modified for your example):
loop do
token = ActiveSupport::SecureRandom.hex 16
break token unless find(:first, :token => token)
end
Line 162:
https://github.com/plataformatec/devise/blob/master/lib/devise/models/authenticatable.rb

Resources