Rails STI - custom association in subclass - activerecord

class Upload < ActiveRecord::Base
has_many :comments
end
class Gallery < Upload
has_many :images
end
class MusicAlbum < Upload
has_many :audio_tracks
end
Should this work as expected? Will Gallery and MusicAlubm models inherit :comments association from their parent (Upload) and add their own?

Yes, the models are just classes, and when inherited they get all the methods from parent class. So, as both Gallery and MusicAlbum are descendants from Upload model, they will have the has_many :comments association, and both will get data from uploads db table (which needs to have a type column to support STI for this model)
A nice simple STI example can be found here

Related

class_name foreign_key in Rails model

I recently come across this code. User has many Answer. What is the purpose of the :class_name and :foreign_key ?
class Answer < ApplicationRecord
belongs_to :user, :class_name => 'Question", :foreign_key => 'question_id'
end
The naming here is kind of strange, but the purpose of :class_name is to allow you to use a class that is different from the one Rails expects. When you have a belongs_to :user on a model, Rails would expect that to point to a parent class called User. In your example, Rails skips looking for a User class and instead looks to the Question model.
The most common usage of this, though, is when a non-default association name makes more sense than the default. So a more apt example is when you have a User model and Competition model wherein each competition has one user as a winner. You could have each Competition belong to a User, but that wouldn't be as expressive. Instead you may want to have the relationship be referred to as winner:
class User < ActiveRecord::Base
has_many :winners, class_name: "Competition", foreign_key: "competition_id"
end
class Competition < ActiveRecord::Base
belongs_to :winner, class_name: "User", foreign_key: "winner_id"
end
This allows you to refer to users as winners:
competition = Competition.first
competition.winner
This is a lot more expressive than if you were to write competition.user.

nested form objects fails validation complaining of missing parent_id

I use simple_form. When using rails accepts_nested_attributes_for, the form fails validation complaining that the nested attributes validation failed because the id of the parent object can't be blank (validates_presence_of).
I know the parent object id is being submitted, because if I remove validation the form submits, and the child record is valid. So, the validations are looking at the params before the parent id is associated with the child...and therefor failing. This seems strange. Why is rails running validation at a stage in form submission before the parent of the nested attributes is correctly associated?
Is there a rails way for handling this scenario?
Just found the answer to this in the Rails docs. Here's their example, using inverse_of:
class Member < ActiveRecord::Base
has_many :posts, inverse_of: :member
accepts_nested_attributes_for :posts
end
class Post < ActiveRecord::Base
belongs_to :member, inverse_of: :posts
validates_presence_of :member
end

How to prevent ActiveRecord::Observer from logging deletion of dependent records if parent record is destroyed?

I have a model User that has_one Company. A Company can have many Taxes and AdditionalFees. I am using ActiveRecord::Observer to log any activity done by the user on their Company data for the admin. For that purpose i am observing User, Company, Tax and AdditionalFee models. For any change made to the attributes in these models by the user i am logging these activities by creating a record in another ActivityLog model.
The associations among my models are -
class User < ActiveRecord::Base
has_one: :company, dependent: :destroy
class Company < ActiveRecord::Base
belongs_to: :user
has_many: :taxes, dependent: :destroy
has_many: :additional_fees, dependent: :destroy
class Tax < ActiveRecord::Base
belongs_to: :company
class AdditionalFee < ActiveRecord::Base
belongs_to: :company
My Observer is as follows:
class ActivityObserver < ActiveRecord::Observer
observe :user, :company, :tax, :additional_fee
The problem i am facing is when the user or company is deleted.
I create a record in the AcitivityLog model in the after_destroy callback in my observer.
def after_destroy(obj)
ActivityLog.create(performer: 'user_name_here', target: 'record_destroyed(serialised object)', action: "destroy", description: "#user_name_here has deleted their company #company_name_here", change: "lot_variant")
end
I create a record in the ActivityLog this way for each record in the observed model that is deleted.
Because the observer is observing all the associated models, it is creating a new entry for each associated record when the user or company is deleted.
Is there a way to not prevent the observer from logging dependent destroyed records when the parent is destroyed? I do not wish to create a new entry in the ActivityLog model for the dependent records destroyed but want to record only for the parent record(company or user) that is destroyed.
You can check obj object type if it is Company or User you can go ahead and create ActivityLog.
def after_destroy(obj)
case obj
when Company, User then create_activity_log!
end
end

Rails multiple belongs_to assignment

Given
User:
class User < ActiveRecord::Base
has_many :discussions
has_many :posts
end
Discussions:
class Discussion < ActiveRecord::Base
belongs_to :user
has_many :posts
end
Posts:
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
end
I am currently initializing Posts in the controller via
#post = current_user.posts.build(params[:post])
My question is, how do I set/save/edit the #post model such that the relationship between the post and the discussion is also set?
Save and edit discussions along with post
Existing Discussion
To associate the post you're building with an existing discussion, just merge the id into the post params
#post = current_user.posts.build(
params[:post].merge(
:discussion_id => existing_discussion.id
)
You will have to have a hidden input for discussion id in the form for #post so the association gets saved.
New Discussion
If you want to build a new discussion along with every post and manage its attributes via the form, use accepts_nested_attributes
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
accepts_nested_attributes_for :discussion
end
You then have to build the discussion in the controller with build_discussion after you built the post
#post.build_discussion
And in your form, you can include nested fields for discussions
form_for #post do |f|
f.fields_for :discussion do |df|
...etc
This will create a discussion along with the post. For more on nested attributes, watch this excellent railscast
Better Relations
Furthermore, you can use the :through option of the has_many association for a more consistent relational setup:
class User < ActiveRecord::Base
has_many :posts
has_many :discussions, :through => :posts, :source => :discussion
end
class Discussion < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :discussion
end
Like this, the relation of the user to the discussion is maintained only in the Post model, and not in two places.

How to ensure a model is saved with the right account in rails?

If I have the following models setup:
class Member < ActiveRecord::Base
has_many :children
end
class Child < ActiveRecord::Base
belongs_to :member
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :child
end
When a new Photo is created, what is the best way to ensure that it is associated with a child in the member's account?
I have login working properly, and a current_member helper method, which doesn't seem to be available in the models
So, from what I gather, the "Rails Way™" of doing this would be to put the conditions in the controllers.
ex:
unless current_member.children.collect { |child| child.id.to_s }.include?(#photo.child_id)
#photo.errors.add :child_id "this is not your child"
end

Resources