Rails ActiveRecord subclass losing primary key after create - ruby-on-rails

I have want to create and then update my subclass, but after creating it, it loses its primary_key, making any update impossible.
My (simplified) setup is the following:
Rails 4.2.9
ruby-2.1.10
Models:
class Contact < ActiveRecord::Base
attr_accessible :firstname,
:lastname
end
class SimpleContact < Contact
self.primary_key = 'id'
end
Both models sharing the contacts table (STI).
Controller:
module Public
class FilmEntriesController < PublicController
# ...
def create
logger.debug "SIM PK #{SimpleContact.primary_key}" # id
contact = SimpleContact.create()
logger.debug "SIM PK after create #{SimpleContact.primary_key}" # nil
contact.update_attributes({"firstname"=>"Moe", "lastname"=>"Test"})
end
end
The contacts table was created with this migration:
class CreateContacts < ActiveRecord::Migration
def change
create_table :contacts do |t|
t.string :firstname # | kontakt_vorname | varchar(64)
t.string :lastname # | kontakt_nachname | varchar(64)
t.timestamps
end
end
end
The full error msg is then
ActiveRecord::StatementInvalid in Public::FilmEntriesController#create
Mysql2::Error: Unknown column 'contacts.' in 'where clause': UPDATE
`contacts` SET `firstname` = 'Moe', `lastname` = 'Test', `updated_at`
= '2017-09-07 11:09:39' WHERE `contacts`.`` = 21964
Thank you for any hints!

To use STI you need a type column in the respective table in the database. Just add to your migration:
t.string "type", default: "Contact", null: false
and remove
self.primary_key = 'id'
from your model.

I guess you are having problems because you set the SimpleContact model to NOT generate a ID by itself. I'm deducting that because you are using this line of code in SimpleContact:
self.primary_key = 'id'
and because none id is being generate.
You can check whether you set or not in you migration file for the SimpleContact table.
If that is true, one way to solve it could be use this line of code in your SimpleContact model:
before_create { self.id = generator_id() }
Where the generator_id method could be any value of id you want. That way your current code would work.
Another way to solve it would be set the SimpleContact table to generate the id by itself. You can google it to see how it is done.
Good luck!

Related

Association type mismatch in Rails app

