How do you call the where method in an ActiveRecord abstract class? - ruby

We use database resource tables to provide the values that appear in our application's combo boxes. Each resource table has a display_name column and a service_name column. The display name is what the user sees and the service name is sent to the web service. Some of our fields are optional and I have added an is_default column. I would like to create an abstract class that returns the default service name. Here is my first effort. However, when the CondenserPumpControlType.default_service_name method is called I get:
Exception message: undefined method where' for Class:Class Stack
trace: ["/apps/ar2/app/models/defaultable_record.rb:5:in
default_service_name'"
I expected the abstract class to invoke CondenserPumpControlType.where and instead it appears to invoke Class.where. What am I doing wrong. I can't seem to find much documentation on ActiveRecord abstract classes.
class DefaultableRecord < ActiveRecord::Base
self.abstract_class = true
def self.default_service_name
default_value = self.class.where(is_default: 1).first
default_value.service_name
end
end
class CondenserPumpControlType < DefaultableRecord
attr_accessible :display_name, :service_name, :sort_order, :is_default
end

self.class.where(is_default: 1).first
should be
self.where(is_default: 1).first

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

Rspec, fake class AR attribute

Hello i am trying to test method that in one place calls activerecord attribute, but however in test i created fake class to test it out and do not have a table for that test class, so how i could give that class a fake table or smthng?
tried to define load schema, but that didn't work:
class TestClassStatus < FrozenRecord::Base
end
class TestClass < ApplicationRecord
def self.load_schema!
#columns_hash={"test_class_status_id" => {#name=>'test_class_status_id', #table_name=>'TestClassStatus', #sql_type_metadata=>{ #sql_type=>'character varying', #type=>:string, #limit=>nil, #precision=>nil, #scale=>nil }, #null=>false, #default=>'', #default_function=>nil, #collation=>nil, #comment=>nil, #max_identifier_length=>63}}
end
has_status
end
here i have TestClassStatus where statuses from yml file are loaded by frozen record, and TestClass is a class where i use the method i want to test has_status. And that method is trying to find test_class_status_id in TestClass. But unfortunately i dont have a table for that fake TestClass. So any advice how to make a fake table for that TestClass to run this spec? :)
Thank you in advance!
Here is a part where it breakes
name = status_name ? status_name.to_s : "#{self.to_s}Status"
klass = name.camelize.constantize
include Module.new {
class_eval %Q{
def #{association_name}
#{klass}.find(self[:#{name.underscore}])
end
}
}
It brakes in line where it tries to find that attribute. klass in my case will be TestClassStatus and name will be test_class_status so in that line it tries to recieve test_class_status attribute in TestClass, but unfortunately as you can see my TestClass does not have a table. So i need somehow to fake the table, because this method hits activerecord :)
There is a way to test it using real class, but I don't want to be attached to a real class in testing in case someday i would need to remove that class.

Missing ActiveRecord methods (find_by) for object

I am trying to do an assignment which requires me to create and save an ActiveRecord within my Model class, and then return it. The rspec is expecting to use the find_by method to verify this. Here's my Model:
-----------------------
class User < ActiveRecord::Base
attr_accessor :id, :username, :password_digest, :created_at, :updated_at
after_initialize :add_user
def initialize(attributes={})
#username = attributes[:username]
#password_digest = attributes[:password_digest]
super
end
def add_user
self[:username] = #username
self[:password_digest] = #password_digest
self.save
self[:id] = self.id
end
end
----------------
If I do User.new(params), the record is in fact stored properly to the DB. But, the find_by method is missing for the returned object. So, rspec fails. I have looked everywhere but can't seem to find the solution. I am a noob, so sorry if the answer is obvious and I can't see it.
You say
If I do User.new(params), the record is in fact stored properly to the DB. But, the find_by method is missing for the returned object
This is expected behavior. Hopefully you understand by now the difference between class and instance methods. The main important point is that query methods such as find_by are not made available to model instances. If you do something like user = User.find_by(id: params[:id]), you're calling the find_by class method on the User model.
There are a number of methods like where, order, limit, etc. that are defined in ActiveRecord::QueryMethods - these are made available to ActiveRecord::Relation object and your model class. Most of these methods will return ActiveRecord::Relation objects, which is why they're chainable, e.g.
User.where(params).order(created_at: :desc).limit(5)
However find_by is an exception - it returns a model instance so you can't continue to query on the results. In summary User.new(params) returns an instance of the model which doesn't have find_by available

