How Does Activerecord::Relation Get Added to Rails' Models and Why Does Each Model Have Individual Relation Class

How does ActiveRecord::Relation get added to Rails' models and why does each model have individual Relation class?

There are actually two questions you are asking:

  1. How does it work?
  2. Why is it like that? (What for?)

@arieljuod has already given you some explanations and a link.

However the second question is still unanswered.

There is another similar question exists which I hope will help you find all the answers:

How can an ActiveRecord::Relation object call class methods

It looks like the two questions (by the link and yours one) answer each other )

Take a look at @nikita-shilnikov's answer. Good luck in your investigation!

Associate different models in the same relation

I would suggest using combination of store_accessor and STI. The store accessors will allow you to add model specific attributes with support for validation. If you use PostgreSQL you can take advantage of GIN/GiST index to search custom fields efficiently.

class User < ActiveRecord::Base
has_many :friends
end

class Friend < ActiveRecord::Base
belongs_to :user

# exclude nil values from uniq check
validates_uniqueness_of :user_id, allow_nil: true

# The friends table should have a text column called
# custom_attrs.
# In Postgres you can use hstore data type. Then
# next line is not required.
store :custom_attrs

# Shared between all friend models
store_accessor [ :shared_interests ]
validates_presence_of :shared_interests

# bit of meta-programming magic to add helper
# associations in User model.
def self.inherited(child_class)
klass = child_class.name
name = klass.pluralize.underscore.to_sym
User.has_many name, -> {where(type: klass)}, class_name: klass
end
end

Define various friend models with model specific attributes.

class BestFriend < Friend
store_accessor [ :favourite_colour ]
validates_presence_of :favourite_colour

def to_s
"This is my best friend whos favourite colour is #{favourite_colour} ..."
end
end

class RegularFriend < Friend
store_accessor [ :address ]
validates_presence_of :address

def to_s
"This is a regular friend who lives at #{address} ..."
end
end

class CloseFriend < Friend
store_accessor [ :car_type ]
validates_presence_of :car_type

def to_s
"This is a close friend who drives a #{car_type} ..."
end
end

Usage

user = User.create!

user.close_friends.create!(
car_type: "Volvo",
shared_interests: "Diving"
)

user.regular_friends.create!(
country: "Norway",
shared_interests: "Diving"
)

user.best_friends.create!(
favourite_colour: "Blue",
shared_interests: "Diving"
)

# throws error
user.best_friends.create!(
shared_interests: "Diving"
)

# returns all friends
user.friends.page(4).per(10)

Reference

Store documentation

PostgreSQL Hstore documentation:

How can an ActiveRecord::Relation object call class methods

There's not much documentation on the application on class methods to ActiveRecord::Relation objects, but we can understand this behavior by taking a look at how ActiveRecord scopes work.

First, a Rails model scope will return an ActiveRecord::Relation object. From the docs:

Class methods on your model are automatically available on scopes. Assuming the following setup:

class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
scope :featured, -> { where(featured: true) }

def self.latest_article
order('published_at desc').first
end

def self.titles
pluck(:title)
end
end

First, invoking a scope returns an ActiveRecord::Relation object:

Article.published.class
#=> ActiveRecord::Relation

Article.featured.class
#=> ActiveRecord::Relation

Then, you can operate on the ActiveRecord::Relation object using the respective model's class methods:

Article.published.featured.latest_article
Article.featured.titles

It's a bit of a roundabout way of understanding the relationship between class methods and ActiveRecord::Relation, but the gist is this:

  1. By definition, model scopes return ActiveRecord::Relation objects
  2. By definition, scopes have access to class methods
  3. Therefore, ActiveRecord::Relation objects have access to class methods

Rails - multiple relationships between two models

Firstly, don't forget that the associations between classes ought to describe the nature of the association. In this case there are multiple possible associations: a user can create a course, they can vote for a course, and they might study a course. Simple Rails naming conventions for the associations are not going to help you make sense of that.

If a course is created by a single user, then the relationship ought to be that the course :belongs_to :creating_user, and the user has_many :created_courses. You would add a user_id column to the courses table for this.

To allow voting by users on courses, you would use a join table between them. However, your association should describe the nature of the association, so calling the join table user_course_votes would make sense. Then you can have an association on the user of has_many :user_course_votes, and has_many :course_votes, :through :user_course_votes -- and appropriate associations on the course also.

The only thing you need to do to be able to name your associations whatever you like is to name the class or source in the association definition.

class User < ApplicationRecord
has_many :created_courses, class_name: "Course"
has_many :user_course_votes
has_many :course_votes, through: :user_course_votes, source: course
end

This methodology allows you to add other associations between user and course, and assign meaningful names to them that will make your code easier to write and to understand.

Get items with Rails relations

As things stand, your relations are incomplete and so Rails won't work properly. If User has_many Tickets then Tickets must belong_to (or at least has_one) User. Alternatively, User can have_many Tickets through Group, which seems more likely in this case.

However, even then, it's not clear what your Group model is doing. Particularly, it's not clear how you intend it to relate to User - this looks like quite a complex relationship.

To start with, though, try and set the models up like this:

class Ticket < ApplicationRecord
belongs_to :group
end

class Group < ApplicationRecord
belongs_to :user
has_many :tickets, dependent: :destroy
end

class User < ApplicationRecord
has_many :groups, dependent: :destroy
has_many :tickets, through: :groups
end

(You'll see that I've also inherited these models from ApplicationRecord, which is how I've always done it.)

If you set it up as above, you can get your ticket records with a simple @user.tickets.

If this works, you can then add the extra HABTM relationship for Groups and Users. But be aware that HABTM relationships can be complex and there are good and bad ways to use them.

(If the primary relationship you really want is Groups > Users > Tickets then let me know and I can adjust accordingly.)



Related Topics



Leave a reply



Submit