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.
Related
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!
I have a polymorphic association in a Rails 3 app where a User may favorite objects of various classes.
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :favoriteable, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :favorites
end
class Image < ActiveRecord::Base
has_many :favorites, :as => :favoriteable
end
class Video < ActiveRecord::Base
has_many :favorites, :as => :favoriteable
end
I would like to be able return a list of just a User's favorite_images for example.
user.favorite_images #returns a list of the images associated with the user via :favoritable
I'm guessing there is a straightforward way of doing this but I haven't been able to figure it out. Let me know if you need anymore info.
Thanks!
===edit====
I know that I could retrieve what I am looking for via
favorite_images = user.favorites.collect{|f| if f.favoriteable_type=='Image'; f.favoriteable; end}
I could define an instance method for the User class and put that inside. I was wondering if there is a way to do it as some sort of has_many association. Really just because going forward it would be easier to have all that in one place.
When you created the table for Favorite you created a couple of columns favoriteable_id and favoriteable_type and you can use this information to restrict your query.
If you do user.favorites you will get all of the favorites and to restrict them to say just the images then you can do user.favorites.where(favoriteable_type: 'image') but that just gives you the favorite records and it sounds like you want the actual images. To get those you can do it by then mapping and pulling the favoriteable out. You'll likely want to include it in the query though so you don't hit the database so much. I would also make this a method on User.
def favorite_images
favorites.includes(:favoriteable).where(favoriteable_type: 'Image').map(&:favoriteable)
end
I've seen some posts dealing with this, and am trying to determine the best solution.
Semantically, I want a Client model with a one-to-one relationship with a Survey. There are different kinds of surveys that have different fields but I want to share a significant amount of code between them. Because of the different fields I want different database tables for the surveys. There is no need to search across different types of surveys. It feels like I want the foreign key in the Client table for fast retrieval and potential eager-loading of the Survey.
So theoretically I think I want polymorphic has_one and multiple inheritance something like this:
class Client < ActiveRecord::Base
has_one :survey, :polymorphic => true
end
class Survey
# base class of shared code, does not correspond to a db table
def utility_method
end
end
class Type1Survey < ActiveRecord::Base, Survey
belongs_to :client, :as => :survey
end
class Type2Survey < ActiveRecord::Base, Survey
belongs_to :client, :as => :survey
end
# create new entry in type1_surveys table, set survey_id in client table
#client.survey = Type1Survey.create()
#client.survey.nil? # did client fill out a survey?
#client.survey.utility_method # access method of base class Survey
#client.survey.type1field # access a field unique to Type1Survey
#client2.survey = Type2Survey.create()
#client2.survey.type2field # access a field unique to Type2Survey
#client2.survey.utility_method
Now, I know Ruby does not support multiple inheritance, nor does :has_one support :polymorphic. So is there a clean Ruby way to achieve what I'm getting at? I feel like it's right there almost...
Here's how I would do this:
class Client < ActiveRecord::Base
belongs_to :survey, :polymorphic => true
end
module Survey
def self.included(base)
base.has_one :client, :as => :survey
end
def utility_method
self.do_some_stuff
end
end
Type1Survey < ActiveRecord::Base
include Survey
def method_only_applicable_to_this_type
# do stuff
end
end
Type2Survey < ActiveRecord::Base
include Survey
end
I have two models that are connected via a has_many/belongs_to association:
Class Project < ActiveRecord::Base
has_many :tasks
end
Class Tasks < ActiveRecord::Base
belongs_to :project
end
Each of the tasks are tagged with a HABTM relationship:
Class Tasks < ActiveRecord::Base
belongs_to :project
has_and_belongs_to_many :tags
end
I am trying to get a list of projects based on a tag id. I can get a list of projects that have tasks with a specific tag by using a class method on my Project model:
def by_tag(tag_id)
Project.joins(:tasks => :tags).where(:tags => {:id = tag_id})
end
Ideally, I'm looking to be able to list all the projects and their associated tasks for a given tag in my view. I could normally get a list of tasks belonging to a given project by using project.tasks if I used a typical find with project like Project.find(1).
However, when I try project.tasks on results found using my new class method Project.by_tag(1), I get a "NoMethodError: Undefined Method 'tasks'" error.
I looked into Named Scopes to get the Project by Tag results but it seems like people are moving away from that approach in favor of class methods. Is that true?
On your project model you need to add it to the class not the instance. Also note that this raises the self object to the class so you can eliminate "Project." unless you want to be explicit.
class << self
def by_tag(tag_id)
joins(:tasks => :tags).where(:tags => {:id = tag_id})
end
end
There is always debate over what is the best method. I myself prefer whatever gets the job done quicker. I like scopes personally but to each his own.
I'm getting some strange behaviour when fetching collections from a has_many association with rails 3 when using STI. I have:
class Branch < ActiveRecord::Base
has_many :employees, class_name: 'User::Employee'
has_many :admins, class_name: 'User::BranchAdmin'
end
class User < ActiveRecord::Base
end
class User::Employee < User
belongs_to :branch
end
class User::BranchAdmin < User::Employee
end
The desired behaviour is that branch.employees returns all employees including branch admins. The branch admins only seem to be 'loaded' under this collection when they have been accessed by branch.admins, this is output from the console:
Branch.first.employees.count
=> 2
Branch.first.admins.count
=> 1
Branch.first.employees.count
=> 3
This can be seen in the generated SQL, the first time:
SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee') AND "users"."branch_id" = 1
and the second time:
SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee', 'User::BranchAdmin') AND "users"."branch_id" = 1
I could solve this problem by just specifying:
class Branch < ActiveRecord::Base
has_many :employees, class_name: 'User'
has_many :admins, class_name: 'User::BranchAdmin'
end
since they all be found from their branch_id but this creates problems in the controller if I want to do branch.employees.build then the class will default to User and I have to hack at the type column somewhere. I have got around this for now with:
has_many :employees, class_name: 'User::Employee',
finder_sql: Proc.new{
%Q(SELECT users.* FROM users WHERE users.type IN ('User::Employee','User::BranchAdmin') AND users.branch_id = #{id})
},
counter_sql: Proc.new{
%Q(SELECT COUNT(*) FROM "users" WHERE "users"."type" IN ('User::Employee', 'User::BranchAdmin') AND "users"."branch_id" = #{id})
}
but I would really like to avoid this if possible. Anyone, any ideas?
EDIT:
The finder_sql and counter_sql haven't really solved it for me because it seems that parent associations don't use this and so organisation.employees that has_many :employees, through: :branches will again only include the User::Employee class in the selection.
Basically, the problem only exists in the development environment where classes are loaded as needed. (In production, classes are loaded and kept available.)
The problem comes in due to the interpreter not having seen yet that Admins are a type of Employee when you first run the Employee.find, etc. call.
(Notice that it later uses IN ('User::Employee', 'User::BranchAdmin'))
This happens with every use of model classes that are more than one level deep, but only in dev-mode.
Subclasses always autoload their parent hierarchy. Base classes don't autoload their child hierachies.
Hack-fix:
You can force the correct behaviour in dev-mode by explicitly requiring all your child classes from the base class rb file.
Can you use :conditions?
class Branch < ActiveRecord::Base
has_many :employees, class_name: 'User::Employee', :conditions => {:type => "User::Employee"}
has_many :admins, class_name: 'User::BranchAdmin', :conditions => {:type => "User::BranchAdmin"}
end
This would be my preferred method. One other way to do it might be to add a default scope to the polymorphic models.
class User::BranchAdmin < User::Employee
default_scope where("type = ?", name)
end
A similar problem continues to exist in Rails 6.
This link outlines the issue and workaround. It contains the following explanation and code snippet:
Active Record needs to have STI hierarchies fully loaded in order to generate correct SQL. Preloading in Zeitwerk was designed for this use case:
By preloading the leaves of the tree, autoloading will take care of the entire hierarchy upwards following superclasses.
These files are going to be preloaded on boot, and on each reload.
# config/initializers/preload_vehicle_sti.rb
autoloader = Rails.autoloaders.main
sti_leaves = %w(car motorbike truck)
sti_leaves.each do |leaf|
autoloader.preload("#{Rails.root}/app/models/#{leaf}.rb")
end
You may require a spring stop for the configuration changes to take.
Indeed, that was the plan in the early days of the gem, but it was abandoned soon (in 2019, before Rails 6 was out). Preloading has been deprecated for a long time, and has been deleted in the forthcoming Zeitwerk 2.5.
In a Rails application you can do it this way:
# config/initializers/preload_vehicle_sti.rb
Rails.application.config.to_prepare do
Car
Motorbike
Truck
end
That is, you "preload" just by using the constants in a to_prepare block.