alias_attribute vs read_attribute and write_attribute

I'm using ActiveRecord without rails. Everything works fine except for a weird quirk with some helper methods I'm writing that I'm hoping someone can explain. I've got a model of a legacy database. Some columns names have a "#" in them so I defined them in the model using read_attribute and write_attribute. For example (accurate example but simplified):
class Product < ActiveRecord::Base
alias_attribute :name, :pname
def sale_number
read_attribute 'sale#'
end
def sale_number=(value)
write_attribute 'sale#', value
end
def self.helper_with_alias
where(name: 'My Product Name')
end
def self.helper_with_attribute
where(sale_number: 5)
end
end
If I call Product.helper_with_alias everything works as expected. But when I call Product.helper_with_attribute I get a ActiveRecord::StatementInvalid error saying that the column sale_number could not be found. In addition, if I replace the code in helper_with_attribute to where('sale#' => 5) everything works fine.
Why does ActiveRecord correctly alias pname to name but not correctly alias sale# to sale_number?
The error is because you are using an in-existent column in the where clause. You also need to define alias_attribute for sale# to sale_number.
In your model, you can do:
class Product < ActiveRecord::Base
alias_attribute :sale_number, :"sale#"
def self.helper_with_attribute
where(sale_number: 5)
end
end
With this you don't need to define setters and getters just for assignment and retrieval purposes, so you can remove sale_number and sale_number=(value) methods. With alias_attribute, getters, setters and query methods are already aliased!
Why does ActiveRecord correctly alias pname to name but not correctly
alias sale# to sale_number?
This is because you have defined alias_attribute :name, :pname which provided the setters, getters and query methods as alias to your existing pname attribute. But, for sale_number, you've only defined a getter and setter but not the query methods.

Clean association definitions with Ruby Sequel

I am using Jeremy Evan's Sequel to populate an (SQLite) database with data I scrape from web pages.
The database involves a number of many_to_many relationships that I express with Associations.
The associations are created in class definitions, which are always evaluated when the script is run.
Importantly, the association class definitions need to have the necessary tables in place.
Thus the table creation methods should be in the top level with the association definitions.
Here is an example:
module Thing
db = Sequel.Sqlite('data.sqlite')
db.create_table(:clients)
String :client_id, :primary_key => true
String :client_data
end
db.create_table(:orders)
String :order_id, :primary_key => true
String :order_data
end
db.create_table(:orders_clients)
String :order_id
String :client_id
primary_key [:order_id,:client_id]
end
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
end
First of all, I think that this is a rather dirty solution, since my method calls and class definitions sit in the same namespace.
If I try to separate the class definitions, I get No database associated with Sequel::Model error (which makes sense, but I want to defer the evaluation of the association definitions, having those after the table calls, whenever they might happen).
I want to be able to create the tables and associations in a method call. Thus, I could for example pass the name of the new database file:
def create_tables_and_schema (database_name)
db = Sequel.Sqlite(database_name)
db.create_table... #three of those, as above
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
end
What I think is needed is a different way to express table relations.
Any suggestions on approach and style are appreciated. Please ask for clarifications if the explanation is confusing.
Your method calls and class definitions do not need to sit in the same namespace, it's just that the tables need to be created before the model classes. An easy way to separate them is to move the table creation to a separate file. Also, usually you assign the database object to a constant.
create_tables.rb:
DB.create_table(:clients)
String :client_id, :primary_key => true
String :client_data
end
DB.create_table(:orders)
String :order_id, :primary_key => true
String :order_data
end
DB.create_table(:orders_clients)
String :order_id
String :client_id
primary_key [:order_id,:client_id]
end
models.rb:
DB = Sequel.sqlite('data.sqlite')
require 'create_tables'
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
You mentioned that you want to create the tables and associations in a method call, but that doesn't make sense if you are creating classes with constants. The main reason to create them via a method call is to allow for multiple databases at runtime, but that wouldn't work with your model classes since they are defined with constant names.
If you don't need multiple databases at runtime, the example above should work if you just want to separate the table creation from the model creation.
If you do need multiple databases at runtime, then creating the tables and models via a method call makes sense, but you need to create anonymous model classes, as otherwise you will have problems.

Resources