I've seen a lot of many-to-many associations, but it seems that the common trend is that they end up using has_many :through relationships.
Let's say you have the following:
class User < ActiveRecord::Base
has_many :user_relationships
has_many :relations, :through => :user_relationships
end
class UserRelationship < ActiveRecord::Base
belongs_to :user
belongs_to :related_user, class_name: "User"
end
Given this type of relationship you end up setting the relationship up as follows:
user1.relations << user2
user2.relations << user1
Or this:
user1.user_relationships.build(related_user_id: 2)
user2.user_relationships.build(related_user_id: 1)
Resulting in rows in the join table looking like this:
user_id | related_user_id
1 | 2
2 | 1
So that when setting up the relation such as the above, you can see that the following can be accomplished
user1.relations.include? user2 = true
user2.relations.include? user1 = true
My question is: Is there a way to accomplish the above, or at least something similar to the above in speed, in Rails WITHOUT having to create 2 rows for every single two-way relationship and maintain the ability to see the relationship from both ends in an efficient manner, reducing the space complexity of creating this relationship by half...
Apologies if this is a noobie question, but I'm new to Rails, just starting to get the hang of things. It's easy to find out how to set these up, but I find it much harder to find out how to actually implement them in an efficient manner
Here's (roughly) what I've done. I'm going to skip a ton of the detail, but am happy to share more if it's helpful. This may be heinous, so I'm curious about others' thoughts.
Essentially, I have a Relationship model something like:
module ActsAsRelatingTo
class Relationship < ActiveRecord::Base
validates :owner_id, presence: true
validates :owner_type, presence: true
validates :in_relation_to_id, presence: true
validates :in_relation_to_type, presence: true
belongs_to :owner, polymorphic: true
belongs_to :in_relation_to, polymorphic: true
acts_as_taggable
acts_as_taggable_on :roles
end
end
Then, I've created a acts_as_relating_to module (again, glossing over the details) that includes stuff like:
module ActsAsRelatingTo
def acts_as_relating_to(*classes_array)
# re-opens the class at run time so I can do things like add
# new instance methods on-the-fly
class_eval do
before_destroy :tell_to_unrelate
has_many :owned_relationships,
as: :owner,
class_name: "ActsAsRelatingTo::Relationship",
dependent: :destroy
has_many :referencing_relationships,
as: :in_relation_to,
class_name: "ActsAsRelatingTo::Relationship",
dependent: :destroy
# iterates through arguments passed in 'acts_as_relating_to' call
# in the Person model, below.
classes_array.each do |class_sym|
# This is a method created on-the-fly. So, when I call
# (in the Person model, below) 'acts_as_relating_to :people',
# then I get a method on each instance of 'Person' called
# 'people_that_relate_to_me. You can also create class methods
# using `define_singleton_method`.
#
# The reason for doing all of this via the define_method
# is that it lets me create any number of 'things_i_relate_to'
# methods on any class descending from ActiveRecord::Base
# (more on this in a bit). Which, if I understand it
# correctly, is how a lot of the ActiveRecord functionality gets
# into a model in the first place.
define_method(class_sym.to_s + "_that_relate_to_me") do |options={}|
... some stuff
end
# Same as above, but know I'm defining a method called
# 'people_i_relate_to'
define_method(class_sym.to_s+"_i_relate_to") do |options={}|
... some more stuff
end
# I can also create static methods and incorporate them in the
# 'Person' class. I just define them in modules (such as
# ActsAsRelatingTo::InstanceMethods and ActsAsRelatingTo::ClassMethods)
# and then either 'include' (for instance methods) or 'extend'
# (for class methods) them.
include InstanceMethods
extend ClassMethods
end
end
end
end
# Here, I'm telling ActiveRecord::Base to 'extend' this module.
# That makes the 'acts_as_relating_to' method available in
# any class that descends from ActiveRecord::Base.
ActiveRecord::Base.extend ActsAsRelatingTo
Then, I can do something like:
class Person < ActiveRecord::Base
# Here, I am calling the method that I defined above, passing in
# :people, :organizations, and :programs. This is exactly the
# sort of thing you do all the time when you say something like
# 'has_one :foo', or 'belongs_to :bar'.
acts_as_relating_to :people, :organizations, :programs
# Here, I am calling a method I have that builds on acts_as_relating_to,
# but which I did not show, that creates administrative methods on
# the person so that I can say stuff like 'person.administrate organization'.
# Or, 'organization.administrators'.
acts_as_administering :organizations, :programs
...
end
So, if I have person_1 and person_2 and I have one relationship record where the owner is person_1, and the in_relation_to is person_2, then I can say person_1.people_i_relate_to and get back person_2. Or, I can say, person_2.people_that_relate_to_me and get back person_1.
I've also go an acts_as_administering module which builds on the acts_as_relating_to module and lets me do stuff like person_1.administered_organizations and person_1.administered_programs.
I've probably rattled on too long. Anyway, if it's interesting, I can say more.
Cheers!
Here's a finder method that I am writing in my Objective class:
def followed_objectives_for_user(user)
Objective.followed_by_user(user)
.joins(:resource_objective_links)
.where('resource_objective_links.resource_id = ? ', self)
end
It feels messy to have the knowledge of the ResourceObjectiveLink's resource_id column name known to the Objective class like this. I tried making a scope on the ResourceObjectiveLink class like this, but it can't find it when I chain it in the followed_objectives_for_user method in Objective:
scope :for_resource, ->(resource) { where(resource_id: resource.id) }
What is the correct way to hide this information?
Why cannot you just use ActiveRecord relations? Than just
User has_many :resource_objective_links
User has_many :objectives, through: :resource_objective_link
Objective has_many :resource_objective_links
Objective has_many :users, through: :resource_objective_links
ResourceObjectiveLink belongs_to :user
ResourceObjectiveLink belongs_to :objective
# And now you can use:
# user = User.find(some_user_id)
# user.objectives
# => will perform database join and return all Objectives for ResourceObjectiveLink with correct user.id
I'm trying to define an ability:
# customer.rb
class Customer
include Mongoid::Document
has_many :accounts
end
class Account
include Mongoid::Document
belongs_to :site
end
# ability.rb
can :manage, Customer, accounts: { :site_id.in => user.managed_site_ids }
# customers_controller.rb
Customer.accessible_by(current_ability)
It's always returns empty result, and I am not sure, but I think because mongoid doesn't support join query. Do you know what's wrong, or other alternatives?
I am using Ruby on Rails 3.2.2 and MySQL. I would like to "merge" the result of more than one ActiveRecord::Associations and one or more scope methods. That is, I have:
class User < ActiveRecord::Base
has_many :a_user_relationships, :foreign_key => 'a_key'
has_many :b_user_relationships, :foreign_key => 'b_key'
has_many :a_articles, :through => :a_user_article_associations # Returns objects kind of 'Article'
has_many :b_articles, :through => :b_user_article_associations # Returns objects kind of 'Article'
end
class Article < ActiveRecord::Base
# Note: This is a scope method.
def self.public
where(:status => 'public')
end
end
Given the above code I would like to run some method (as-like the following) so to retieve all public "a" and "b" user articles by executing as few as possible database queries:
#user.all_articles
class User < ActiveRecord::Base
# Note: Code in the following method is incorrect, but maybe it helps
# understanding what I mean.
def all_articles
self.(a_articles & b_articles).public
(self.a_articles.public & self.b_articles.public)
end
end
Is it possible?
Since you have two separate associations, you'll need at least two queries.
If you always want articles from both here, is there anything stopping you from having a simple "articles" association in addition to or instead of these two separate ones? That way, you could retrieve your articles with self.articles.public.
have a standard
def mutual_friends(friend)
self.friends & friend.friends
end
Trying to :include => :profile
Can this be done?
If not, can someone help me with a scope version of this?
Given that User has:
has_one :profile, :dependent => :destroy
has_many :friendships, :dependent => :destroy
has_many :friends,
:through => :friendships,
:conditions => "status = 'accepted'"
To answer your question there are 2 ways to do this.
The Rails way
1) Create a module (in your libs folder) add the shared methods to that module and include the module in the classes where needed.
or
The OO way
2) Create a base class descended from ActiveRecord::Base, make it an abstract class to avoid STI and add the common methods to the base class then descend Friend and Friendships from that
Option 1 example
Friend model
#You might need to require the friends module for Rails 3
require 'friends'
include Friends
class Friend < ActiveRecord::Base
#...
end
Just do the same in profile model and any other classes that need this
Friends module a file called friends.rb in lib folder
module Friends
module ClassMethods
#put your class methods here e.g.
def self.some_class_method
end
end
#put you instance methods here e.g.
def another_method
end
# extend host class with class methods when we're included
def self.included(host_class)
host_class.extend(ClassMethods)
end
end
Option 2 example
base_friends.rb in models folder (Or libs if you prefer)
class BaseFriends < ActiveRecord::Base
self.abstract_class = true
#All you usual methods that you want to share in the usual model way here
end
Friend model
Again - just do the same in profile and any other models that need this
class Friend < BaseFriends
#...
end
I prefer option 2 - It's much cleaner you have all the files in the same place (the models folder) and it's intuitive (You are already familiar with working in the model layer) plus this is what Ruby is all about (OO) Option 1 is more for when you want multiple inheritance which Rails does not allow for very good reason.
That answers your question but I think your question is wrong
You wouldn't do this through a method. No need and it doesn't make sense. Either approach above will allow you to share common methods plus Friends and Friendships would normally work from the same table and would therefore be the same class anyway you would just add class_name and foreign_key declarations to your has_many friendships and belongs_to friends declarations.