Cannot update record with ActiveRecord (no rails) - ruby

Hello I have the following table in my PostgreSQL database:
CREATE TABLE user_logins (token VARCHAR(512) NOT NULL, user_id UUID UNIQUE NOT NULL, create_date TIMESTAMPTZ DEFAULT clock_timestamp(), last_visit TIMESTAMPTZ DEFAULT clock_timestamp(), expire TIMESTAMPTZ DEFAULT clock_timestamp() + INTERVAL '30 DAYS');
For which I have the following ActiveRecord class
require "active_record"
class UserLogin < ActiveRecord::Base
end
I want to update the last_visit column, which I try to do like this:
def update_last_visited(user_token)
user_login = UserLogin.find_by(token: user_token)
if user_login == nil
return false
end
user_login.update(last_visit: Time.now.utc)
end
However, I get this error:
2023-02-19 11:55:21 - ActiveRecord::StatementInvalid - PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
LINE 1: ...logins" SET "last_visit" = $1 WHERE "user_logins"."" IS NULL
As I have understood ActiveRecord if I have the object ActiveRecord should be able to update the record, or have I miss understood something?
Should I instead do an update based on a where?
A minimal reproducible example:
require "active_record"
require "yaml"
require 'securerandom'
db_config_file = File.open("database.yml")
db_config = YAML::load(db_config_file)
ActiveRecord::Base.establish_connection(db_config)
class UserLogin < ActiveRecord::Base
end
def update_last_visited(user_token)
user_login = UserLogin.find_by(token: user_token)
if user_login == nil
return false
end
user_login.update(last_visit: Time.now.utc)
end
user_login = UserLogin.new
user_login.token = "wompas"
user_login.user_id = SecureRandom.uuid
user_login.save
update_last_visited(user_login.token)
The database.yml file:
adapter: 'postgresql',
host: 'localhost',
port: 5432
username: 'user',
password: 'password',
database: 'dbname'
Note: The project does not utilise Rails, we only use ActiveRecord for its ORM capabilities.

Your UserLogin doesn't have a primary key. But ActiveRecord expects tables to have a primary key column named id per default. See Active Record Basics / Schema Conventions in the official Rails Guides.
In your example, it feels like the user_id can be used as a primary key, because is cannot be NULL and it must be unique. You can try to configure user_id to be used by as primary key by ActiveRecord like this.
class UserLogin < ActiveRecord::Base
self.primary_key = :user_id
end
But in general, I suggest not fighting the default conventions and would just add a bigint id column to that table. While it is possible to configure custom primary key columns, it makes everything a bit more complex because you will need to remember those custom setting in other use cases – like, for example, when defining association.

Related

How to implement Application Record in ruby without rails

I've been looking at this repository
https://github.com/stungeye/ActiveRecord-without-Rails to understand how can I implement activerecord without rails.I got some problems. At first I got this error when I tried to run this class:
require 'active_record'
ActiveRecord::Base.establish_connection(adapter: 'mysql2', database: 'rbuserroom')
# Can override table name and primary key
class User < ActiveRecord::Base
self.primary_key = 'user_id'
def initialize(id, email)
#user_id = id
#user_email = email
#user_room
end
def create()
self.save
end
# accessor get and set method
attr_accessor :user_room
attr_reader :user_id, :user_email
end
usr = User.new(1, "user#user")
usr.create()
but I got this error:
Traceback (most recent call last):
1: from -:25:in `<main>'
/home/felipe/.rbenv/versions/2.7.5/lib/ruby/gems/2.7.0/gems/activerecord-6.1.4.1/lib/active_record/inheritance.rb:52:in `new': wrong number of arguments (given 2, expected 0..1) (ArgumentError)
it seems that active record doesn't accept the parameters in the creation of the class, in fact after that i noticed that the classes in this example don't contain anything inside, how would active record define the columns of the tables?
i'm used to java jpa and springboot that i have to define all the attributes of the class.
besides i don't know if the active record is really working.
I just want that when I create a new user with my user class, the information persists in the database as an insert, or that it updates when I make a change to my object attribute value.
With ActiveRecord you don't need to specify the column names. It detects them from the DB.
You can just write:
require 'active_record'
ActiveRecord::Base.establish_connection(adapter: 'mysql2', database: 'rbuserroom')
class User < ActiveRecord::Base
self.primary_key = 'user_id'
end
usr = User.create(user_id: 1, user_email: "user#user")
You can read more about creating models in the docs. Especially in 3 Creating Active Record Models

Ruby (without rails) using activerecord, insert into does not work

I need to insert a new record into an existing table in a databse. I tried both approaches below:
class UserDetail < ActiveRecord::Base
def self.add_new_user
new_user = UserDetail.new
new_user.first_name = 'Joe'
new_user.last_name = 'Smith'
new_user.user_id = 'TEST'
new_user.save
end
def self.add_new_user_2
UserDetail.create(user_id: 'TEST', first_name: 'Joe',
last_name: 'Smith')
end
However, both approaches give me the error below:
ActiveRecord::StatementInvalid: OCIError: ORA-00926:
missing VALUES keyword: INSERT INTO "USER_DETAIL" DEFAULT VALUES
What am I missing? Please share your solutions.
(Using Ruby 1.9.3, ActiveRecord 4.2.4)
ActiveRecord makes assumptions about primary keys. It is expecting to find a primary key on your UserDetail table called "ID". Should look something like.
class UserDetail < ActiveRecord::Base
self.table_name = :user_detail
self.primary_key = :user_id
def self.add_new_user
new_user = UserDetail.new
new_user.first_name = 'Joe'
new_user.last_name = 'Smith'
new_user.user_id = 1
new_user.save
end
end

