Rails share custom database connection between classes - without class inheritance? - ruby-on-rails

I have a replica database slave running, where I want to send some readonly calls to, to offload the master. For this reason I would like to create two customs models, that connect separately to that database, so that I can use those, whenever I have the need to offload some queries.
I have two classes:
class Car < ActiveRecord::Base
has_many :prices
end
class Price < ActiveRecord::Base
belongs_to :car
end
Rails has the possibility to share custom database connections on model level like this:
class MyCustomDbConnection < ActiveRecord::Base
self.abstract_class = true
establish_connection({database: 'www.slavedb.com', port: 5432, pass: 'some'})
end
class ReadonlyCar < MyCustomDbConnection
end
class ReadonlyPrice < MyCustomDbConnection
end
While this works well, my use case is that I would ALSO like to have the ´ReadonlyCar´ and ReadonlyPrice inherit from the original models that uses the default ActiveRecord::Base.connection. This is needed to have the ReadonlyCar behave exactly like the original one Car (business logic). Now I can't do class inheritance from both ReadonlyCar < MyCustomDbConnection and ReadonlyCar < Carat the same time, so how do I get the full functionality from Car, but share the same connection to the slave between both ReadonlyCar and ReadonlyPrice?

You could write a module where in the included hook you do that call to establish the connection.
module MyCustomDbConnection
def self.included(base)
base.establish_connection(
database: 'www.slavedb.com',
port: 5432,
pass: 'some'
)
end
end
class ReadOnlyCar < Car
include MyCustomDbConnection
end

Related

How do I add a superclass to two classes in Rails?

In my Rails application I have two classes :
UserPost and GroupPost (inheriting from ApplicationRecord), that I want to make subclasses of a new class, Post.
What would be the best way to add this in?
Is it as simple as making the class manually, and adding in the inheritance or are there problems that could cause?
I'm making the assumption that UserPost and GroupPost are inherited from ActiveRecord::Base, i.e. you want to save them in your DB
If you have class UserPost and GroupPost inherit from Post in the following way, this is known as Single Table Inheritance
class Post < ApplicationRecord
end
class GroupPost < Post
end
class UserPost < Post
end
Rails will, by default, expect you to have a column called "type" (can be configured to any other name) in the Posts table. GroupPost and UserPost will populate their respective values in the same table, adding in "GroupPost" or "UserPost" to the type. You can read up on it here
Rails also provides you the ability to have the parent class be "abstract", i.e. it shouldn't be able to persist to the database. Perhaps you noticed it in the the app/models/application_record.rb file as well. This way the parent serves as just a concept where you can add in common behaviour, STI will not kick in. There will need to be separate tables for children.
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
If you plan on having separate tables for the classes, I'd recommend trying composition instead of inheritance using mixins. Basically you write common functionality in a "module" and include it to the classes that need the separate functionality. Concerns are a nice way to append class methods, associations etc without having to rely on arcane ruby syntax. I'd recommend having a look at the documentation
module Fooable
extend ActiveSupport::Concern
included do |base|
scope :enabled, -> { where(enabled: true) }
has_many :boos, dependent: :destroy
end
class_methods do
def having_name
where('name ILIKE ?', "#{name}%")
end
end
def hi
return 'hi'
end
end
class UserPost < ApplicationRecord
include Fooable
end
UserPost.first.boos # relationship
UserPost.enabled # calling a scope
UserPost.having_name('name') # calling a class method
UserPost.new.hi #calling an instance method

Single Table Inheritance & ActiveRecord Associations

I'm attempting to have a parent model that other models inherit from, and a secondary class that has associations with the children from the first model. Here's what I have so far...
I used the first command to create the parent model.
rails g model Person name:string age:integer height:float
I then set up Person as the parent class, and two children class which inherit from it as seen below.
class Person < ActiveRecord::Base
self.abstract_class = true
end
class Driver < Person
self.table_name = "Driver"
belongs_to :vehicle
end
class Passenger < Person
self.table_name = "Passenger"
belongs_to :vehicle
end
I also set up a Vehicle class to use in an association with the Driver and Passenger children classes.
rails g model Vehicle color:string capacity:integer
class Vehicle < ActiveRecord::Base
has_one :driver
has_many :passengers
end
However, if I use the rails console and attempt to create a new instance Driver or Passenger, using Driver.new or Passenger.new, I encounter an PostgreSQL error.
For reasons which I cannot figure out, Rails is writing SQL statements to query on a Driver or Passenger table. However, it is my understanding that only one Person table is made and has a type attribute that is used for queries.
If anyone can offer some guidance here, I'd be very appreciative.
Thanks
Since it seems you're trying to use the common table persons for both Driver and Passenger, you don't need the abstract_class declaration, or the table_name declarations in either of the sub classes. Per the rails documentation, these options would be used when you want classes to inherit behavior from their parent, without the parent itself having a table.
The snippet below should work fine.
class Person < ActiveRecord::Base
end
class Driver < Person
belongs_to :vehicle
end
class Passenger < Person
belongs_to :vehicle
end

