Associations, Joins and Scopes - ruby-on-rails

I have the following setup:
class Program
has_many :participants
end
class Participant
belongs_to :user
end
class User
has_many :participants
end
I want class method or scope to return all the programs in which a certain user participates. Here's what I have so far:
def self.where_user_participates(user)
Program.joins(:participants).where('participants.user_id' => user.id)
end
I believe that works but I am not in love with it. I prefer not to talk about 'id's but use the associations, but I could not get it to work, e.g.:
def self.where_user_participates(user)
Program.joins(:participants).where('participants.user' => user)
end
How can I improve this? And is it true that official 'scope's are not needed and a class method is 'best practice' in Rails 3?

class Program
has_many :participants
end
class Participant
belongs_to :user
belongs_to :program
end
class User
has_many :participants
has_many :programs, :through => :participants
end
Then to get the programs call:
user.programs

Related

Rails: alternatives to using nested loops to find match in controller

There has to be a better way to do this. My Favorite model belongs to User while Applicant belongs to both Gig and User. I am trying to efficiently determine whether a user has applied for Gig that was favorited (<% if #application.present? %>).
I tried chaining the collection by using something like #favorites.each.gig to no avail. While the below index action for Favorites seems to work, it's really verbose and inefficient. What is a more succinct way of doing this?
def index
#favorites = Favorite.where(:candidate_id => current_candidate)
#applications = Applicant.where(:candidate_id => current_candidate)
#favorites.each do |favorite|
#applications.each do |application|
if favorite.gig.id == application.id
#application = application
end
end
end
end
class User
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Favorite < ActiveRecord::Base
belongs_to :candidate
belongs_to :gig
end
class Applicant < ActiveRecord::Base
belongs_to :gig
belongs_to :candidate
end
class Candidate < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Gig < ActiveRecord::Base
belongs_to :employer
has_many :applicants
has_many :favorites
has_many :users, :through => :applicants
end
For lack of a better answer, here's my idea:
--
User
Your user model should be structured as such (I just highlighted foreign keys, which I imagine you'd have anyway):
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants, foreign_key: "candidate_id"
has_many :favorites, foreign_key: "candidate_id"
end
This means you'll be able to call:
current_candidate.favorites
current_candidate.applicants
This will remove the need for your #applications and #favorites queries
--
Favorite
You basically want to return a boolean of whether applicant is part of the favorite model or not. In essence, for each favorite the candidate has made, you'll be able to check if it's got an application
I would do this by setting an instance method on your favorites method using an ActiveRecord Association Extension, like so:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :favorites do
def applied?
self.applicant.exists? proxy_association.owner.gig.id
end
end
end
This will allow you to call:
<%= for favorite in current_candidate.favorites do %>
<%= if favorite.applied? %>
<% end %>
This is untested & highly speculative. I hope it gives you some ideas, though!

How to use scopes to join across multiple tables

I am new to ROR and I am trying to understand scopes. In my current implementation I am getting all the Processors and displaying it in the view.
class ProcessorsController
def index
#processors = Processor.all
end
end
I want to modify this so I can get only the processors where the user is admin. This is how my relations are set up.
class Processor
belongs_to :feed
#SCOPES (what I have done so far)
scope :feed, joins(:feed)
scope :groups, joins(:feed => :groups).join(:user).where(:admin => true)
end
class Feed < ActiveRecord::Base
has_and_belongs_to_many :groups
end
class Group < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :groups
scope :admin, where(:admin => true)
end
I was able to do this in my pry
pry(main)> Processor.find(63).feed.groups.first.user.admin?
PS: could someone provide some good resources where I could learn how to use scopes if the relationships are complex.
scope :with_admin, -> { joins(:feed => { :groups => :user }).where('users.admin' => true) }
As for the resources, have you gone through the official documentation on ActiveRecord joins?
you do not need scopes... you can get only the processors where the user is admin using relations and conditions:
class Feed < ActiveRecord::Base
...
has_one :user, through: :groups
end
class Processor
...
has_one :admin, through: :feed, source: :user, conditions: ['users.admin = 1']
end

How to combine two has_many associations into one?

This seems to be a fairly common problem over here, yet there is no definitive solution. To restate once again, say I have a model:
def Model < ActiveRecord::Base
has_many :somethings, ...
has_many :otherthings, ...
end
The question is then how to add a third association :combined that combines the two? I know this can be done with :finder_sql and similar result can be achieved with a scope, but neither of these gives me an actual association. The whole point of this is to be able to use it for another association with :through and things like Model.first.combined.some_scope.count
EDIT: the relevant portions of the actual code
class Donation < ActiveRecord::Base
# either Project or Nonprofit
belongs_to :donatable, :polymorphic => true
belongs_to :account
end
class Project < ActiveRecord::Base
belongs_to :nonprofit
end
class Nonprofit < ActiveRecord::Base
has_many :projects
# donations can be either direct or through a project
# the next two associations work fine on their own
# has_many :donations, :as => :donatable, :through => :projects
# has_many :donations, :as => :donatable
has_many :donations, .... # how do I get both here,
has_many :supporters, :through => :donations # for this to work?
end
Thanks.
If Something and Otherthing are sufficiently similar, use STI:
def Model < ActiveRecord::Base
has_many :somethings
has_many :otherthings
has_many :genericthings
end
def Genericthing < Activerecord::Base
# put a string column named "type" in the table
belongs_to :model
end
def Something < Genericthing
end
def Otherthing < Genericthing
end

How to traverse multiple many to many associations in activerecord

I am building a authorization framework that will eventually use cancan at the code level. I am creating the model and associations and have things almost perfect, but I ran into a snag.
I have User, Roles and Rights with many to many join tables (user_roles and role_rights) and I have things setup so that you can do User.roles and User.roles.first.rights but I would like to be able to do User.rights
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, :through => :user_roles
end
class UserRole < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :user_roles
has_many :users, :through => :users_roles
has_many :role_rights
has_many :rights, :through => :role_rights
end
class RoleRight < ActiveRecord::Base
belongs_to :role
belongs_to :right
end
class Right < ActiveRecord::Base
has_many :role_rights
has_many :roles, :through => :role_rights
end
The following works:
User.roles
so does this:
User.roles.first.rights
but what I want to do is:
User.rights
but when I try, I get the follow error: NoMethodError: undefined method `rights'
I assume that I need to add something to the User model to let it transverse to the Right model but I can't figure out the associations.
I'm using Rails 2.3.4 and Ruby 1.8.7
Try something like this:
class User < ActiveRecord::Base
def self.rights
Right.joins(:roles => :user).all("users.id = ?", self.id)
end
end

Rails has_many :through and has_one :through associations

First I'm using Rails 3.1 from the 3-1-stable branch updated an hour ago.
I'm developing an application where I have 3 essential models User, Company and Job, Here's the relevant part of the models:
class User < ActiveRecord::Base
has_many :companies_users, class_name: "CompaniesUsers"
has_many :companies, :through => :companies_users, :source => :company
end
class Company < ActiveRecord::Base
has_many :companies_users, class_name: "CompaniesUsers"
has_many :employees, :through => :companies_users, :source => :user
has_many :jobs, :dependent => :destroy
end
class Job < ActiveRecord::Base
belongs_to :company, :counter_cache => true
end
class CompaniesUsers < ActiveRecord::Base
belongs_to :company
belongs_to :user
end
The code works just fine, but I have been wondering if it's possible to:
I want to link a job with an employer, so think of this scenario: A user John who's an employee at Example, he posted the job Rails Developer, so I want to access #job.employer and it should get me back the user John, in other words:
#user = User.find_by_name('john')
#job = Job.find(1)
#job.employer == #user #=> true
So I thought of two possible solutions
First solution
class Job
has_one :employer, :through => :employers
end
class User
has_many :jobs, :through => :employers
end
class Employer
belongs_to :job
belongs_to :user
end
Second solution
class Job
has_one :employer, :class_name => "User"
end
class User
belongs_to :job
end
Which route should I go? Is my code right ?
I have another question, how to get rid of the class_name => "CompaniesUsers" option passed to has_many, should the class be Singular or Plural ? Should I rename it to something like Employees ?
P.S: I posted the same question to Ruby on Rails: Talk
Unless I'm missing something, I'd suggest simply doing
class Job
belongs_to :employer, :class_name => "User"
end
class User
has_many :jobs
end
This would give you methods like
user = User.first
user.jobs.create(params)
user.jobs # array
job = user.jobs.first
job.employer == user # true
You'll need an employer_id integer field in your Jobs table for this to work.
Typically you want to name your pass through model:
company_user
Then you don't need this:
class_name: "CompaniesUsers"
Just make sure the name of your database table is:
company_users
What you have works for you, so that's great. I just find when I don't follow convention I
run in to trouble down the road.

Resources