Rails: alternatives to using nested loops to find match in controller - ruby-on-rails

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!

Related

Associations, Joins and Scopes

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

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

Listing all instances of a document model that belongs to user through group relationship

I basically followed the ROR guide, http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association, to create the relationship models as shown below.
Because of the through association, I figured that #user.trips would give you both the trips that the user created and the trips that belong to the user. However, when I do #user.trips.count in console, the result was only the number of trips that the users created; the trips that belonged to the user through the 'group' association was not counted.
Question: How do I get my view to display both the trips that the user created and the trips that the user belongs to through 'group'?
user/show.html.erb
<% unless #user.all_trips.empty? %>
<% #user.all_trips.each do |trip| %>
<!-- Content -->
<% end %>
<% end %>
user.rb
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:source => :trip
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
self.trips | self.group_trips
end
end
trip.rb
class Trip < ActiveRecord::Base
belongs_to :user
belongs_to :traveldeal
has_many :groups
has_many :users, :through => :groups
end
group.rb
class Group < ActiveRecord::Base
belongs_to :trip
belongs_to :user
end
Thanks!
Edit: Modified code per TSherif's partial solution.
Edit 2: Fixed up the all_trips method. Everything appears to work for me at this point.
Oh! I think I get what you're trying to do and why it's a problem. I was wondering why has_many :trips was called twice. But from what I understand, you have two different User-Trip relationships. These two can't have the same name, otherwise one will hide the other. Try something like this:
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:class_name => "Trip"
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
Trip.joins(:groups).where({:user_id => self.id} | {:groups => {:user_id => self.id}})
end
end
Or if you're using an older version of Rails that doesn't have MetaWhere:
def all_trips
Trip.joins(:groups).where("(trips.user_id = ?) OR (groups.user_id = ?)", self.id, self.id)
end

Rails: has_many through with polymorphic association - will this work?

A Person can have many Events and each Event can have one polymorphic Eventable record. How do I specify the relationship between the Person and the Eventable record?
Here are the models I have:
class Event < ActiveRecord::Base
belongs_to :person
belongs_to :eventable, :polymorphic => true
end
class Meal < ActiveRecord::Base
has_one :event, :as => eventable
end
class Workout < ActiveRecord::Base
has_one :event, :as => eventable
end
The main question concerns the Person class:
class Person < ActiveRecord::Base
has_many :events
has_many :eventables, :through => :events # is this correct???
end
Do I say has_many :eventables, :through => :events like I did above?
Or do I have to spell them all out like so:
has_many :meals, :through => :events
has_many :workouts, :through => :events
If you see an easier way to accomplish what I'm after, I'm all ears! :-)
You have to do:
class Person < ActiveRecord::Base
has_many :events
has_many :meals, :through => :events, :source => :eventable,
:source_type => "Meal"
has_many :workouts, :through => :events, :source => :eventable,
:source_type => "Workout"
end
This will enable you to do this:
p = Person.find(1)
# get a person's meals
p.meals.each do |m|
puts m
end
# get a person's workouts
p.workouts.each do |w|
puts w
end
# get all types of events for the person
p.events.each do |e|
puts e.eventable
end
Another option of this is to use a Single Table Inheritance (STI) or Multi Table Inheritance (MTI) pattern, but that requires some ActiveRecord/DB Table rework, but this may help others still finding this who are designing it for the first time.
Here is the STI method in Rails 3+:
Your Eventable concept becomes a class and needs a type column (which rails automatically populates for you).
class Eventable < ActiveRecord::Base
has_one :event
end
Then, your other two classes inherit from Eventable instead of AR::Base
class Meal < Eventable
end
class Workout < Eventable
end
And your event object is basically the same, just not polymorphic:
class Event < ActiveRecord::Base
belongs_to :person
belongs_to :eventable
end
This may make some of your other layers more confusing, if you've never seen this before and you're not careful. For example, a single Meal object can be accessed at /meals/1 and /eventable/1 if you make both endpoints available in the routes, and you need to be aware of the class you're using when you pull an inherited object (hint: the becomes method may be very useful if you need to override the default rails behavior)
But this is a much cleaner deliniation of responsibilities as apps scale, in my experience. Just a pattern to consider.

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