How to simplify association name?

I have two models:
# /app/models/service_user.rb
class ServiceUser < ActiveRecord::Base
has_many :service_user_images
end
# /app/models/service_user_image.rb
class ServiceUserImage < ActiveRecord::Base
belongs_to :service_user
end
And when I want get all images for a service user I need to call:
#service_user.service_user_images
#^^^^^^^^^^^^^^^^^^^^^^^^^
How I can simplify the name 'service_user_images' to 'images' exactly for ServiceUser model? And should I do it? Are calls like 'service_user.service_user_xxxs' normal for ActiveRecord?
Now I see three solutions:
1) using with the ':class_name' option, but I think that it's overhead for my code and it will make the code dirtier if I have a few associations
2) using with method alias, but I don't think that it's a good solution, because ActiveRecord creates multiple methods for each association and I will be forced to use too many aliases
3) using nested classes like 'ServiceUser::Image', but it creates another difficulties, for example I need to define the name of this class in each factory (I use FactoryGirl), because it translates names like ':service_user_image' to 'ServiceUserImage' by default:
FactoryGirl.define do
factory :service_user_image do |image| # works for ServiceUserImage model, does not work for ServiceUser::Image model
# ...
end
factory :service_user_image, class: ServiceUser::Image do |image| # works for ServiceUser::Image model
# ...
end
end
Ideally I search solution like:
class ServiceUser < ActiveRecord::Base
has_many :service_user_images, alias: :images # JUST EXAMPLE! DO NOT USE THIS!
end
Now I found solution:
class ServiceUser < ActiveRecord::Base
has_many :service_user_images
alias_attribute :images, :service_user_images
end
It works perfectly and it has no "side effects", which were described by me.

Model spread on several tables

Here is what I'm attempting to do:
class Account < ActiveRecord::Base
def name
end
end
class TypeA < Account
end
class TypeB < Account
end
Where TypeA and TypeB are stored on two distinct tables and Account acts pretty much as an abstract interface (with no table associated). They both have large number of entris and large number of fields so I want to keep them separated. Is there a way to go for this ?
(The exemple above does not work as a table for account is expected btw).
UPDATE
Now, if I use modules (as suggested in the answers), that raises another problem:
Let's say I have
class Transaction < ActiveRecord::Base
belongs_to :account, :polymorphic => true
end
where account can be TypeA or TypeB. I get the following misbehavior:
i = TypeA.new(:name => "Test")
t = Transaction.new(:account => i)
t.account.name
>> nil
which is not what I want as account.name should return "Test". How to get this?
Use module instead. You have shared behavior between those two models that you want to share. That's a great use-case for modules.
# inside lib/account.rb
module Account
# ...
def name
# code here
end
# ...
end
# inside app/models/type_a.rb
class TypeA < ActiveRecord::Base
include Account
end
# inside app/models/type_b.rb
class TypeB < ActiveRecord::Base
include Account
end

Rails transparent child relationship

I have a polymorphic relationship, and I would like the child (polymorph?) to be completely transparent. The setup is generic:
class ScheduledEvent < ActiveRecord::Base
belongs_to :scheduleable, polymorphic:true
#has column names like #starts_at, #ends_at
end
class AppointmentTypeOne < ActiveRecord::Base
has_one :scheduled_event, :as=>:scheduleable, :dependent=>:destroy
end
class AppointmentTypeTwo < ActiveRecord::Base
has_one :scheduled_event, :as=>:scheduleable, :dependent=>:destroy
end
I would like to be able to treat AppointmentTypeOne and AppointmentTypeTwo as if THEY had the #starts_at and #ends_at table columns.
Method-wise it's very easy to add #starts_at, #starts_at=, etc to my AppointmentX classes, and refere back to ScheduledEvent. But how can I setup so that the relationship is transparent to ActiveRelation also? Letting me do something like:
AppointmentTypeOne.where('starts_at IS NOT NULL')
(not having to join or include :scheduled_event)
It sounds like you want to use Single Table Inheritance, not a has_one association. That will allow you to create subclasses of ScheduledEvent for each appointment type:
class ScheduledEvent < ActiveRecord::Base
end
class AppointmentTypeOne < ScheduledEvent
end
class AppointmentTypeTwo < ScheduledEvent
end
Basically, you add a type column to your scheduled_events table, and rails takes care of the rest.
This forum post covers all of the details: http://railsforum.com/viewtopic.php?id=3815

Resources