Rails 5.1
My migration file:
class CreateFwExports < ActiveRecord::Migration[5.1]
def change
create_table :fw_exports, id: :string do |t|
t.string :screen_name, index: true, limit: 16
t.string :full_name, limit: 21
t.string :location
t.timestamps
end
end
end
In my helper file, I have the following method:
def process_spreadsheet(number_of_rows, spreadsheet)
for i in 1..number_of_rows do
fw_export_record = FwExport.new(
:screen_name => spreadsheet[i][0].to_s,
:full_name => spreadsheet[i][1].to_s,
:location => spreadsheet[i][2].to_s,
)
fw_export_record.save
end
end
What this method does, is receive a spreadsheet CSV object, and iterates through the data, trying to save each row to the fw_exports table.
The first data row is:
xxxxxxxx,xxxxxxxxxx,"Nottingham, England"
I am getting the following error message:
ActiveRecord::AssociationTypeMismatch (Location(#38400060) expected, got "Nottingham, England" which is an instance of String(#10657520)):
app/helpers/fw_exports_helper.rb:21:in `block in process_spreadsheet'
app/helpers/fw_exports_helper.rb:20:in `process_spreadsheet'
app/controllers/fw_exports_controller.rb:82:in `process_parsed_spreadsheet'
When I looked at the actual MySQL table, here's what I got:
id Primary varchar(255) utf8mb4_unicode_ci No None
screen_name varchar(16) utf8mb4_unicode_ci Yes NULL
full_name varchar(21) utf8mb4_unicode_ci Yes NULL
location varchar(255) utf8mb4_unicode_ci Yes NULL
From the controller:
def fw_export_params
params.require(:fw_export).permit(:screen_name, :full_name, :location)
end
id is generated through a method defined in the concerns section
Any idea why I'm getting the error message?
Edit:
In my fw_exports.rb model, I had the following:
has_one :location
I have a locations table (and model), with the following fields:
t.string :fw_exports_id, index: true
t.string :city
t.string :state
t.string :country
When I commented out, the line in the fw_exports.rb model:
# has_one :location
I stopped getting the above mentioned error, and instead, I am now getting the following error:
NoMethodError (undefined method `each' for "0":String):
app/helpers/fw_exports_helper.rb:21:in `block in process_spreadsheet'
app/helpers/fw_exports_helper.rb:20:in `process_spreadsheet'
app/controllers/fw_exports_controller.rb:82:in `process_parsed_spreadsheet'
Same spot in code, different message.
Add |i| after the do
for i in 1..number_of_rows do |i|
Edit after response in comment:
You don't show the model but probably you have a relationship called location that is conflicting with the field.
As you have:
class FwExport < ApplicationRecord
has_one :location
and assuming that:
class Location < ApplicationRecord
belongs_to :fw_export
so you cannot define :location as a string column in CreateFwExports migration.
First you need to write another migration to remove the column from :fw_exports table:
class RemoveColumnFromFwExports < ActiveRecord::Migration[5.1]
def change
remove_column :fw_exports, :location, :string
end
end
Now rewrite the helper method that would parse the location string from csv into a Location instance and assign it into the FwExport instance:
def process_spreadsheet(number_of_rows, spreadsheet)
1.upto(number_of_rows) do |i|
fw_export_record = FwExport.new(
screen_name: spreadsheet[i][0].to_s,
full_name: spreadsheet[i][1].to_s,
)
fw_export_record.save
# now create the location and associate it to fw_export_record
location = find_or_create_location(spreadsheet[i][2].to_s)
location.fw_exports_id = fw_export_record.id
location.save
end
end
private
def find_or_create_location(s)
city, country = s.split(',').map(&:strip)
Location.find_or_create_by!(city: city, country: country)
end

In Rails, how do I create a migration that will change the type of my primary key?

I’m using Rails 4.2.7 with PostGres. I have created several tables, all of which have numeric primary keys. Below is an example of one of my Rails migrations
class CreateMyObjects < ActiveRecord::Migration
def change
create_table :my_objects do |t|
t.string :name
t.date :day
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
I do not have any data in this table, but I do have several tables that link to it through foreign keys. I want to change the primary key from being a numeric primary key to a GUID (UUID) because I’m going to have a situation where data gets created in two different databases and I don’t want there to be primary key collisions when I combine the data. How do I create a migration that will change the primary key’s type from being numeric to my UUID type and how do I update all the foreign keys that link to the table?
Thanks, - Dave
class ChangePrimaryKey < ActiveRecord::Migration
def change
remove_column :my_objects, :id # remove existing primary key
add_column :my_objects, :uuid, :string
execute "ALTER TABLE my_objects ADD PRIMARY KEY (uuid);"
end
end
class MyObject < ActiveRecord::Base
self.primary_key = :uuid
end
As to auto incrementing - no, Rails won't increment your string id for you, so you'll have to take care of it on your own.
You can use a before_create callback in the model:
before_create :generate_uuid
private
def generate_uuid
loop do
self.uuid = SecureRandom.base64(16) # or other way to generate uniq string
break unless self.class.find_by(uuid: uuid) # make sure you don't repeat the uuid
end
end
You can also add constraints to the primary_key column, like not null constraint, uniqueness, length etc

How to override the default primary key column in ruby on rails 4.0.+?

I have an already existing database schema with tables that have a string column as primary key and also some tables with more than one columns as key. I would like to map this schema in rails, but I dont know how to override the default primary key (a column id created by the rails framework).
You can override the primary key like this
class Book < ActiveRecord::Base
self.primary_key = 'author'
end
I don't know what you're trying to do. It's a mistake altering primary key in Rails.
But for that matter try to do it in your migration.
class Foos < ActiveRecord::Migration
def self.up
create_table :foos, :id => false do |t|
t.string :my_id
t.timestamps
end
end
end

Creating custom primary keys in Rails application

In my Rails application which works with Oracle database (version 11g) I need primary keys to be Strings, not Integers. For example, in my Product model I need primary keys like "p001", "p002" etc.
class AddProductWithDifferentPrimaryKey < ActiveRecord:Migration
def change
create_table :table, id: false do |t|
t.string :id, null: false
# other columns
t.timestamps
end
execute "ALTER TABLE table ADD PRIMARY KEY (id);"
end
end
Don't forget to also add this line to your table model so rails knows how to find your new primary key!
class Product < ActiveRecord::Base
self.primary_key = :id
# rest of code
end
Hope this helps. And credit should go to
A K H
For more information you can check out his as well as other answers. primary key info

Rails many to many update and create

I'm interested in creating and updating a row in table without a primary key. My table has 3 columns - person_id, year and salary. I understand that I should use has_and_belongs_to but I'm having problems understanding how to implement my create and update methods and my form.html file. Can anyone help explain this to me, perhaps with a simple example of how to do it?
has_and_belongs_to_many example
# category model
class Category < ActiveRecord::Base
has_and_belongs_to_many :users
end
# user model
class User < ACtiveRecord::Base
has_and_belongs_to_many :categories
end
join table look like
class CreateCategoriesUsersJoinTable < ACtiveRecord::Migration
def change
create_table :categories_users, :id => false do |t|
t.integer :category_id
t.integer :user_id
end
end
end
now you can accessing your information
$ User.categories
$ Category.users
$ user = User.first
$ user.categories
$ category = Category.first
$ category.users
Add a primary key, and ignore it. You can add a unique index on (person_id, year) to simulate a PK constraint, but ActiveRecord heavily relies on having ids for its instances.

Resources