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:
- How does it work?
- 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:
- By definition, model scopes return
ActiveRecord::Relation
objects - By definition, scopes have access to class methods
- 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
I Want to Return a Single Result Using .Find() with Ruby on Rails
Connection Refused Using Sunspot and Solr in Rails
Why Must I Explicitly Call Self on Accessor When Using the Array Union Operator |= in Ruby
Rails 4 Use Application Helpers Inside Initializers
{|_, E| E.Length>1} What Is the Use of Underscore ( _ ) in Ruby
Implementing Dry-Run in Ruby Script
How to Capture Terminal Arrow Keys in Ruby
New to Ruby and Am Having Trouble with Load_Path
Rails Activerecord: Saving Nested Models Is Rolled Back
Rails 3 + Daemons Gem: Exception When Querying Model
Disabling Flash Message Without Disabling Cache on Click on Back Button in Rails
Ruby/Rails - Active Record Db Migration to MySQL - Timestamp Type
Ruby Instance Method & Conditional Local Variable Assignment with Same Name
Ruby Executable Won't Start on Win10 and Win7
Can't Find Ffi.H When Installing Ffi Ruby Gem
Numeric Literals Prepended with '0'
Seahorse::Client::Networkingerror Amazon S3 File Upload with Rails