I have the following models:
class Supervision::ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.table_name_prefix
"supervision_"
end
end
---------------
class Supervision::Activity < Supervision::ApplicationRecord
has_one :supervision_missed_visit, class_name: Supervision::MissedVisit
(...)
end
---------------
class Supervision::MissedVisit < Supervision::ApplicationRecord
belongs_to :supervision_activity, class_name: Supervision::Activity
(...)
end
And I also have this model, which isn't namespaced:
class Activity < ApplicationRecord
(...)
end
Whenever I try to reach the Supervision::MissedVisit through its has_one relationship, as in
#supervision_activity.supervision_missed_visit
I get the following error:
ERROR: column supervision_missed_visits.activity_id does not exist
How do I make it so that Rails understands that I'm actually looking for supervision_missed_visits.supervision_activity_id?
You could specify the foreign key:
has_one :supervision_missed_visit, class_name: Supervision::MissedVisit, foreign_key: 'supervision_activity_id'
Another way to do it would be to create an instance method in Supervision::Activity:
def missed_visit
Supervision::MissedVisit.where(supervision_activity_id: id).take
end
Related
I have an Article model that can have many different types of content blocks. So an Article can have many HeadingBlocks and ParagraphBlocks like this:
class Article < ApplicationRecord
has_many :heading_blocks
has_many :paragraph_blocks
end
class HeadingBlock < ApplicationRecord
belongs_to :article
end
class ParagraphBlock < ApplicationRecord
belongs_to :article
end
I want to be able to alias both HeadingBlock and ParagraphBlock with the same name (blocks) so if I have an instance of an article, I can do something like this:
#article.blocks // returns all heading blocks and paragraph blocks associated
Is this possible in Rails? If so, could you please provide an example on how to alias multiple models in a has many association using the same name?
Thank you.
You can have a method that returns an array of blocks:
class Article < ApplicationRecord
has_many :heading_blocks
has_many :paragraph_blocks
# NOTE: returns an array of heading and paragraph blocks
def blocks
heading_blocks + paragraph_blocks
end
end
You can reorganize the relationships to have a polymorphic association:
class Article < ApplicationRecord
has_many :blocks
end
# NOTE: to add polymorphic relationship add this in your migration:
# t.references :blockable, polymorphic: true
class Block < ApplicationRecord
belongs_to :article
belongs_to :blockable, polymorphic: true
end
class HeadingBlock < ApplicationRecord
has_one :block, as: :blockable
end
class ParagraphBlock < ApplicationRecord
has_one :block, as: :blockable
end
If you can merge HeadingBlock and ParagraphBlock into one database table:
class Article < ApplicationRecord
has_many :blocks
end
# id
# article_id
# type
class Block < ApplicationRecord
belongs_to :article
# set type as needed to `:headding` or `:paragraph`
end
# NOTE: I'd avoid STI; but this does set `type` column automatically to `ParagraphBlock`
# class ParagraphBlock < Block
# end
I have the following problem,
A user can have several professions, more than 10. For example, a user may be a doctor, teacher, and N. Each profession has its own attributes.
I could do, Doctor belongs_to User, but if I want to know all the professions of this user I will have to check each row of the User table.
I created the following code
class User < ApplicationRecord
has_many :jobables
end
class Job < ApplicationRecord
belongs_to :user
belongs_to :jobable
end
class Jobable < ApplicationRecord
has_one :job
end
class Medic < Jobable
end
class Programmer < Jobable
end
But I do not know if that would be the best answer
I would think that it would be much easier to do something like:
class User < ApplicationRecord
has_many :user_professions
has_many :professions, through: :user_professions
end
# == Schema Information
#
# Table name: professions
#
# id :integer not null, primary key
# name :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Profession < ApplicationRecord
has_many :user_professions
has_many :users, through: :user_professions
end
class UserProfession < ApplicationRecord
belongs_to :user
belongs_to :profession
end
You could then create logic to ensure that a Profession is only assigned to a User once.
Then, you could simply do:
#user.professions
And get all the Professions for a User.
You could also do:
#profession.users
And get all the Users that belong to the Profession.
Based on the edit to your question, you could do something like:
class UserProfession < ApplicationRecord
belongs_to :user
belongs_to :profession
belongs_to :profession_detail, polymorphic: true
end
In which case you might have something like:
class DoctorDetail < ApplicationRecord
end
And you could do something like:
#user.professional_detail_for(:doctor)
Of course, you would need to implement the professional_detail_for method on the User model which might look something like:
class User < ApplicationRecord
has_many :user_professions
has_many :professions, through: :user_professions
def professional_detail_for(profession_type)
user_profession_for(profession_for(profession_type)).try(:profession_detail)
end
private
def profession_for(profession_type)
Profession.find_by(name: profession_type.to_s)
end
def user_profession_for(profession)
user_professions.find_by(profession: profession)
end
end
That's a little rough, but I imagine you get the idea.
Question:
How can I tell ActiveRecord to not include the namespace of the association class while storing/querying the association type column?
Current state of things:
Consider the following class definitions:
class PageTemplateA < ActiveRecord::Base
has_many :type_a_pages, :as => :pageable, :class_name => 'TypeAPage', :inverse_of => :pageable
end
##### The following class is implemented through STI.
class TypeAPage < Page
belongs_to :pageable, :class_name => 'PageTemplateA', :inverse_of => :type_a_page
end
class Page < ActiveRecord::Base
belongs_to :pageable, :polymorphic => true
end
To summarize:
TypeAPage is implemented through STI in the database table, pages.
TypeAPage is associated with PageTemplateA through a polymorphic association (pages.pageable_type is PageTemplateA when associated with PageTemplateA)
The change I want to make:
I want to move all the above models into a new namespace, say, PagesEngine, so my definition for PageTemplateA looks like:
module PagesEngine
class PageTemplateA < ActiveRecord::Base
has_many :type_a_pages, :as => :pageable, :class_name => 'TypeAPage', :inverse_of => :pageable
end
end
This works fine, except that ActiveRecord infers the pageable_type for TypeAPage to be PagesEngine::PageTemplateA.
How can I tell ActiveRecord to not include the namespace, and resolve pageable_type to PageTemplateA instead of PagesEngine::PageTemplateA?
For the model that holds the polymorphic relation, you can set self.store_full_sti_class = false or inherit from an abstract model that sets store_full_sti_class to false.
module PagesEngine
class Page < Base
# self.store_full_sti_class = false
belongs_to :pageable, polymorphic: true
end
class TypeAPage < Page
belongs_to :pageable,
class_name: 'PageTemplateA',
inverse_of: :type_a_pages
end
end
Explicitly scope the relation (type_as_pages) with the type column (pageable_type) matching the associated model (PagesEngine::PageTemplateA) but without the namespace.
module PagesEngine
class PageTemplateA < Base
has_many :type_a_pages,
-> { where(pageable_type: 'PageTemplateA') },
foreign_key: :pageable_id,
inverse_of: :pageable
end
end
Finally override belongs_to method and customise Active Record Belongs To Polymorphic Association.
module PagesEngine
class Base < ActiveRecord::Base
self.abstract_class = true
self.store_full_sti_class = false
def self.belongs_to(name, scope = nil, options = {})
super(name, scope, options).tap do |hash|
reflection = hash[name.to_s]
def reflection.association_class
if polymorphic?
BelongsToPolymorphicAssociation
else
super
end
end unless store_full_sti_class
end
end
end
class BelongsToPolymorphicAssociation < ActiveRecord::Associations::BelongsToPolymorphicAssociation
def klass
type = owner[reflection.foreign_type]
type.presence && PagesEngine.const_get(type)
end
end
end
Use the self.sti_name to override the type column name for the respective class:
module PagesEngine
class PageTemplateA < ActiveRecord::Base
def self.sti_name
"PageTemplateA"
end
end
end
You might find the following blog article useful: https://lorefnon.me/2014/07/27/optimizing-sti-columns.html
I'm using Rails 4.
I have an Anomaly Records class called Ar that inherits from the following classes as follows:
class RecordBase < ActiveRecord::Base
self.abstract_class = true
end
class ArAndEcrBase < RecordBase
self.abstract_class = true
# Relations
belongs_to :originator, class_name: 'User', foreign_key: 'originator_id'
has_many :attachments
end
class Ar < ArAndEcrBase
end
I want to share some relations with a class that handles another type of records in the Ar subclass however the has_many relationship doesn't work.
The following works:
> Ar.last.originator
=> #<User id: 1, ...
The following crashes:
> Ar.last.attachments
Mysql2::Error: Unknown column 'attachments.ar_and_ecr_base_id'
For some reason the has_many relationship doesn't work well. It should look for column attachments.ar_id and not attachments.ar_and_ecr_base_id
Am I doing something wrong? Or is this a Rails bug?
Atm the only way to get the code working is to move the has_many relation to the Ar class:
class Ar < ArAndEcrBase
has_many :attachments
end
If you want several models to have association to the same other model you probably need a polymorphic association
class Picture < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
end
class Employee < ActiveRecord::Base
has_many :pictures, as: :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, as: :imageable
end
I want to a a foreign key "the rails way" after creation of an instance. How does it work?
Models:
class Letter < ActiveRecord::Base
belongs_to :sender
belongs_to :campaign
end
class Campaign < ActiveRecord::Base
has_many :letters
end
class Sender < ActiveRecord::Base
has_many :letters
end
Code:
sender1 = Sender.first
letter1 = sender1.letters.create
#Now I want to add a connection from letter1 to a campaign,
#but this cannot be the proper rails way:
letter1.campaign_id = 1