Can't access uuid field in rails migration - ruby-on-rails

I have a table called 'products' (model is Product) and I can't access the :uuid attribute when running in migration context. The migration itself does not change the structure but accesses and creates new objects to populate the DB.
This is a snippet of the schema.rb prior to the migration:
create_table "products", force: :cascade do |t|
t.string "title"
t.string "description"
t.string "price"
t.uuid "uuid"
end
The Product object is defined as follows:
class Product < ActiveRecord::Base
end
Now when running in rails console / code this works fine:
p = Product.create!(:uuid => xxx)
puts p.uuid # => xxx
puts p.inspect # => Product(title: string, description: string, price: string, uuid: uuid)
However while running in the migration context - the same code raises an exception:
p = Product.create!(:uuid => xxx)
puts p.uuid # => undefined method `uuid' for <Product:0x007fea6d7aa058>/opt/rubies/2.2.0/lib/ruby/gems/2.2.0/gems/activemodel-4.2.3/lib/active_model/attribute_methods.rb:433
puts p.inspect # => Product(title: string, description: string, price: string)
The uuid attribute is missing! What's wrong?

put
Product.reset_column_information
before your Product.create line.

The schema of the model is generally refreshed after the migration. Therefore, even if the uuid field is created, the model doesn't have knowledge of it yet.
You can force a refresh by using
Product.reset_column_information
However, the issue in your code reveals you are probably using the migration feature to create records inside the migration itself. This is generally not recommended as the migrations are designed to change the schema of your database, not the data.
You should use create a specific rake task to modify the data and run the task after the migration is completed, for example from the console.

Related

Why integer is automatically converted to string when updating/saving in DB?

I have a column that is supposed to be a string. In schema.rb it looks something like this:
create_table "users", force: :cascade do |t|
t.string "login_token", default: "xxxxx", null: false
end
But if I try to update the column, the DB accepts integers and automatically converts them to strings for some reason.
user = User.first.update(login_token: 1)
#=> true
user.login_token
#=> "1"
Why is this, and is it possible to add any restrictions to the DB or validations in Rails to prevent this kind of typecasting?
Works the other way around too. If you have an integer column and pass a string, rails tries to convert it. Very useful when you, say, create a record from an html form (most everything comes as a string from the browser).
user_params # => { "age" => "20" }
u = User.create(user_params)
u.age # => 20
It's a feature/convention. I wouldn't fight it, if I were you.

Trying to save JSON data to database

I'm not sure what I'm missing, but the following code below isn't saving to my database. I'm using rails 5.1.4 and ruby 2.4.1. I have no controller or views and using Mysql if that's of any help.
Model
class Agent < ApplicationRecord
json = JSON.parse('{"Agents":[{"firstName":"John","lastName":"Smith","id":"57fa5f47-8851-11e7-b391-02cbcf8dd991"},{"firstName":"Alice","lastName":"Thompson","id":"77eccb07-101d-11e7-83be-02e5025d7d75"}]}')
json['Agents'].each do |data|
Agent.create(
id: data['id'],
first_name: data['firstName'],
last_name: data['lastName']
)
end
end
Schema
create_table "agents", id: :string, limit: 36, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.string "first_name"
t.string "last_name"
end
As Gokul M pointed out, firstName and lastName need to be snake-cased to match your schema. However, your code still will not work, because :id is a protected attribute in ActiveRecord models. Meaning that it can’t be mass-assigned. You can however assign it normally. i.e model.id=foo
So you could try something like
json['Agents'].each do |data|
agent = Agent.new
agent.id = data['id']
agent.first_name = data['firstName']
agent.last_name = data['lastName']
agent.save
end
Though, I haven’t tested this code.

returning specific values extracted from an hstore (Postgres + hstore_translate gem)

I am using hstore_translate within a Rails4 project to handle
my I18n needs.
Assume I have the following model:
class Thingy < ActiveRecord::Base
translates :name
end
with table defined in a migration as
create_table :thingies do |t|
t.hstore name_translations
end
add_index ::thingies, :name_translations, using: :gin
In my Ruby code I wish to retrieve a list of the all the names and ids for Thingies with a name in a specific locale.
Previously, before my Thingy had a localised name, I could just do
thingies = Thingy.order(:name).pluck(:id, :name)
Now I am doing
thingies = Thingy.where("name_translations ? 'en'").order("name_translations -> 'en'").to_a.map do |t|
{id: t.id, name: t.name}
end
But I can't help feeling there's a way I can better leverage Postgres to do this all in one line of code without invoking a loop in Ruby.
I've worked it out with a bit of trial and error.
thingies = Thingy.where("name_translations ? 'en'")
.order("name_translations -> 'en'")
.pluck(:id, ("name_translations -> 'en'"))
does the job.
It's not very DRY but it works.

Rails + UUID generated schema assumes UUID is an integer rather than a string

