My application needs to automatically assign a number (starting at 1001) to records of a certain type (think of something like a check register). The number should be displayed as part of the input screen, but of course not modifiable.
How do I do this in Rails?
Note: this has nothing to do with the id field.
As I know you can only set one auto_increment column to a table and that needs to be defined as the primary keytoo. So this isnt an option for you because the id field is the primary key of your table.
So you need to do this application internal. Just add an integer column to the table:
add_column :table_name, :column_name, :integer
Then add a method to your model that figures out the value of the column in the last dataset created.
def self.last_<column_name>_used
return last.<column_name> unless last.nil?
return 1000
end
def self.next_<column_name>_to_use
last_<column_name>_used+1
end
Then you can use filter to automaticly assign the value of Model.next__to_use to the which should be incremended and you can also use it to get the value that should be displayed in the form. The form can easyly be disabled using jQuery.
$('#model_<column_name>').attr("disabled", true);
Hope this is what you needed!
Related
I have a table(Users). In one of its columns(configs) i added a default value ("A"=>0) through a migration. Now all the new users i create have default value of A but the old users don't. I want to backfill the default value of A for the old users using migration. How do I do that?
given:
t.jsonb "configs", default: {"B"=>7, "C"=>10, "D"=>10}
This is my existing column. Here B, C and D have different values for different Users. I want to make it into
t.jsonb "configs", default: {"B"=>7, "C"=>10, "D"=>10, "A"=>0}
where the values of B, C and D stays the same for all Users but just the default value of "A" gets added to the existing json in the column.
rails - 4.2.11
db - postgres
I have gone through some documentations but couldn't find a comprehensive answer. Any help is appreciated.
From your comments is sounds like you want to update a JSONB column to have a new set of defaults, and any existing json hashes get the new key/value pair of "A": 0 added to the current value. A migration can change the DB but you will need to do it programmatically to update the rows that have values already, especially because their values are not all the same. With that said it could be something like:
User.all.each do |u|
u.configs["A"] = 0
u.save
end
This will iterate through all of the users and set the value of "a" to zero. If no "a" exists in the hash it will add it with the value of zero without touching anything else in the JSON. If "a" already exists for a user it will be set to zero. So if you have users whose value for "a" has already changed from the default of zero you can avoid them with:
User.all.each do |u|
unless conifigs["A"] # if "A" already exists skip this
u.configs["A"] = 0
u.save
end
end
Please read https://nandovieira.com/using-postgresql-and-jsonb-with-ruby-on-rails for information on how to leverage JSONB in Rails. It is a very powerful tool if you put in the code to really get the most use out of it. Be sure and read the part about store_accessor, it would help you to do a lot more with that JSONB column.
I'm using RoR5 with PostgreSQL. I have a table with a column status. Which is of a type integer and holds three values 0, 1, 2. Those values represent three statuses allowed, not_allowed and no_tests.
I'm gonna change the logic. I want to convert two statuses allowed and not_allowed into boolean. Then I'll create a separate column for the no_tests.
Right now I have this accordance:
enum status: %i[allowed not_allowed no_tests].
How should I write a migration to have allowed as true and both not_allowed and no_tests as false in the changed column?
Actually this is two separate operations (change table structure, convert existing data), only one of would typically be done in a migration. If I were you I would first run a migration to add the new status column, and then do an update either in sql if you have easy access to the postgres console or at the rails console to recode the existing data in your new column. After you've recoded the data you can drop the old columns in another migration.
I think you will need some stuff in up method within migration.
def up
add_column :table_name, :status2, :boolean
Loop on each element of model check for status and update status2
remove_column :table_name, :status
rename_column :table_name, :status2, :status
end
You can also go with case statement in update here It will help. You can put your query in migration as well something similar to here.
You can also do one more thing like :
1. Make migration to add new column.
2. Make rake task to populate data.
3. Make one more migration to delete old column and rename newly added column.
Update : For better understanding on migration do read this article.
I could be asking this in the wrong place so go easy and point me in the right direction if I am.
I can't get my head around how changing the data type of an existing column in an existing table with existing data in Rails will effect any app I am working on.
If I have a boolean column called football. The football can be either true or false. Either, I have a football or I don't. I realise that, for example, the football can sometimes be loaned. So, I want to change the data type of the column football to be a string. The value of the string can be true, false or loaned.
How bad a time am I going to have after running the migration for this? What will happen to my existing data? What do I need to do to mitigate against messing up all my existing data? And, am I asking this question in the right place?
If you change the column type from boolean to string in rails then you have no worry because the existing data will automatically change into string. Like if you have boolean true this will automatically convert into string "true".
FYI, I checked this on my system ;)
If I were you I would do this by creating a new column, then updating everything from the old column, and then removing the old column, and renaming the new one. Also I wouldn't save a boolean as "true" or "false" either (which Rails should give you by default if you DO just change the type)... If I were you I would create an enum for this column.
First your migration:
class ChangeFootball < ActiveRecord::Migration
def change
# Add the new column. Use an integer type, so you can set up an Enum in your model
add_column :examples, :football_new, :integer, default: 0
# Set up the new column values for all your existing records:
Example.where(football: true).update_all football_new: 0
Example.where(football: false).update_all football_new: 1
# Now remove the old column:
remove_column :examples, :football
# Finally, rename the new column to the same as the old one
rename_column :examples, :football_new, :football
# Oh, and add an index:
add_index :examples, :football
end
end
Now set up the enum in your model:
enum football: { own: 0, lost: 1, misplaced: 2, loaned: 3 }
I'm using Odoo 8 and I have a problem with compute field with type is Many2One.
Here, I declared department_id:
department_id = fields.Text(
string="Department", store=True,
comodel_name="hr.department",
compute="_get_department_id"
)
And fuction of this compute field:
#api.depends('employee_id')
def _get_department_id(self):
if self.employee_id.department_id:
self.department_id = self.employee_id.department_id.name
It seems to work right now, but it's not. In view, I can see the value of department_id. But in the database, the table has no column department_id and has no value of this column.
My question is: how can I store the department_id in database?
Notes:
In the declaration of department_id, I set store=True, but it did NOT store the value of this field in database.
I did a test. I add compute_field with type Text, It works, I don't know why compute field doesn't work with type Many2One.
#api.depends('employee_id')
def _get_compute_field(self):
if self.employee_id.department_id:
self.compute_field = self.employee_id.department_id.name
compute_field = fields.Text(
string="Compute Field", store=True,
compute="_get_compute_field"
)
The store=True works.
It may be that you added the computation to the field after it was created on the database. In this case the initial computation is not triggered.
A work around is to drop the column from the table and then upgrade your module. When the field is recreated the initial values should be computed.
I have three rails objects: User, DemoUser and Stats. Both the User and the DemoUser have many stats associated with them. The User and Stats tables are stored on Postgresql (using ActiveRecord). The DemoUser is stored in redis. The id for the DemoUser is a (random) string. The id for the User is a (standard-rails) incrementing integer.
The stats table has a user_id column that can contain either the User id or the DemoUser id. For that reason, the user_id column is a string, rather than an integer.
There isn't an easy way to translate from the random string to an integer, but there's a very easy way to translate the integer id to a string (42 -> "42"). The ids are guaranteed not to overlap (there won't be a User instance with the same id as a DemoUser, ever).
I have some code that manages those stats. I'd like to be able to pass over a some_user instance (which can either be a DemoUser or a User) and then be able to use the id to fetch Stats, update them etc. Also would be nice to be able to define a has_many for the User model, so I can do things like user.stats
However, operations like user.stats would create a query like
SELECT "stats".* FROM "stats" WHERE "stats"."user_id" = 42
which then breaks with PG::UndefinedFunction: ERROR: operator does not exist: character varying = integer
Is there a way to either let the database (Postgresql), or Rails do auto-translation of the ids on JOIN? (the translation from integer to string should be simple, e.g. 42 -> "42")
EDIT: updated the question to try to make things as clear as possible. Happy to accept edits or answer questions to clarify anything.
You can't define a foreign key between two types that don't have built-in equality operators.
The correct solution is to change the string column to be an integer.
In your case you could create a user-defined = operator for varchar = string, but that would have messy side effects elsewhere in the database; for example, it would allow bogus code like:
SELECT 2014-01-02 = '2014-01-02'
to run without an error. So I'm not going to give you the code to do that. If you truly feel it's the only solution (which I don't think is likely to be correct) then see CREATE OPERATOR and CREATE FUNCTION.
One option would be to have separate user_id and demo_user_id columns in your stats table. The user_id would be an integer that you could use as a foreign key to the users table in PostgreSQL and the demo_user_id would be a string that would link to your Redis database. If you wanted to treat the database properly, you'd use a real FK to link stats.user_id to users.id to ensure referential integrity and you'd include a CHECK constraint to ensure that exactly one of stats.user_id and stats.demo_user_id was NULL:
check (user_id is null <> demo_user_id is null)
You'll have to fight ActiveRecord a bit to properly constrain your database of course, AR doesn't believe in fancy things like FKs and CHECKs even though they are necessary for data integrity. You'd have to keep demo_user_id under control by hand though, some sort of periodic scan to make sure they link up with values in Redis would be a good idea.
Now your User can look up stats using a standard association to the stats.user_id column and your DemoUser can use stats.demo_user_id.
For the time being, my 'solution' is not to use a has_many in Rails, but I can define some helper functions in the models if necessary. e.g.
class User < ActiveRecord::Base
# ...
def stats
Stats.where(user_id: self.id.to_s)
end
# ...
end
also, I would define some helper scopes to help enforce the to_s translation
class Stats < ActiveRecord::Base
scope :for_user_id, -> (id) { where(user_id: id.to_s) }
# ...
end
This should allow calls like
user.stats and Stats.for_user_id(user.id)
I think I misunderstood a detail of your issue before because it was buried in the comments.
(I strongly suggest editing your question to clarify points when comments show that there's something confusing/incomplete in the question).
You seem to want a foreign key from an integer column to a string column because the string column might be an integer, or might be some unrelated string. That's why you can't make it an integer column - it's not necessarily a valid number value, it might be a textual key from a different system.
The typical solution in this case would be to have a synthetic primary key and two UNIQUE constraints instead, one for keys from each system, plus a CHECK constraint preventing both from being set. E.g.
CREATE TABLE my_referenced_table (
id serial,
system1_key integer,
system2_key varchar,
CONSTRAINT exactly_one_key_must_be_set
CHECK (system1_key IS NULL != system2_key IS NULL),
UNIQUE(system1_key),
UNIQUE(system2_key),
PRIMARY KEY (id),
... other values ...
);
You can then have a foreign key referencing system1_key from your integer-keyed table.
It's not perfect, as it doesn't prevent the same value appearing in two different rows, one for system1_key and one for system2_key.
So an alternative might be:
CREATE TABLE my_referenced_table (
the_key varchar primary key,
the_key_ifinteger integer,
CONSTRAINT integerkey_must_equal_key_if_set
CHECK (the_key_ifinteger IS NULL OR (the_key_ifinteger::varchar = the_key)),
UNIQUE(the_key_ifinteger),
... other values ...
);
CREATE OR REPLACE FUNCTION my_referenced_table_copy_int_key()
RETURNS trigger LANGUAGE plpgsql STRICT
AS $$
BEGIN
IF NEW.the_key ~ '^[\d]+$' THEN
NEW.the_key_ifinteger := CAST(NEW.the_key AS integer);
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER copy_int_key
BEFORE INSERT OR UPDATE ON my_referenced_table
FOR EACH ROW EXECUTE PROCEDURE my_referenced_table_copy_int_key();
which copies the integer value if it's an integer, so you can reference it.
All in all though I think the whole idea is a bit iffy.
I think I may have a solution for your problem, but maybe not a massively better one:
class User < ActiveRecord::Base
has_many :stats, primary_key: "id_s"
def id_s
read_attribute(:id).to_s
end
end
Still uses a second virtual column, but maybe more handy to use with Rails associations and is database agnostic.