How to remove table column in Active Record

I would like to remove column from the table by using Active record. Please find the below snippet
require "active_record"
require 'sqlite3'
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => 'test_one')
class Account < ActiveRecord::Base
table_name = "AccountBean"
primary_key = "process_id"
remove_column "xxx" // I need this type of method to remove column "xxx" from accounts table
end
Is there any class method in ActiveRecord which satisfy this requirement ?
I guess ActiveRecord presumes that changes to structure shall be done using migrations.
If you really need to, you could use the same methods rails uses in migrations to e.g. remove a column - like here
I don't recommend this :)
ActiveRecord::Base.connection.remove_column("persons", "first_name")
Within a class that would look something like:
class Account < ActiveRecord::Base
table_name = "AccountBean"
primary_key = "process_id"
connection.remove_column(table_name, "xxx")
end

ActiveRecord is showing wrong column defaults

This is my first time using ActiveRecord in a non-rails application, and I'm running into a problem. ActiveRecord is able to figure out the columns I have in my sqlite3 database, but it can't figure out the default column values for some reason.
Here is the sql description for the table in question.
-- Describe ACCOUNTS
CREATE TABLE "accounts" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL DEFAULT (0),
"username" TEXT NOT NULL,
"password_hash" BLOB NOT NULL,
"creation_time" INTEGER NOT NULL DEFAULT(strftime('%s', 'now')),
"expiration_time" INTEGER NOT NULL DEFAULT(strftime('%s', 'now') + 2592000)
)
I used the following code for loading my database file.
require 'active_record'
require './config.rb'
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:database => DB_FILE
)
class Account < ActiveRecord::Base
end
When I look at the column defaults for the Account table with a REPL, this is what I get:
[10] pry(main)> Account.column_defaults
=> {"id"=>0,
"username"=>nil,
"password_hash"=>nil,
"creation_time"=>0,
"expiration_time"=>0}
I worked with ActiveRecord for a rails app before and it was smart enough to figure out the default values. For some reason, it can't figure them out now.
Am I doing something wrong here? I read that I can manually specify the default value with default :id => bla, but shouldn't ActiveRecord be able to figure out the defaults?
Update: I think I figured out a workaround. The hash returned by Account.column_defaults is writeable, and changing those elements seems to work fine.
Try doing this:
class Account < ActiveRecord::Base
after_initialize :default_values
private
def default_values
self.username ||= "default value"
#etc...
end
end

Sequel model over two joined tables

I have a legacy PostgreSQL database, which has a single model split into two tables, with one-to-one mapping between them.
CREATE TABLE auth_user (
id SERIAL,
username VARCHAR(30),
email VARCHAR(75),
password VARCHAR(64),
first_name VARCHAR(75),
last_name VARCHAR(75)
)
CREATE TABLE user_profile (
user_id INTEGER REFERENCES auth_User.id,
phone VARCHAR(32)
)
Unfortunately, I'm unable to change database structure.
I want to use this as a single Sequel model. Retreiving data from database works as expected:
class User < Sequel::Model
end
# Variant 1: using LEFT JOIN
#User.set_dataset DB[:auth_user].left_join(:user_profile, :user_id => :id)
# Variant 2: using two FROM tables
User.set_dataset DB[:auth_user, :user_profile]\
.where(:auth_user__id => :user_profile__user_id)
user = User[:username => "root"] # This works.
However, saving the model fails:
user.set :first_name => "John"
user.save # This fails.
If I use first variant of the dataset (with left_join) I get a "Need multiple FROM tables if updating/deleting a dataset with JOINs" error. If I use second variant, it still fails: "PG::Error: ERROR: column "phone" of relation "auth_user" does not exist LINE 1: ..."email" = 'nobody#example.org', "password" = '!', "phone"..."
Is there a way I could make Sequel seamlessly issue two UPDATE statements? (Same question holds for INSERTs, too).
You can have a Sequel model that uses a joined dataset, but there's no easy way to save such a model.
Personally, I would use a many_to_one relationship, nested attributes, and hooks for what you want:
class UserProfile < Sequel::Model(:user_profile)
end
class User < Sequel::Model(:auth_user)
many_to_one :user_profile, :key=>:id, :primary_key=>:user_id
plugin :nested_attributes
nested_attributes :user_profile
def phone
user_profile.phone
end
def phone=(v)
user_profile.phone = v
end
def user_profile
if s = super
s
else
self.user_profile_attributes = {}
super
end
end
def before_destroy
user_profile.destroy
super
end
def before_create
user_profile
super
end
def after_update
super
user_profile.save
end
end
I haven't tested that, but something like it should work. If you have problems with it, you should probably post on the sequel-talk Google Group.

Resources