I’m trying to use a UUID as the primary key for a Rails app, and am running into problem after problem.
I am specifying in migration this:
create_table :users, :id => false do |t|
then this:
execute("ALTER TABLE users ADD PRIMARY KEY(uuid)")
In my user model:
set_primary_key "uuid"
Using the UUID tools to generate the UUID.
This is all working great, the problem I currently have is that the schema.rb that gets generated looks like this:
create_table "users", :primary_key => "uuid", :force => true do |t|
Which assumes that the primary key column is an 11 character integer rather than a 36 character string, so running migrations produces a correct database, but test database is generated incorrectly, and if I were to run rake db:schema:load, it would fail as well...
Need to figure out how to override the way that schema.rb assumes that if there’s a primary key column that it will be an integer....
I think the best approach is to switch from managing your schema in Ruby (schema.rb) to managing it in SQL (development_structure.sql).
To do this:
In application.rb set config.active_record.schema_format = :sql
Delete your schema.rb file.
Every time you run rake db:migrate, run rake db:dump:structure as well. That will dump your schema to db/development_structure.sql. When you run rake db:test:prepare it will now use the development_structure.sql file instead of the scheam.rb file.
You can read more about this in section 6.2 of the migrations guide (http://guides.rubyonrails.org/migrations.html).
We have been using UUID as the primary key for a long time (currently with Rails 3.0.0), but so far have not found a way to make Schema dumper understand that there is no integer id.
So our solution has been fixing the schema by hand, when needed. For example always after rake db:migrate. :D
We just change the line
create_table "people", :force => true do |t|
to
create_table "people", :id => false, :force => true do |t|
t.string "id", :limit => 22, :null => false
It's rather annoying but then everything works. So the problem is not in Rails not allowing UUID primary keys, but schema dumper not understanding those.
I ran into this problem. As far as i can tell, it's impossible to override rails' requirement that the PK be an integer. What i did to bypass this was add a key to unique on the database and then setup my default scopes on each model to search via my unique string instead of the regular integer

Change starting id number

I have an 'Account' model in Rails with its corresponding 'accounts' table in the database. If I wipe the database and start over, the 'account_id' field will always start at 1 and count up from there. I would like to change the starting number, so that, when the very first account is created in a fresh database, the 'account_id' is, say, 1000. Is there a way to do that in Rails, or do I need specialized database-dependent SQL code?
For the sake of illustration, here is a simplified version of my 'accounts' table:
create_table "accounts", :force => true do |t|
t.string "email", :null => false
t.string "crypted_password", :null => false
t.string "name", :null => false
t.boolean "email_verified", :default => false
end
for PostgreSQL:
execute("ALTER SEQUENCE accounts_id_seq START with 1000 RESTART;")
see https://www.postgresql.org/docs/current/static/sql-altersequence.html
You'll need to do some specialized database-dependent SQL to get this functionality.
If you're using MySQL, you can add the following code to your migration after the create_table code:
execute("ALTER TABLE tbl AUTO_INCREMENT = 1000")
For sqlite
sequences are stored in the table sqlite_sequence (name,seq)
Check first if the sequence already exists?
select name,seq from sqlite_sequence where name = 'accounts'
if sequence.empty?
insert into sqlite_sequence(name,seq) values('accounts', 1000);
else
update sqlite_sequence set seq = 1000 where name = 'accounts';
A pure Ruby, database-independent approach could be:
class MyModel
before_create do
self.id = [1000, (self.class.maximum(:id) || 0) + 1].max if self.id.nil?
end
end
When you're creating lots of records at once, this may not perform so well though.
Another possible concept might be to simply use a start_at variable in your model file?
Such as define a base number such as start_at = 53131 and then...
Make an accessor method (could call it "key") which adds your start_at number to your database's real ID before returning it.
And you could make a attr writer method that subtracts the start_at before saving the key, that may not even be necessary depending on your implementation.
Example in pseudo-code so bear with me.
class FakeModel
attr_accessible :name
start_at = 53121
def self.find_by_key(key)
find_by_id(key-start_at))
end
def key
(self.id+start_at)
end
end
Not sure how practical this is or if it would even work 100% but at least you wouldn't have to modify the database to handle it.
in SQL Server:
execute('DBCC CHECKIDENT (accounts, reseed, 1000)')
In my case, the development environment and the production environment are using different type of database.
This code block will run the relevant execution accordin to DB type - just put it in the relevant migration:
puts 'Migration trys to set initial account ID to adapter:' + ActiveRecord::Base.connection.adapter_name
case ActiveRecord::Base.connection.adapter_name
when 'MySQL'
execute('ALTER TABLE accounts AUTO_INCREMENT = 1000')
when 'SQLServer'
execute('DBCC CHECKIDENT (accounts, reseed, 1000)')
when 'SQLite'
begin
execute('insert into sqlite_sequence(name,seq) values(\'accounts\', 1000);')
rescue
puts 'insert error... updating'
end
execute('update sqlite_sequence set seq = 1000 where name = \'accounts\';')
else
puts "cant recognize the database"
end

Resources